diff --git a/webapp/public/locales/en/main.json b/webapp/public/locales/en/main.json index 7c6a13307a..dfa4e75222 100644 --- a/webapp/public/locales/en/main.json +++ b/webapp/public/locales/en/main.json @@ -80,6 +80,7 @@ "global.semicolon": "Semicolon", "global.language": "Language", "global.path": "Path", + "global.weight": "Weight", "global.time.hourly": "Hourly", "global.time.daily": "Daily", "global.time.weekly": "Weekly", @@ -136,8 +137,8 @@ "maintenance.error.messageInfoError": "Unable to retrieve message info", "maintenance.error.maintenanceError": "Unable to retrieve maintenance status", "form.submit.error": "Error while submitting", - "form.submit.inProgress": "The form is being submitted. Are you sure you want to leave the page?", - "form.changeNotSaved": "The form has not been saved. Are you sure you want to leave the page?", + "form.submit.inProgress": "The form is being submitted. Are you sure you want to close it?", + "form.changeNotSaved": "The form has not been saved. Are you sure you want to close it?", "form.asyncDefaultValues.error": "Failed to get values", "form.field.required": "Field required", "form.field.duplicate": "Value already exists", @@ -361,8 +362,6 @@ "study.configuration.general.mcScenarioPlaylist.action.disableAll": "Disable all", "study.configuration.general.mcScenarioPlaylist.action.reverse": "Reverse", "study.configuration.general.mcScenarioPlaylist.action.resetWeights": "Reset weights", - "study.configuration.general.mcScenarioPlaylist.status": "Status", - "study.configuration.general.mcScenarioPlaylist.weight": "Weight", "study.configuration.general.geographicTrimming": "Geographic trimming", "study.configuration.general.thematicTrimming": "Thematic trimming", "study.configuration.general.thematicTrimming.action.enableAll": "Enable all", diff --git a/webapp/public/locales/fr/main.json b/webapp/public/locales/fr/main.json index 395ed91e7e..7df4675e89 100644 --- a/webapp/public/locales/fr/main.json +++ b/webapp/public/locales/fr/main.json @@ -80,6 +80,7 @@ "global.semicolon": "Point-virgule", "global.language": "Langue", "global.path": "Chemin", + "global.weight": "Poids", "global.time.hourly": "Horaire", "global.time.daily": "Journalier", "global.time.weekly": "Hebdomadaire", @@ -136,8 +137,8 @@ "maintenance.error.messageInfoError": "Impossible de récupérer le message d'info", "maintenance.error.maintenanceError": "Impossible de récupérer le status de maintenance de l'application", "form.submit.error": "Erreur lors de la soumission", - "form.submit.inProgress": "Le formulaire est en cours de soumission. Etes-vous sûr de vouloir quitter la page ?", - "form.changeNotSaved": "Le formulaire n'a pas été sauvegardé. Etes-vous sûr de vouloir quitter la page ?", + "form.submit.inProgress": "Le formulaire est en cours de soumission. Etes-vous sûr de vouloir le fermer ?", + "form.changeNotSaved": "Le formulaire n'a pas été sauvegardé. Etes-vous sûr de vouloir le fermer ?", "form.asyncDefaultValues.error": "Impossible d'obtenir les valeurs", "form.field.required": "Champ requis", "form.field.duplicate": "Cette valeur existe déjà", @@ -360,9 +361,7 @@ "study.configuration.general.mcScenarioPlaylist.action.enableAll": "Activer tous", "study.configuration.general.mcScenarioPlaylist.action.disableAll": "Désactiver tous", "study.configuration.general.mcScenarioPlaylist.action.reverse": "Inverser", - "study.configuration.general.mcScenarioPlaylist.action.resetWeights": "Reset weights", - "study.configuration.general.mcScenarioPlaylist.status": "Status", - "study.configuration.general.mcScenarioPlaylist.weight": "Weight", + "study.configuration.general.mcScenarioPlaylist.action.resetWeights": "Réinitialiser les poids", "study.configuration.general.geographicTrimming": "Geographic trimming", "study.configuration.general.thematicTrimming": "Thematic trimming", "study.configuration.general.thematicTrimming.action.enableAll": "Activer tout", diff --git a/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ScenarioPlaylistDialog.tsx b/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ScenarioPlaylistDialog.tsx new file mode 100644 index 0000000000..14b858b259 --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ScenarioPlaylistDialog.tsx @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +import { Button, ButtonGroup, Divider } from "@mui/material"; +import { useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import * as R from "ramda"; +import * as RA from "ramda-adjunct"; +import type { StudyMetadata } from "../../../../../../../common/types"; +import usePromise from "../../../../../../../hooks/usePromise"; +import BasicDialog from "../../../../../../common/dialogs/BasicDialog"; +import UsePromiseCond from "../../../../../../common/utils/UsePromiseCond"; +import type { SubmitHandlerPlus } from "../../../../../../common/Form/types"; +import DataGridForm, { + DataGridFormState, + type DataGridFormApi, +} from "@/components/common/DataGridForm"; +import ConfirmationDialog from "@/components/common/dialogs/ConfirmationDialog"; +import useConfirm from "@/hooks/useConfirm"; +import type { PlaylistData } from "@/services/api/studies/config/playlist/types"; +import { + getPlaylistData, + setPlaylistData, +} from "@/services/api/studies/config/playlist"; +import { DEFAULT_WEIGHT } from "@/services/api/studies/config/playlist/constants"; + +interface Props { + study: StudyMetadata; + open: boolean; + onClose: VoidFunction; +} + +function ScenarioPlaylistDialog(props: Props) { + const { study, open, onClose } = props; + const { t } = useTranslation(); + const dataGridApiRef = useRef>(null); + const [isSubmitting, setIsSubmitting] = useState(false); + const [isDirty, setIsDirty] = useState(false); + const closeAction = useConfirm(); + const res = usePromise( + () => getPlaylistData({ studyId: study.id }), + [study.id], + ); + + const columns = useMemo(() => { + return [ + { + id: "status" as const, + title: t("global.status"), + grow: 1, + }, + { + id: "weight" as const, + title: t("global.weight"), + grow: 1, + }, + ]; + }, []); + + //////////////////////////////////////////////////////////////// + // Event Handlers + //////////////////////////////////////////////////////////////// + + const handleUpdateStatus = (fn: RA.Pred) => () => { + if (dataGridApiRef.current) { + const { data, setData } = dataGridApiRef.current; + setData(R.map(R.evolve({ status: fn }), data)); + } + }; + + const handleResetWeights = () => { + if (dataGridApiRef.current) { + const { data, setData } = dataGridApiRef.current; + setData(R.map(R.assoc("weight", DEFAULT_WEIGHT), data)); + } + }; + + const handleSubmit = (data: SubmitHandlerPlus) => { + return setPlaylistData({ studyId: study.id, data: data.values }); + }; + + const handleClose = () => { + if (isSubmitting) { + return; + } + + if (isDirty) { + return closeAction.showConfirm().then((confirm) => { + if (confirm) { + onClose(); + } + }); + } + + onClose(); + }; + + const handleFormStateChange = (formState: DataGridFormState) => { + setIsSubmitting(formState.isSubmitting); + setIsDirty(formState.isDirty); + }; + + //////////////////////////////////////////////////////////////// + // JSX + //////////////////////////////////////////////////////////////// + + return ( + + {t("global.close")} + + } + PaperProps={{ sx: { height: 700 } }} + maxWidth="md" + fullWidth + > + ( + <> + + + + + + + + + `MC Year ${index + 1}`, + }} + onSubmit={handleSubmit} + onStateChange={handleFormStateChange} + apiRef={dataGridApiRef} + enableColumnResize={false} + /> + + {t("form.changeNotSaved")} + + + )} + /> + + ); +} + +export default ScenarioPlaylistDialog; diff --git a/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ScenarioPlaylistDialog/index.tsx b/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ScenarioPlaylistDialog/index.tsx deleted file mode 100644 index abb04d0a88..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ScenarioPlaylistDialog/index.tsx +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright (c) 2024, RTE (https://www.rte-france.com) - * - * See AUTHORS.txt - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - * - * This file is part of the Antares project. - */ - -import { Box, Button, Divider } from "@mui/material"; -import { useRef } from "react"; -import { useTranslation } from "react-i18next"; -import * as R from "ramda"; -import * as RA from "ramda-adjunct"; -import Handsontable from "handsontable"; -import { StudyMetadata } from "../../../../../../../../common/types"; -import usePromise from "../../../../../../../../hooks/usePromise"; -import BasicDialog from "../../../../../../../common/dialogs/BasicDialog"; -import TableForm from "../../../../../../../common/TableForm"; -import UsePromiseCond from "../../../../../../../common/utils/UsePromiseCond"; -import { - DEFAULT_WEIGHT, - getPlaylist, - PlaylistData, - setPlaylist, -} from "./utils"; -import { SubmitHandlerPlus } from "../../../../../../../common/Form/types"; -import { - HandsontableProps, - HotTableClass, -} from "../../../../../../../common/Handsontable"; - -interface Props { - study: StudyMetadata; - open: boolean; - onClose: VoidFunction; -} - -function ScenarioPlaylistDialog(props: Props) { - const { study, open, onClose } = props; - const { t } = useTranslation(); - const tableRef = useRef({} as HotTableClass); - const res = usePromise(() => getPlaylist(study.id), [study.id]); - - //////////////////////////////////////////////////////////////// - // Event Handlers - //////////////////////////////////////////////////////////////// - - const handleUpdateStatus = (fn: RA.Pred) => () => { - const api = tableRef.current.hotInstance; - if (!api) { - return; - } - - const changes: Array<[number, string, boolean]> = api - .getDataAtProp("status") - .map((status, index) => [index, "status", fn(status)]); - - api.setDataAtRowProp(changes); - }; - - const handleResetWeights = () => { - const api = tableRef.current.hotInstance as Handsontable; - - api.setDataAtRowProp( - api.rowIndexMapper - .getIndexesSequence() - .map((rowIndex) => [rowIndex, "weight", DEFAULT_WEIGHT]), - ); - }; - - const handleSubmit = (data: SubmitHandlerPlus) => { - return setPlaylist(study.id, data.values); - }; - - const handleCellsRender: HandsontableProps["cells"] = function cells( - this, - row, - column, - prop, - ) { - if (prop === "weight") { - const status = this.instance.getDataAtRowProp(row, "status"); - return { readOnly: !status }; - } - return {}; - }; - - //////////////////////////////////////////////////////////////// - // JSX - //////////////////////////////////////////////////////////////// - - return ( - {t("global.close")}} - // TODO: add `maxHeight` and `fullHeight` in BasicDialog` - PaperProps={{ sx: { height: 500 } }} - maxWidth="sm" - fullWidth - > - ( - <> - - - - - - - - - `MC Year ${row.id}`, - tableRef, - stretchH: "all", - className: "htCenter", - cells: handleCellsRender, - }} - /> - - )} - /> - - ); -} - -export default ScenarioPlaylistDialog; diff --git a/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ScenarioPlaylistDialog/utils.ts b/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ScenarioPlaylistDialog/utils.ts deleted file mode 100644 index bd7c015df8..0000000000 --- a/webapp/src/components/App/Singlestudy/explore/Configuration/General/dialogs/ScenarioPlaylistDialog/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2024, RTE (https://www.rte-france.com) - * - * See AUTHORS.txt - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - * - * This file is part of the Antares project. - */ - -import { StudyMetadata } from "../../../../../../../../common/types"; -import client from "../../../../../../../../services/api/client"; - -interface PlaylistColumns extends Record { - status: boolean; - weight: number; -} - -export type PlaylistData = Record; - -export const DEFAULT_WEIGHT = 1; - -function makeRequestURL(studyId: StudyMetadata["id"]): string { - return `v1/studies/${studyId}/config/playlist/form`; -} - -export async function getPlaylist( - studyId: StudyMetadata["id"], -): Promise { - const res = await client.get(makeRequestURL(studyId)); - return res.data; -} - -export function setPlaylist( - studyId: StudyMetadata["id"], - data: PlaylistData, -): Promise { - return client.put(makeRequestURL(studyId), data); -} diff --git a/webapp/src/components/App/Singlestudy/explore/Configuration/TimeSeriesManagement/index.tsx b/webapp/src/components/App/Singlestudy/explore/Configuration/TimeSeriesManagement/index.tsx index c888c774ab..3c04d03707 100644 --- a/webapp/src/components/App/Singlestudy/explore/Configuration/TimeSeriesManagement/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Configuration/TimeSeriesManagement/index.tsx @@ -31,7 +31,7 @@ function TimeSeriesManagement() { const { study } = useOutletContext<{ study: StudyMetadata }>(); const { t } = useTranslation(); const [launchTaskInProgress, setLaunchTaskInProgress] = useState(false); - const apiRef = useRef>(); + const apiRef = useRef>(null); const handleGenerateTs = usePromiseHandler({ fn: generateTimeSeries, diff --git a/webapp/src/components/common/DataGridForm.tsx b/webapp/src/components/common/DataGridForm.tsx index 1ae6c8064c..1a32fd29d1 100644 --- a/webapp/src/components/common/DataGridForm.tsx +++ b/webapp/src/components/common/DataGridForm.tsx @@ -12,7 +12,6 @@ * This file is part of the Antares project. */ -import type { IdType } from "@/common/types"; import { GridCellKind, type Item, @@ -21,17 +20,18 @@ import { type FillHandleDirection, } from "@glideapps/glide-data-grid"; import type { DeepPartial } from "react-hook-form"; -import { FormEvent, useCallback, useState } from "react"; -import DataGrid from "./DataGrid"; +import { FormEvent, useCallback, useEffect, useMemo, useState } from "react"; +import DataGrid, { DataGridProps } from "./DataGrid"; import { Box, Divider, IconButton, + setRef, SxProps, Theme, Tooltip, } from "@mui/material"; -import useUndo from "use-undo"; +import useUndo, { type Actions } from "use-undo"; import UndoIcon from "@mui/icons-material/Undo"; import RedoIcon from "@mui/icons-material/Redo"; import SaveIcon from "@mui/icons-material/Save"; @@ -42,53 +42,88 @@ import * as R from "ramda"; import { SubmitHandlerPlus } from "./Form/types"; import useEnqueueErrorSnackbar from "@/hooks/useEnqueueErrorSnackbar"; import useFormCloseProtection from "@/hooks/useCloseFormSecurity"; +import { useUpdateEffect } from "react-use"; +import { toError } from "@/utils/fnUtils"; -type GridFieldValuesByRow = Record< - IdType, - Record ->; +type Data = Record>; + +export interface DataGridFormState { + isDirty: boolean; + isSubmitting: boolean; +} + +export interface DataGridFormApi { + data: TData; + setData: Actions["set"]; + formState: DataGridFormState; +} export interface DataGridFormProps< - TFieldValues extends GridFieldValuesByRow = GridFieldValuesByRow, + TData extends Data = Data, SubmitReturnValue = unknown, > { - defaultData: TFieldValues; - columns: ReadonlyArray; + defaultData: TData; + columns: ReadonlyArray; + rowMarkers?: DataGridProps["rowMarkers"]; allowedFillDirections?: FillHandleDirection; - onSubmit?: ( - data: SubmitHandlerPlus, + enableColumnResize?: boolean; + onSubmit: ( + data: SubmitHandlerPlus, event?: React.BaseSyntheticEvent, ) => void | Promise; onSubmitSuccessful?: ( - data: SubmitHandlerPlus, + data: SubmitHandlerPlus, submitResult: SubmitReturnValue, ) => void; + onStateChange?: (state: DataGridFormState) => void; sx?: SxProps; extraActions?: React.ReactNode; + apiRef?: React.Ref>; } -function DataGridForm({ +function DataGridForm({ defaultData, columns, allowedFillDirections = "vertical", + enableColumnResize, + rowMarkers, onSubmit, onSubmitSuccessful, + onStateChange, sx, extraActions, -}: DataGridFormProps) { + apiRef, +}: DataGridFormProps) { const { t } = useTranslation(); const enqueueErrorSnackbar = useEnqueueErrorSnackbar(); const [isSubmitting, setIsSubmitting] = useState(false); const [savedData, setSavedData] = useState(defaultData); - const [{ present: data }, { set, undo, redo, canUndo, canRedo }] = + const [{ present: data }, { set: setData, undo, redo, canUndo, canRedo }] = useUndo(defaultData); - const isSubmitAllowed = savedData !== data; + // Shallow comparison to check if the data has changed. + // So even if the content are the same, we consider it as dirty. + // Deep comparison fix the issue but we big objects it can be slow. + const isDirty = savedData !== data; + + const rowNames = Object.keys(data); + + const formState = useMemo( + () => ({ + isDirty, + isSubmitting, + }), + [isDirty, isSubmitting], + ); + + useFormCloseProtection({ isSubmitting, isDirty }); - useFormCloseProtection({ - isSubmitting, - isDirty: isSubmitAllowed, - }); + useUpdateEffect(() => onStateChange?.(formState), [formState]); + + useEffect( + () => setRef(apiRef, { data, setData, formState }), + [apiRef, data, setData, formState], + ); //////////////////////////////////////////////////////////////// // Utils @@ -96,14 +131,13 @@ function DataGridForm({ const getRowAndColumnNames = (location: Item) => { const [colIndex, rowIndex] = location; - const rowNames = Object.keys(data); const columnIds = columns.map((column) => column.id); return [rowNames[rowIndex], columnIds[colIndex]]; }; const getDirtyValues = () => { - return Object.keys(data).reduce((acc, rowName) => { + return rowNames.reduce((acc, rowName) => { const rowData = data[rowName]; const savedRowData = savedData[rowName]; @@ -125,11 +159,11 @@ function DataGridForm({ } return acc; - }, {} as DeepPartial); + }, {} as DeepPartial); }; //////////////////////////////////////////////////////////////// - // Callbacks + // Content //////////////////////////////////////////////////////////////// const getCellContent = useCallback( @@ -182,7 +216,7 @@ function DataGridForm({ //////////////////////////////////////////////////////////////// const handleCellsEdited: DataEditorProps["onCellsEdited"] = (items) => { - set( + setData( items.reduce((acc, { location, value }) => { const [rowName, columnName] = getRowAndColumnNames(location); const newValue = value.data; @@ -204,13 +238,9 @@ function DataGridForm({ return true; }; - const handleFormSubmit = (event: FormEvent) => { + const handleSubmit = async (event: FormEvent) => { event.preventDefault(); - if (!onSubmit) { - return; - } - setIsSubmitting(true); const dataArg = { @@ -218,17 +248,15 @@ function DataGridForm({ dirtyValues: getDirtyValues(), }; - Promise.resolve(onSubmit(dataArg, event)) - .then((submitRes) => { - setSavedData(data); - onSubmitSuccessful?.(dataArg, submitRes); - }) - .catch((err) => { - enqueueErrorSnackbar(t("form.submit.error"), err); - }) - .finally(() => { - setIsSubmitting(false); - }); + try { + const submitRes = await onSubmit(dataArg, event); + setSavedData(data); + onSubmitSuccessful?.(dataArg, submitRes); + } catch (err) { + enqueueErrorSnackbar(t("form.submit.error"), toError(err)); + } finally { + setIsSubmitting(false); + } }; //////////////////////////////////////////////////////////////// @@ -247,19 +275,22 @@ function DataGridForm({ sx, )} component="form" - onSubmit={handleFormSubmit} + onSubmit={handleSubmit} > Object.keys(data)[index], - }} + rowMarkers={ + rowMarkers || { + kind: "clickable-string", + getTitle: (index) => rowNames[index], + } + } fillHandle allowedFillDirections={allowedFillDirections} + enableColumnResize={enableColumnResize} getCellsForSelection /> ({ ; - apiRef?: React.Ref | undefined>; + apiRef?: React.Ref>; } export function useFormContextPlus() { diff --git a/webapp/src/hooks/useConfirm.ts b/webapp/src/hooks/useConfirm.ts index 4df7890c7f..537674d220 100644 --- a/webapp/src/hooks/useConfirm.ts +++ b/webapp/src/hooks/useConfirm.ts @@ -22,7 +22,35 @@ function errorFunction() { /** * Hook that allows to wait for a confirmation from the user with a `Promise`. * It is intended to be used in conjunction with a confirm view (like `ConfirmationDialog`). - + * + * @example + * ```tsx + * const action = useConfirm(); + * + * return ( + * <> + * + * + * Are you sure? + * + * + * ); + * ``` + * * @returns An object with the following properties: * - `showConfirm`: A function that returns a `Promise` that resolves to `true` if the user confirms, * `false` if the user refuses, and `null` if the user cancel. diff --git a/webapp/src/services/api/studies/config/playlist/constants.ts b/webapp/src/services/api/studies/config/playlist/constants.ts new file mode 100644 index 0000000000..bec0c020cb --- /dev/null +++ b/webapp/src/services/api/studies/config/playlist/constants.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +export const DEFAULT_WEIGHT = 1; diff --git a/webapp/src/services/api/studies/config/playlist/index.ts b/webapp/src/services/api/studies/config/playlist/index.ts new file mode 100644 index 0000000000..40ee7b21c1 --- /dev/null +++ b/webapp/src/services/api/studies/config/playlist/index.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +import type { StudyMetadata } from "@/common/types"; +import client from "@/services/api/client"; +import { format } from "@/utils/stringUtils"; +import type { PlaylistData, SetPlaylistDataParams } from "./types"; + +const URL = "/v1/studies/{studyId}/config/playlist/form"; + +export async function getPlaylistData(params: { + studyId: StudyMetadata["id"]; +}) { + const url = format(URL, { studyId: params.studyId }); + const { data } = await client.get(url); + return data; +} + +export async function setPlaylistData({ + studyId, + data, +}: SetPlaylistDataParams) { + const url = format(URL, { studyId }); + await client.put(url, data); +} diff --git a/webapp/src/services/api/studies/config/playlist/types.ts b/webapp/src/services/api/studies/config/playlist/types.ts new file mode 100644 index 0000000000..9eeae5441e --- /dev/null +++ b/webapp/src/services/api/studies/config/playlist/types.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2024, RTE (https://www.rte-france.com) + * + * See AUTHORS.txt + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + * + * This file is part of the Antares project. + */ + +import type { StudyMetadata } from "@/common/types"; + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type Playlist = { + status: boolean; + weight: number; +}; + +export type PlaylistData = Record; + +export interface SetPlaylistDataParams { + studyId: StudyMetadata["id"]; + data: PlaylistData; +}