Skip to content

Commit

Permalink
feat(ui-storages): add storages list view and form
Browse files Browse the repository at this point in the history
  • Loading branch information
hdinia committed Oct 31, 2023
1 parent 47737e2 commit 8205f70
Show file tree
Hide file tree
Showing 10 changed files with 645 additions and 27 deletions.
8 changes: 8 additions & 0 deletions webapp/public/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,14 @@
"study.modelization.hydro.correlation.coefficient": "Coeff. (%)",
"study.modelization.hydro.allocation.viewMatrix": "View all allocations",
"study.modelization.hydro.allocation.error.field.delete": "Error when deleting the allocation",
"study.modelization.storages": "Storages",
"study.modelization.storages.capacities": "Injection/withdrawal capacities",
"study.modelization.storages.ruleCurves": "Rule Curves",
"study.modelization.storages.inflows": "inflows",
"study.modelization.storages.chargeCapacity": "Charge capacity",
"study.modelization.storages.dischargeCapacity": "Discharge capacity",
"study.modelization.storages.lowerRuleCurve": "Lower rule curve",
"study.modelization.storages.upperRuleCurve": "Upper rule curve",
"study.modelization.wind": "Wind",
"study.modelization.solar": "Solar",
"study.modelization.renewables": "Renewables Clus.",
Expand Down
8 changes: 8 additions & 0 deletions webapp/public/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,14 @@
"study.modelization.hydro.correlation.coefficient": "Coeff. (%)",
"study.modelization.hydro.allocation.viewMatrix": "Voir les allocations",
"study.modelization.hydro.allocation.error.field.delete": "Erreur lors de la suppression de l'allocation",
"study.modelization.storages": "Stockages",
"study.modelization.storages.capacities": "Capacités d'injection / soutirage",
"study.modelization.storages.ruleCurves": "Courbe guides",
"study.modelization.storages.inflows": "Apports",
"study.modelization.storages.chargeCapacity": "Capacité de charge",
"study.modelization.storages.dischargeCapacity": "Capacité de décharge",
"study.modelization.storages.lowerRuleCurve": "Courbe guide inférieure",
"study.modelization.storages.upperRuleCurve": "Courbe guide supérieure",
"study.modelization.wind": "Éolien",
"study.modelization.solar": "Solaire",
"study.modelization.renewables": "Clus. Renouvelables",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ function AreasTab(props: Props) {
label: t("study.modelization.thermal"),
path: `/studies/${study.id}/explore/modelization/area/${areaId}/thermal`,
},
{
label: t("study.modelization.storages"),
path: `/studies/${study.id}/explore/modelization/area/${areaId}/storages`,
},
{
label: t("study.modelization.hydro"),
path: `/studies/${study.id}/explore/modelization/area/${areaId}/hydro`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useTranslation } from "react-i18next";
import NumberFE from "../../../../../../common/fieldEditors/NumberFE";
import SelectFE from "../../../../../../common/fieldEditors/SelectFE";
import StringFE from "../../../../../../common/fieldEditors/StringFE";
import SwitchFE from "../../../../../../common/fieldEditors/SwitchFE";
import Fieldset from "../../../../../../common/Fieldset";
import { useFormContextPlus } from "../../../../../../common/Form";
import { STORAGE_GROUP_OPTIONS, Storage } from "./utils";

function Fields() {
const [t] = useTranslation();
const { control } = useFormContextPlus<Storage>();

////////////////////////////////////////////////////////////////
// JSX
////////////////////////////////////////////////////////////////

return (
<Fieldset legend={t("global.general")}>
<StringFE
label={t("global.name")}
name="name"
control={control}
disabled
/>
<SelectFE
label={t("study.modelization.clusters.group")}
name="group"
control={control}
options={STORAGE_GROUP_OPTIONS}
sx={{
alignSelf: "center",
}}
/>
<NumberFE
label="Injection Nominal Capacity"
name="injectionNominalCapacity"
control={control}
/>
<NumberFE
label="Withdrawal Nominal Capacity"
name="withdrawalNominalCapacity"
control={control}
/>
<NumberFE
label="Reservoir Capacity"
name="reservoirCapacity"
control={control}
/>
<NumberFE label="Efficiency" name="efficiency" control={control} />
<NumberFE label="Initial Level" name="initialLevel" control={control} />
<SwitchFE
label="Initial Level Optim"
name="initialLevelOptim"
control={control}
sx={{
alignItems: "center",
alignSelf: "center",
}}
/>
</Fieldset>
);
}

export default Fields;
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useCallback } from "react";
import { Box, Button } from "@mui/material";
import { useParams, useOutletContext, useNavigate } from "react-router-dom";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import { useTranslation } from "react-i18next";
import { StudyMetadata } from "../../../../../../../common/types";
import Form from "../../../../../../common/Form";
import Fields from "./Fields";
import { SubmitHandlerPlus } from "../../../../../../common/Form/types";
import useAppSelector from "../../../../../../../redux/hooks/useAppSelector";
import { getCurrentAreaId } from "../../../../../../../redux/selectors";
import { Storage, getStorage, updateStorage } from "./utils";
import Matrix from "./Matrix";
import { nameToId } from "../../../../../../../services/utils";
import useNavigateOnCondition from "../../../../../../../hooks/useNavigateOnCondition";

function StorageForm() {
const { t } = useTranslation();
const { study } = useOutletContext<{ study: StudyMetadata }>();
const navigate = useNavigate();
const areaId = useAppSelector(getCurrentAreaId);
const { storageId = "" } = useParams();

useNavigateOnCondition({
deps: [areaId],
to: `../storages`,
});

// prevent re-fetch while useNavigateOnCondition event occurs
const defaultValues = useCallback(() => {
return getStorage(study.id, areaId, storageId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

////////////////////////////////////////////////////////////////
// Event handlers
////////////////////////////////////////////////////////////////

const handleSubmit = ({ dirtyValues }: SubmitHandlerPlus<Storage>) => {
return updateStorage(study.id, areaId, storageId, dirtyValues);
};

////////////////////////////////////////////////////////////////
// JSX
////////////////////////////////////////////////////////////////

return (
<Box sx={{ width: 1, p: 1, overflow: "auto" }}>
<Button
color="secondary"
size="small"
onClick={() => navigate("../storages")}
startIcon={<ArrowBackIcon color="secondary" />}
sx={{ alignSelf: "flex-start", px: 0 }}
>
{t("button.back")}
</Button>
<Form
key={study.id + areaId}
config={{
defaultValues,
}}
onSubmit={handleSubmit}
autoSubmit
>
<Fields />
<Box
sx={{
width: 1,
display: "flex",
flexDirection: "column",
height: "500px",
}}
>
<Matrix
study={study}
areaId={areaId}
storageId={nameToId(storageId)}
/>
</Box>
</Form>
</Box>
);
}

export default StorageForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/* eslint-disable react/jsx-props-no-spreading */
import * as React from "react";
import * as R from "ramda";
import { styled } from "@mui/material";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box";
import { useTranslation } from "react-i18next";
import { MatrixStats, StudyMetadata } from "../../../../../../../common/types";
import MatrixInput from "../../../../../../common/MatrixInput";
import { Storage } from "./utils";
import SplitLayoutView from "../../../../../../common/SplitLayoutView";

export const StyledTab = styled(Tabs)({
width: "98%",
borderBottom: 1,
borderColor: "divider",
});

interface Props {
study: StudyMetadata;
areaId: StudyMetadata["id"];
storageId: Storage["id"];
}

function Matrix({ study, areaId, storageId }: Props) {
const [t] = useTranslation();
const [value, setValue] = React.useState(0);

////////////////////////////////////////////////////////////////
// JSX
////////////////////////////////////////////////////////////////

return (
<Box
sx={{
display: "flex",
width: 1,
height: 1,
flexDirection: "column",
justifyContent: "flex-start",
alignItems: "center",
}}
>
<StyledTab value={value} onChange={(_, v) => setValue(v)}>
<Tab label={t("study.modelization.storages.capacities")} />
<Tab label={t("study.modelization.storages.ruleCurves")} />
<Tab label={t("study.modelization.storages.inflows")} />
</StyledTab>
<Box
sx={{
display: "flex",
width: 1,
height: 1,
}}
>
{R.cond([
[
() => value === 0,
() => (
<SplitLayoutView
left={
<MatrixInput
study={study}
url={`input/st-storage/series/${areaId}/${storageId}/pmax_injection`}
computStats={MatrixStats.NOCOL}
title={t("study.modelization.storages.chargeCapacity")}
/>
}
right={
<MatrixInput
study={study}
url={`input/st-storage/series/${areaId}/${storageId}/pmax_withdrawal`}
computStats={MatrixStats.NOCOL}
title={t("study.modelization.storages.dischargeCapacity")}
/>
}
sx={{
mt: 1,
".SplitLayoutView__Left": {
width: "50%",
},
".SplitLayoutView__Right": {
height: 1,
width: "50%",
},
}}
/>
),
],
[
() => value === 1,
() => (
<SplitLayoutView
left={
<MatrixInput
study={study}
url={`input/st-storage/series/${areaId}/${storageId}/lower_rule_curve`}
computStats={MatrixStats.NOCOL}
title={t("study.modelization.storages.lowerRuleCurve")}
/>
}
right={
<MatrixInput
study={study}
url={`input/st-storage/series/${areaId}/${storageId}/upper_rule_curve`}
computStats={MatrixStats.NOCOL}
title={t("study.modelization.storages.upperRuleCurve")}
/>
}
sx={{
mt: 1,
".SplitLayoutView__Left": {
width: "50%",
},
".SplitLayoutView__Right": {
height: 1,
width: "50%",
},
}}
/>
),
],
[
R.T,
() => (
<MatrixInput
study={study}
url={`input/st-storage/series/${areaId}/${storageId}/inflows`}
computStats={MatrixStats.NOCOL}
title={t("study.modelization.storages.inflows")}
/>
),
],
])()}
</Box>
</Box>
);
}

export default Matrix;
Loading

0 comments on commit 8205f70

Please sign in to comment.