Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions webapp/public/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,8 @@
"study.modeling.renewables.select": "Renewable",
"study.modeling.renewables.parameters": "Parameters",
"study.modeling.reserves": "Reserves",
"study.modeling.reserves.error.optimizationConfig": "Unable to retrieve the reserves configuration",
"study.modeling.reserves.readOnly.alert": "Reserves are read-only. Enable them in the optimization configuration to edit.",
"study.modeling.reserves.needs": "Needs",
"study.modeling.reserves.needs.select": "Reserve",
"study.modeling.reserves.needs.empty": "Create a reserve to define its needs",
Expand Down
2 changes: 2 additions & 0 deletions webapp/public/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,8 @@
"study.modeling.renewables.select": "Renouvelable",
"study.modeling.renewables.parameters": "Paramètres",
"study.modeling.reserves": "Réserves",
"study.modeling.reserves.error.optimizationConfig": "Impossible de récupérer la configuration des réserves",
"study.modeling.reserves.readOnly.alert": "Les réserves sont en lecture seule. Pour les modifier, activez-les dans la configuration d'optimisation.",
"study.modeling.reserves.needs": "Besoins",
"study.modeling.reserves.needs.select": "Réserve",
"study.modeling.reserves.needs.empty": "Créez une réserve pour définir ses besoins",
Expand Down
17 changes: 12 additions & 5 deletions webapp/src/components/GroupedDataTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface GroupedDataTableProps<
onNameClick?: (row: TData) => void;
nameLinkOptions?: (row: TData) => ToOptions;
onDataChange?: (data: TData[]) => void;
readOnly?: boolean;
isLoading?: boolean;
deleteConfirmationMessage?: string | ((rows: TData[]) => string);
fillPendingRow?: (
Expand All @@ -97,6 +98,7 @@ function GroupedDataTable<TGroups extends string[], TData extends RowData<TGroup
onNameClick,
nameLinkOptions,
onDataChange,
readOnly = false,
isLoading,
deleteConfirmationMessage,
fillPendingRow,
Expand All @@ -106,7 +108,7 @@ function GroupedDataTable<TGroups extends string[], TData extends RowData<TGroup
const [tableData, setTableData] = useState(data);
const [rowSelection, setRowSelection] = useState<MRT_RowSelectionState>({});
const enqueueErrorSnackbar = useEnqueueErrorSnackbar();
const callbacksRef = useUpdatedRef({ onNameClick, nameLinkOptions });
const callbacksRef = useUpdatedRef({ onNameClick, nameLinkOptions, readOnly });
const pendingRows = useRef<Array<RowData<TGroups[number]>>>([]);
const { createOps, deleteOps, totalOps } = useOperationInProgressCount();
const { isDarkMode } = useThemeColorScheme();
Expand Down Expand Up @@ -140,9 +142,9 @@ function GroupedDataTable<TGroups extends string[], TData extends RowData<TGroup
filterVariant: "autocomplete",
filterSelectOptions: existingNames,
Cell: ({ renderedCellValue, row }) => {
const { onNameClick, nameLinkOptions } = callbacksRef.current;
const { onNameClick, nameLinkOptions, readOnly } = callbacksRef.current;

if (isPendingRow(row.original)) {
if (isPendingRow(row.original) || readOnly) {
return renderedCellValue;
}

Expand Down Expand Up @@ -219,6 +221,10 @@ function GroupedDataTable<TGroups extends string[], TData extends RowData<TGroup
positionToolbarAlertBanner: "none",
// Rows
muiTableBodyRowProps: ({ row }) => {
if (readOnly) {
return {};
}

const isPending = isPendingRow(row.original);

return {
Expand Down Expand Up @@ -254,6 +260,7 @@ function GroupedDataTable<TGroups extends string[], TData extends RowData<TGroup
startIcon={<AddCircleOutlineIcon />}
variant="contained"
onClick={() => setOpenDialog("add")}
disabled={readOnly}
>
{t("button.add")}
</Button>
Expand All @@ -263,7 +270,7 @@ function GroupedDataTable<TGroups extends string[], TData extends RowData<TGroup
startIcon={<ContentCopyIcon />}
variant="outlined"
onClick={() => setOpenDialog("duplicate")}
disabled={table.getSelectedRowModel().rows.length !== 1}
disabled={readOnly || table.getSelectedRowModel().rows.length !== 1}
Comment thread
skamril marked this conversation as resolved.
>
{t("global.duplicate")}
</Button>
Expand All @@ -274,7 +281,7 @@ function GroupedDataTable<TGroups extends string[], TData extends RowData<TGroup
color="error"
variant="outlined"
onClick={() => setOpenDialog("delete")}
disabled={table.getSelectedRowModel().rows.length === 0}
disabled={readOnly || table.getSelectedRowModel().rows.length === 0}
>
{t("global.delete")}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@

import GroupedDataTable from "@/components/GroupedDataTable";
import type { RowData } from "@/components/GroupedDataTable/types";
import usePromiseWithSnackbarError from "@/hooks/usePromiseWithSnackbarError";
import { reserveMutations } from "@/queries/reserves/mutations";
import { reserveQueries } from "@/queries/reserves/queries";
import type { Reserve } from "@/services/api/studies/areas/reserves/types";
import { Chip } from "@mui/material";
import { getOptimization } from "@/services/api/studies/config/optimization";
import { Alert, Chip } from "@mui/material";
import { useMutation, useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
import { createMRTColumnHelper } from "material-react-table";
Expand Down Expand Up @@ -74,6 +76,16 @@ function ReservesGeneral() {
const { studyId, areaId } = Route.useParams();
const queryClient = useQueryClient();

// Reserves are editable only when enabled in the optimization configuration
// ("includeReserves"). Otherwise the table is shown in read-only mode.
const { data: showReserves, isLoading } = usePromiseWithSnackbarError(
() => getOptimization({ studyId }).then((o) => o.includeReserves),
{
errorMessage: t("study.modeling.reserves.error.optimizationConfig"),
deps: [studyId],
},
);

const { queryKey: listQueryKey } = reserveQueries.list(studyId, areaId);

const { data: rows } = useSuspenseQuery({
Expand Down Expand Up @@ -140,27 +152,36 @@ function ReservesGeneral() {
////////////////////////////////////////////////////////////////

return (
<GroupedDataTable
key={`${studyId}-${areaId}`}
data={rows}
columns={columns}
onCreate={handleCreate}
renderCreateDialog={({ open, onClose, onSubmit, existingNames }) => (
<CreateReserveDialog
open={open}
onClose={onClose}
onSubmit={onSubmit}
existingNames={existingNames}
/>
<>
{showReserves === false && (
<Alert severity="warning" sx={{ mb: 1 }}>
{t("study.modeling.reserves.readOnly.alert")}
</Alert>
)}
onDuplicate={handleDuplicate}
onDelete={handleDelete}
deleteConfirmationMessage={(rowsToDelete) =>
t("study.modeling.reserves.question.delete", {
count: rowsToDelete.length,
reserveNames: rowsToDelete.map((row) => row.name),
})
}
/>
<GroupedDataTable
key={`${studyId}-${areaId}`}
data={rows}
columns={columns}
readOnly={!showReserves}
isLoading={isLoading}
onCreate={handleCreate}
renderCreateDialog={({ open, onClose, onSubmit, existingNames }) => (
<CreateReserveDialog
open={open}
onClose={onClose}
onSubmit={onSubmit}
existingNames={existingNames}
/>
)}
onDuplicate={handleDuplicate}
onDelete={handleDelete}
deleteConfirmationMessage={(rowsToDelete) =>
t("study.modeling.reserves.question.delete", {
count: rowsToDelete.length,
reserveNames: rowsToDelete.map((row) => row.name),
})
}
/>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@

import TabsView from "@/components/page/TabsView";
import ViewWrapper from "@/components/page/ViewWrapper";
import usePromise from "@/hooks/usePromise";
import useAppSelector from "@/redux/hooks/useAppSelector";
import { getStudySynthesis } from "@/redux/selectors";
import { getOptimization } from "@/services/api/studies/config/optimization";
import useStudy from "@/routes/_authenticated/studies/$studyId/-hooks/useStudy";
import useArea from "@/routes/_authenticated/studies/$studyId/explore/modeling/areas/$areaId/-hooks/useArea";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
Expand All @@ -42,13 +40,6 @@ function AreaLayout() {
// editable in "/studies/$studyId/explore/configuration/advanced-params"
const enrModelling = useAppSelector((state) => getStudySynthesis(state, study.id)?.enr_modelling);

// The reserves tab is only shown when reserves are enabled in the
// optimization configuration ("includeReserves").
const { data: showReserves } = usePromise(
() => getOptimization({ studyId: study.id }).then((o) => o.includeReserves),
[study.id],
);

return (
<ViewWrapper>
<TabsView
Expand Down Expand Up @@ -118,15 +109,14 @@ function AreaLayout() {
params,
}),
},
semver.gte(study.version, "10.0.0") &&
showReserves && {
id: "reserves",
label: t("study.modeling.reserves"),
linkOptions: linkOptions({
to: "/studies/$studyId/explore/modeling/areas/$areaId/reserves",
params,
}),
},
semver.gte(study.version, "10.0.0") && {
id: "reserves",
label: t("study.modeling.reserves"),
linkOptions: linkOptions({
to: "/studies/$studyId/explore/modeling/areas/$areaId/reserves",
params,
}),
},
{
id: "miscGen",
label: t("study.modeling.miscGen"),
Expand Down
Loading