Skip to content

Commit

Permalink
Merge pull request #1984 from AntaresSimulatorTeam/feature/ANT-1357_c…
Browse files Browse the repository at this point in the history
…luster
  • Loading branch information
skamril authored Apr 18, 2024
2 parents e524b13 + b0782af commit b42fee2
Show file tree
Hide file tree
Showing 28 changed files with 1,048 additions and 863 deletions.
10 changes: 7 additions & 3 deletions webapp/public/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"global.assign": "Assign",
"global.undo": "Undo",
"global.redo": "Redo",
"global.total": "Total",
"global.time.hourly": "Hourly",
"global.time.daily": "Daily",
"global.time.weekly": "Weekly",
Expand All @@ -81,6 +82,8 @@
"global.error.failedtoretrievejobs": "Failed to retrieve job information",
"global.error.failedtoretrievelogs": "Failed to retrieve job logs",
"global.error.failedtoretrievedownloads": "Failed to retrieve downloads list",
"global.error.create": "Creation failed",
"global.error.delete": "Deletion failed",
"global.area.add": "Add an area",
"login.error": "Failed to authenticate",
"tasks.title": "Tasks",
Expand All @@ -94,6 +97,7 @@
"data.title": "Data",
"dialog.title.confirmation": "Confirmation",
"dialog.message.logout": "Are you sure you want to logout?",
"dialog.message.confirmDelete": "Do you confirm the deletion?",
"button.collapse": "Collapse",
"button.expand": "Expand",
"button.yes": "Yes",
Expand All @@ -117,7 +121,7 @@
"form.submit.inProgress": "The form is being submitted. Are you sure you want to leave the page?",
"form.asyncDefaultValues.error": "Failed to get values",
"form.field.required": "Field required",
"form.field.duplicate": "Value already exists: {{0}}",
"form.field.duplicate": "Value already exists",
"form.field.minLength": "{{0}} character(s) minimum",
"form.field.minValue": "The minimum value is {{0}}",
"form.field.maxValue": "The maximum value is {{0}}",
Expand Down Expand Up @@ -486,8 +490,8 @@
"study.modelization.clusters.matrix.timeSeries": "Time-Series",
"study.modelization.clusters.backClusterList": "Back to cluster list",
"study.modelization.clusters.tsInterpretation": "TS interpretation",
"study.modelization.clusters.group": "Group",
"studies.modelization.clusters.question.delete": "Are you sure you want to delete this cluster?",
"studies.modelization.clusters.question.delete_one": "Are you sure you want to delete this cluster?",
"studies.modelization.clusters.question.delete_other": "Are you sure you want to delete these {{count}} clusters?",
"study.modelization.bindingConst.comments": "Comments",
"study.modelization.bindingConst.type": "Type",
"study.modelization.bindingConst.constraints": "Constraints",
Expand Down
10 changes: 7 additions & 3 deletions webapp/public/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"global.assign": "Assigner",
"global.undo": "Annuler",
"global.redo": "Rétablir",
"global.total": "Total",
"global.time.hourly": "Horaire",
"global.time.daily": "Journalier",
"global.time.weekly": "Hebdomadaire",
Expand All @@ -81,6 +82,8 @@
"global.error.failedtoretrievejobs": "Échec de la récupération des tâches",
"global.error.failedtoretrievelogs": "Échec de la récupération des logs",
"global.error.failedtoretrievedownloads": "Échec de la récupération des exports",
"global.error.create": "La création a échoué",
"global.error.delete": "La suppression a échoué",
"global.area.add": "Ajouter une zone",
"login.error": "Échec de l'authentification",
"tasks.title": "Tâches",
Expand All @@ -94,6 +97,7 @@
"data.title": "Données",
"dialog.title.confirmation": "Confirmation",
"dialog.message.logout": "Êtes vous sûr de vouloir vous déconnecter ?",
"dialog.message.confirmDelete": "Confirmez-vous la suppression ?",
"button.collapse": "Réduire",
"button.expand": "Étendre",
"button.yes": "Oui",
Expand All @@ -117,7 +121,7 @@
"form.submit.inProgress": "Le formulaire est en cours de soumission. Etes-vous sûr de vouloir quitter la page ?",
"form.asyncDefaultValues.error": "Impossible d'obtenir les valeurs",
"form.field.required": "Champ requis",
"form.field.duplicate": "Cette valeur existe déjà: {{0}}",
"form.field.duplicate": "Cette valeur existe déjà",
"form.field.minLength": "{{0}} caractère(s) minimum",
"form.field.minValue": "La valeur minimum est {{0}}",
"form.field.maxValue": "La valeur maximum est {{0}}",
Expand Down Expand Up @@ -486,8 +490,8 @@
"study.modelization.clusters.matrix.timeSeries": "Séries temporelles",
"study.modelization.clusters.backClusterList": "Retour à la liste des clusters",
"study.modelization.clusters.tsInterpretation": "TS interpretation",
"study.modelization.clusters.group": "Groupes",
"studies.modelization.clusters.question.delete": "Êtes-vous sûr de vouloir supprimer ce cluster ?",
"studies.modelization.clusters.question.delete_one": "Êtes-vous sûr de vouloir supprimer ce cluster ?",
"studies.modelization.clusters.question.delete_other": "Êtes-vous sûr de vouloir supprimer ces {{count}} clusters ?",
"study.modelization.bindingConst.comments": "Commentaires",
"study.modelization.bindingConst.type": "Type",
"study.modelization.bindingConst.constraints": "Contraintes",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function Fields() {
disabled
/>
<SelectFE
label={t("study.modelization.clusters.group")}
label={t("global.group")}
name="group"
control={control}
options={RENEWABLE_GROUPS}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,198 +1,169 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "material-react-table";
import { Box, Chip } from "@mui/material";
import { useMemo, useState } from "react";
import { createMRTColumnHelper } from "material-react-table";
import { Box } from "@mui/material";
import { useLocation, useNavigate, useOutletContext } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { StudyMetadata } from "../../../../../../../common/types";
import {
RENEWABLE_GROUPS,
RenewableCluster,
RenewableClusterWithCapacity,
RenewableGroup,
createRenewableCluster,
deleteRenewableClusters,
duplicateRenewableCluster,
getRenewableClusters,
type RenewableClusterWithCapacity,
} from "./utils";
import useAppSelector from "../../../../../../../redux/hooks/useAppSelector";
import { getCurrentAreaId } from "../../../../../../../redux/selectors";
import GroupedDataTable from "../../../../../../common/GroupedDataTable";
import {
addClusterCapacity,
capacityAggregationFn,
useClusterDataWithCapacity,
} from "../common/utils";
import SimpleLoader from "../../../../../../common/loaders/SimpleLoader";
import SimpleContent from "../../../../../../common/page/SimpleContent";
import UsePromiseCond from "../../../../../../common/utils/UsePromiseCond";
getClustersWithCapacityTotals,
} from "../common/clustersUtils";
import { TRow } from "../../../../../../common/GroupedDataTable/types";
import BooleanCell from "../../../../../../common/GroupedDataTable/cellRenderers/BooleanCell";
import usePromiseWithSnackbarError from "../../../../../../../hooks/usePromiseWithSnackbarError";

const columnHelper = createMRTColumnHelper<RenewableClusterWithCapacity>();

function Renewables() {
const { study } = useOutletContext<{ study: StudyMetadata }>();
const [t] = useTranslation();
const areaId = useAppSelector(getCurrentAreaId);
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const areaId = useAppSelector(getCurrentAreaId);

const {
clusters,
clustersWithCapacity,
totalUnitCount,
totalInstalledCapacity,
totalEnabledCapacity,
} = useClusterDataWithCapacity<RenewableCluster>(
() => getRenewableClusters(study.id, areaId),
t("studies.error.retrieveData"),
[study.id, areaId],
);

const columns = useMemo<Array<MRT_ColumnDef<RenewableClusterWithCapacity>>>(
() => [
{
accessorKey: "name",
header: "Name",
muiTableHeadCellProps: {
align: "left",
},
muiTableBodyCellProps: {
align: "left",
},
size: 100,
Cell: ({ renderedCellValue, row }) => {
const clusterId = row.original.id;
return (
<Box
sx={{
cursor: "pointer",
"&:hover": {
color: "primary.main",
textDecoration: "underline",
},
}}
onClick={() => navigate(`${location.pathname}/${clusterId}`)}
>
{renderedCellValue}
</Box>
);
},
const { data: clustersWithCapacity = [], isLoading } =
usePromiseWithSnackbarError<RenewableClusterWithCapacity[]>(
async () => {
const clusters = await getRenewableClusters(study.id, areaId);
return clusters?.map(addClusterCapacity);
},
{
accessorKey: "group",
header: "Group",
size: 50,
filterVariant: "select",
filterSelectOptions: [...RENEWABLE_GROUPS],
muiTableHeadCellProps: {
align: "left",
},
muiTableBodyCellProps: {
align: "left",
},
Footer: () => (
<Box sx={{ display: "flex", alignItems: "flex-start" }}>Total:</Box>
),
resetDataOnReload: true,
errorMessage: t("studies.error.retrieveData"),
deps: [study.id, areaId],
},
{
accessorKey: "enabled",
);

const [totals, setTotals] = useState(
getClustersWithCapacityTotals(clustersWithCapacity),
);

const columns = useMemo(() => {
const { totalUnitCount, totalEnabledCapacity, totalInstalledCapacity } =
totals;

return [
columnHelper.accessor("enabled", {
header: "Enabled",
size: 50,
filterVariant: "checkbox",
Cell: ({ cell }) => (
<Chip
label={cell.getValue<boolean>() ? t("button.yes") : t("button.no")}
color={cell.getValue<boolean>() ? "success" : "error"}
size="small"
sx={{ minWidth: 40 }}
/>
),
},
{
accessorKey: "tsInterpretation",
Cell: BooleanCell,
}),
columnHelper.accessor("tsInterpretation", {
header: "TS Interpretation",
size: 50,
},
{
accessorKey: "unitCount",
}),
columnHelper.accessor("unitCount", {
header: "Unit Count",
size: 50,
aggregationFn: "sum",
AggregatedCell: ({ cell }) => (
<Box sx={{ color: "info.main", fontWeight: "bold" }}>
{cell.getValue<number>()}
{cell.getValue()}
</Box>
),
Footer: () => <Box color="warning.main">{totalUnitCount}</Box>,
},
{
accessorKey: "nominalCapacity",
}),
columnHelper.accessor("nominalCapacity", {
header: "Nominal Capacity (MW)",
size: 200,
Cell: ({ cell }) => Math.floor(cell.getValue<number>()),
},
{
accessorKey: "installedCapacity",
size: 220,
Cell: ({ cell }) => Math.floor(cell.getValue()),
}),
columnHelper.accessor("installedCapacity", {
header: "Enabled / Installed (MW)",
size: 200,
size: 220,
aggregationFn: capacityAggregationFn(),
AggregatedCell: ({ cell }) => (
<Box sx={{ color: "info.main", fontWeight: "bold" }}>
{cell.getValue<string>() ?? ""}
{cell.getValue() ?? ""}
</Box>
),
Cell: ({ row }) => (
<>
{Math.floor(row.original.enabledCapacity ?? 0)} /{" "}
{Math.floor(row.original.installedCapacity ?? 0)}
{Math.floor(row.original.enabledCapacity)} /{" "}
{Math.floor(row.original.installedCapacity)}
</>
),
Footer: () => (
<Box color="warning.main">
{totalEnabledCapacity} / {totalInstalledCapacity}
</Box>
),
},
],
[
location.pathname,
navigate,
t,
totalEnabledCapacity,
totalInstalledCapacity,
totalUnitCount,
],
);
}),
];
}, [totals]);

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

const handleCreateRow = ({
id,
installedCapacity,
enabledCapacity,
...cluster
}: RenewableClusterWithCapacity) => {
return createRenewableCluster(study.id, areaId, cluster);
const handleCreate = async (values: TRow<RenewableGroup>) => {
const cluster = await createRenewableCluster(study.id, areaId, values);
return addClusterCapacity(cluster);
};

const handleDeleteSelection = (ids: string[]) => {
const handleDuplicate = async (
row: RenewableClusterWithCapacity,
newName: string,
) => {
const cluster = await duplicateRenewableCluster(
study.id,
areaId,
row.id,
newName,
);

return { ...row, ...cluster };
};

const handleDelete = (rows: RenewableClusterWithCapacity[]) => {
const ids = rows.map((row) => row.id);
return deleteRenewableClusters(study.id, areaId, ids);
};

const handleNameClick = (row: RenewableClusterWithCapacity) => {
navigate(`${location.pathname}/${row.id}`);
};

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

return (
<UsePromiseCond
response={clusters}
ifPending={() => <SimpleLoader />}
ifResolved={() => (
<GroupedDataTable
data={clustersWithCapacity}
columns={columns}
groups={RENEWABLE_GROUPS}
onCreate={handleCreateRow}
onDelete={handleDeleteSelection}
/>
)}
ifRejected={(error) => <SimpleContent title={error?.toString()} />}
<GroupedDataTable
isLoading={isLoading}
data={clustersWithCapacity}
columns={columns}
groups={[...RENEWABLE_GROUPS]}
onCreate={handleCreate}
onDuplicate={handleDuplicate}
onDelete={handleDelete}
onNameClick={handleNameClick}
deleteConfirmationMessage={(count) =>
t("studies.modelization.clusters.question.delete", { count })
}
fillPendingRow={(row) => ({
unitCount: 0,
enabledCapacity: 0,
installedCapacity: 0,
...row,
})}
onDataChange={(data) => {
setTotals(getClustersWithCapacityTotals(data));
}}
/>
);
}
Expand Down
Loading

0 comments on commit b42fee2

Please sign in to comment.