From ee04bf0870831d3d9ea10137d7b334695a9fda93 Mon Sep 17 00:00:00 2001 From: Clara Ni Date: Mon, 18 Sep 2023 17:52:20 +0200 Subject: [PATCH] front: rolling-stock-editor: fix several bugs - prevent the user from creating a rolling stock without name or effort curves - when creating a rolling stock, do not create any empty mode by default - prevent the user from deleting the standard comfort - update default curve when the first curve of each mode is edited - reset the traction mode when adding a new rolling stock --- front/public/locales/en/rollingstock.json | 3 + front/public/locales/fr/rollingstock.json | 3 + .../views/RollingStockEditor.tsx | 5 +- front/src/common/SelectorSNCF.tsx | 62 ++--- .../AddRollingstockParam.tsx | 6 +- .../RollingStockEditorCurves.tsx | 174 +++++++----- .../RollingStockEditorForm.tsx | 92 ++++--- front/src/modules/rollingStock/consts.ts | 4 +- .../src/modules/rollingStock/helpers/utils.ts | 76 +++--- .../src/reducers/rollingstockEditor/index.ts | 6 +- .../rollingStockEditor/_rollingStockForm.scss | 254 +++++++++--------- 11 files changed, 359 insertions(+), 326 deletions(-) diff --git a/front/public/locales/en/rollingstock.json b/front/public/locales/en/rollingstock.json index fc213585bad..31f5fc4db84 100644 --- a/front/public/locales/en/rollingstock.json +++ b/front/public/locales/en/rollingstock.json @@ -56,6 +56,9 @@ "messages": { "success": "Group", "failure": "Operation failed", + "invalidForm": "Invalid form", + "missingName": "Please fill the name of the new rolling stock", + "missingEffortCurves": "Please fill in at least one effort-speed curve", "rollingStockAdded": "Rolling stock added", "rollingStockUpdated": "Rolling stock updated", "rollingStockDeleted": "Rolling stock deleted", diff --git a/front/public/locales/fr/rollingstock.json b/front/public/locales/fr/rollingstock.json index 775e9373bfb..97c94ffb663 100644 --- a/front/public/locales/fr/rollingstock.json +++ b/front/public/locales/fr/rollingstock.json @@ -57,6 +57,9 @@ "messages": { "success": "Opération réussie", "failure": "Opération échouée", + "invalidForm": "Formulaire incomplet", + "missingName": "Veuillez renseigner un nom", + "missingEffortCurves": "Veuillez renseigner une courbe effort-vitesse", "rollingStockAdded": "Le matériel roulant a été ajouté.", "rollingStockUpdated": "Le matériel roulant a été mis à jour.", "rollingStockDeleted": "Le matériel roulant a été supprimé.", diff --git a/front/src/applications/rollingStockEditor/views/RollingStockEditor.tsx b/front/src/applications/rollingStockEditor/views/RollingStockEditor.tsx index 279d3e1141d..54a8a865580 100644 --- a/front/src/applications/rollingStockEditor/views/RollingStockEditor.tsx +++ b/front/src/applications/rollingStockEditor/views/RollingStockEditor.tsx @@ -39,7 +39,7 @@ export default function RollingStockEditor({ rollingStocks }: RollingStockEditor const [openedRollingStockCardId, setOpenedRollingStockCardId] = useState(); - const { data: selectedRollingStock } = osrdEditoastApi.useGetRollingStockByIdQuery( + const { data: selectedRollingStock } = osrdEditoastApi.endpoints.getRollingStockById.useQuery( { id: openedRollingStockCardId as number, }, @@ -50,7 +50,7 @@ export default function RollingStockEditor({ rollingStocks }: RollingStockEditor const resetRollingstockCurvesParams = () => { dispatch(updateComfortLvl(STANDARD_COMFORT_LEVEL)); - dispatch(updateTractionMode('')); + dispatch(updateTractionMode(null)); dispatch(updateElectricalProfile(null)); dispatch(updatePowerRestriction(null)); }; @@ -171,6 +171,7 @@ export default function RollingStockEditor({ rollingStocks }: RollingStockEditor type="button" className="btn btn-primary mb-4" onClick={() => { + resetRollingstockCurvesParams(); setIsAdding(true); setOpenedRollingStockCardId(undefined); }} diff --git a/front/src/common/SelectorSNCF.tsx b/front/src/common/SelectorSNCF.tsx index 46c896dc5c2..d5f0092a947 100644 --- a/front/src/common/SelectorSNCF.tsx +++ b/front/src/common/SelectorSNCF.tsx @@ -1,5 +1,5 @@ import { isNull } from 'lodash'; -import React, { useMemo } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import { MdArrowRight } from 'react-icons/md'; import { getTranslationKey } from 'utils/strings'; @@ -13,6 +13,7 @@ export default function SelectorSNCF( borderClass: string; title: K; itemsList: T[]; + permanentItems?: T[]; selectedItem?: T; hoveredItem?: string | null; onItemSelected?: (value: T) => void; @@ -26,6 +27,7 @@ export default function SelectorSNCF( borderClass, title, itemsList, + permanentItems, selectedItem, hoveredItem, onItemSelected, @@ -43,39 +45,39 @@ export default function SelectorSNCF(
- {useMemo( - () => - itemsList.map((item, index: number) => ( -
{ - if (onItemSelected) onItemSelected(item); - }} - onMouseOver={() => { - if (onItemHovered) onItemHovered(item); - }} - onFocus={() => { - if (onItemHovered) onItemHovered(item); - }} - key={`selector-${title}-${index}`} - > -
- {!isNull(item) - ? t(getTranslationKey(translationList, String(item))) - : t('unspecified')} -
- {onItemRemoved && ( + {itemsList.map( + (item, index: number) => ( +
{ + if (onItemSelected) onItemSelected(item); + }} + onMouseOver={() => { + if (onItemHovered) onItemHovered(item); + }} + onFocus={() => { + if (onItemHovered) onItemHovered(item); + }} + key={`selector-${title}-${index}`} + > +
+ {!isNull(item) + ? t(getTranslationKey(translationList, String(item))) + : t('unspecified')} +
+ {((permanentItems && !permanentItems.includes(item)) || !permanentItems) && + onItemRemoved && (
onItemRemoved(item, title)} />
)} -
- )), +
+ ), [itemsList, selectedItem, hoveredItem] )}
diff --git a/front/src/modules/rollingStock/components/rollingStockEditor/AddRollingstockParam.tsx b/front/src/modules/rollingStock/components/rollingStockEditor/AddRollingstockParam.tsx index 3f6c88a3e0a..85785c24185 100644 --- a/front/src/modules/rollingStock/components/rollingStockEditor/AddRollingstockParam.tsx +++ b/front/src/modules/rollingStock/components/rollingStockEditor/AddRollingstockParam.tsx @@ -11,11 +11,13 @@ export default function AddRollingstockParam({ listName, allOptionsList, displayedLists, + disabled, updateDisplayedLists, }: { listName: string; allOptionsList: string[] | (string | null)[]; displayedLists: RollingStockSelectorParams; + disabled?: boolean; updateDisplayedLists: (arg: string) => void; }) { const COMFORT_LEVELS_KEY: keyof RollingStockSelectorParams = 'comfortLevels'; @@ -48,14 +50,14 @@ export default function AddRollingstockParam({ {isSelectVisible && ( -
+
{ diff --git a/front/src/modules/rollingStock/components/rollingStockEditor/RollingStockEditorCurves.tsx b/front/src/modules/rollingStock/components/rollingStockEditor/RollingStockEditorCurves.tsx index 6b5108fdaa7..96186725d57 100644 --- a/front/src/modules/rollingStock/components/rollingStockEditor/RollingStockEditorCurves.tsx +++ b/front/src/modules/rollingStock/components/rollingStockEditor/RollingStockEditorCurves.tsx @@ -10,7 +10,7 @@ import { osrdEditoastApi, } from 'common/api/osrdEditoastApi'; import { isEmpty, isNull } from 'lodash'; -import React, { Dispatch, SetStateAction, useEffect, useState, useCallback } from 'react'; +import React, { Dispatch, SetStateAction, useEffect, useState, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import Spreadsheet, { CellBase, Matrix, createEmptyMatrix } from 'react-spreadsheet'; import { emptyStringRegex } from 'utils/strings'; @@ -25,7 +25,6 @@ import { getComfortLevel, getElectricalProfile, getPowerRestriction, - getTractionMode, } from 'reducers/rollingstockEditor/selectors'; import SelectorSNCF from 'common/SelectorSNCF'; import { @@ -46,55 +45,62 @@ import { createEmptyCurve, orderSelectorList } from 'modules/rollingStock/helper import AddRollingstockParam from './AddRollingstockParam'; import RollingStockEditorFormModal from './RollingStockEditorFormModal'; +const EMPTY_MATRIX = createEmptyMatrix>(8, 2); +const EMPTY_PARAMS = { + comfortLevels: [STANDARD_COMFORT_LEVEL], + tractionModes: [], + electricalProfiles: [], + powerRestrictions: [], +}; + export default function RollingStockEditorCurves({ data, currentRsEffortCurve, setCurrentRsEffortCurve, + selectedTractionMode, }: { data?: RollingStock; - currentRsEffortCurve: RollingStock['effort_curves']; - setCurrentRsEffortCurve: Dispatch>; + currentRsEffortCurve: RollingStock['effort_curves'] | null; + setCurrentRsEffortCurve: Dispatch>; + selectedTractionMode: string | null; }) { const { t } = useTranslation('rollingstock'); const dispatch = useDispatch(); const { openModal } = useModal(); const selectedComfortLvl = useSelector(getComfortLevel); - const selectedTractionMode = useSelector(getTractionMode); const selectedElectricalProfile = useSelector(getElectricalProfile); const selectedPowerRestriction = useSelector(getPowerRestriction); - const EMPTY_MATRIX = createEmptyMatrix>(8, 2); - const EMPTY_SELECTED_CURVES: SelectedCurves = { - [selectedTractionMode]: { - ...currentRsEffortCurve.modes[selectedTractionMode], - curves: [], - }, - }; - const EMPTY_PARAMS = { - comfortLevels: [], - tractionModes: [], - electricalProfiles: [], - powerRestrictions: [], - }; + const EmptySelectedCurves: SelectedCurves = useMemo( + () => + selectedTractionMode && currentRsEffortCurve + ? { + [selectedTractionMode]: { + ...currentRsEffortCurve.modes[selectedTractionMode], + curves: [], + }, + } + : {}, + [selectedTractionMode] + ); const [rollingstockParams, setRollingstockParams] = useState(EMPTY_PARAMS); - const EMPTY_TRACTION_MODE = { - curves: rollingstockParams.comfortLevels.map((comfort) => - createEmptyCurve(comfort, null, null) - ), + /** given a tractionMode and a list of comfort, return empty EffortCurves */ + const createEmptyCurves = (tractionMode: string, comforts: Comfort[]) => ({ + curves: comforts.map((comfort) => createEmptyCurve(comfort)), default_curve: { speeds: [0], max_efforts: [0] }, - is_electric: currentRsEffortCurve.default_mode !== THERMAL_TRACTION_IDENTIFIER, - }; + is_electric: tractionMode !== THERMAL_TRACTION_IDENTIFIER, + }); const [hoveredRollingstockParam, setHoveredRollingstockParam] = useState(); - const [selectedCurves, setSelectedCurves] = useState(EMPTY_SELECTED_CURVES); + const [selectedCurves, setSelectedCurves] = useState(EmptySelectedCurves); const dispatchComfortLvl = (value: string) => { dispatch(updateComfortLvl(value as Comfort)); }; - const dispatchTractionMode = (value: string) => { + const dispatchTractionMode = (value: string | null) => { dispatch(updateTractionMode(value)); }; const dispatchElectricalProfil = (value: string | null) => { @@ -109,10 +115,13 @@ export default function RollingStockEditorCurves({ * the selected rollingstock to fill the selectors lists */ const updateRollingstockParams = useCallback(() => { + if (!currentRsEffortCurve) return EMPTY_PARAMS; + const rsComfortLevels = listCurvesComfort(currentRsEffortCurve); const tractionModes = Object.keys(currentRsEffortCurve.modes); const powerRestrictions: (string | null)[] = []; const rsElectricalProfiles = + selectedTractionMode && selectedTractionMode !== THERMAL_TRACTION_IDENTIFIER && currentRsEffortCurve.modes[selectedTractionMode] ? currentRsEffortCurve.modes[selectedTractionMode].curves.reduce((acc, curve) => { @@ -139,8 +148,8 @@ export default function RollingStockEditorCurves({ if (!rsComfortLevels.includes(selectedComfortLvl)) { dispatchComfortLvl(rsComfortLevels[0] || STANDARD_COMFORT_LEVEL); } - if (!tractionModes.includes(selectedTractionMode)) { - dispatchTractionMode(Object.keys(currentRsEffortCurve.modes)[0]); + if (selectedTractionMode && !tractionModes.includes(selectedTractionMode)) { + dispatchTractionMode(Object.keys(currentRsEffortCurve.modes)[0] || null); } if (!rsElectricalProfiles.includes(selectedElectricalProfile)) { dispatchElectricalProfil(rsElectricalProfiles[0]); @@ -161,9 +170,10 @@ export default function RollingStockEditorCurves({ /** Filter all the curves to find a list of curves matching the selected comfort and traction mode */ const selectMatchingCurves = useCallback((): SelectedCurves => { + if (!selectedTractionMode || !currentRsEffortCurve) return {}; const selectedTractionModeCurves = currentRsEffortCurve.modes[selectedTractionMode]; if (!selectedTractionModeCurves) { - return EMPTY_SELECTED_CURVES; + return EmptySelectedCurves; } const curvesList = selectedTractionModeCurves?.curves.filter( (curve) => curve.cond?.comfort === selectedComfortLvl @@ -188,7 +198,9 @@ export default function RollingStockEditorCurves({ const selectAndFormatCurveForSpreadsheet = ( newSelectedCurves: RollingStock['effort_curves']['modes'] ): Matrix> => { - if (isEmpty(newSelectedCurves[selectedTractionMode].curves)) return EMPTY_MATRIX; + if (!selectedTractionMode || isEmpty(newSelectedCurves[selectedTractionMode].curves)) { + return EMPTY_MATRIX; + } const isElectric = selectedTractionMode !== THERMAL_TRACTION_IDENTIFIER; const curvesList = newSelectedCurves[selectedTractionMode].curves; @@ -266,6 +278,7 @@ export default function RollingStockEditorCurves({ }; const updateCurrentRs = (e: Matrix<{ value: string }>) => { + if (!selectedTractionMode || !currentRsEffortCurve) return; const parsedCurve = parseCurve(e); const selectedTractionModeCurves = currentRsEffortCurve.modes[selectedTractionMode].curves; if (!isEmpty(parsedCurve.max_efforts) && !isEmpty(parsedCurve.speeds)) { @@ -280,14 +293,14 @@ export default function RollingStockEditorCurves({ const updatedSelectedCurve = index < 0 - ? ({ + ? { cond: { comfort: selectedComfortLvl, electrical_profile_level: selectedElectricalProfile, power_restriction_code: selectedPowerRestriction, }, curve: parsedCurve, - } as ConditionalEffortCurve) + } : { cond: selectedTractionModeCurves[index].cond, curve: parsedCurve, @@ -299,6 +312,10 @@ export default function RollingStockEditorCurves({ ]; if (index > 0) updatedCurvesList.unshift(...selectedTractionModeCurves.slice(0, index)); + const defaultCurve = + index === 0 + ? updatedSelectedCurve.curve + : currentRsEffortCurve.modes[selectedTractionMode].default_curve; const updatedCurrentRsEffortCurve = { default_mode: currentRsEffortCurve.default_mode, @@ -306,6 +323,7 @@ export default function RollingStockEditorCurves({ ...currentRsEffortCurve.modes, [selectedTractionMode]: { ...currentRsEffortCurve.modes[selectedTractionMode], + default_curve: defaultCurve, curves: updatedCurvesList, }, }, @@ -343,9 +361,10 @@ export default function RollingStockEditorCurves({ }; const updateComfortLevelsList = (value: string) => { + if (!currentRsEffortCurve) return; const updatedModesCurves = Object.keys(currentRsEffortCurve.modes).reduce((acc, key) => { const currentMode = currentRsEffortCurve.modes[key]; - const newEmptyCurve = createEmptyCurve(value as Comfort, null, null); + const newEmptyCurve = createEmptyCurve(value as Comfort); return { ...acc, [key]: { @@ -356,24 +375,27 @@ export default function RollingStockEditorCurves({ }, {}); setCurrentRsEffortCurve((prevState) => ({ - ...prevState, + default_mode: prevState !== null ? prevState.default_mode : currentRsEffortCurve.default_mode, modes: updatedModesCurves, })); + dispatchComfortLvl(value as Comfort); }; - const updateTractionModesList = (value: string) => { + const updateTractionModesList = (newTractionMode: string) => { setCurrentRsEffortCurve((prevState) => ({ - ...prevState, + default_mode: prevState ? prevState.default_mode : newTractionMode, modes: { - ...prevState.modes, - [value]: EMPTY_TRACTION_MODE, + ...(prevState ? prevState.modes : {}), + [newTractionMode]: createEmptyCurves(newTractionMode, rollingstockParams.comfortLevels), }, })); + dispatchTractionMode(newTractionMode); }; const updateElectricalProfilesList = (value: string) => { + if (!selectedTractionMode || !currentRsEffortCurve) return; const selectedModeCurves = currentRsEffortCurve.modes[selectedTractionMode]; - const newEmptyCurve = createEmptyCurve(selectedComfortLvl, value, null); + const newEmptyCurve = createEmptyCurve(selectedComfortLvl, value); const updatedCurrentRsEffortCurve = { ...currentRsEffortCurve, @@ -387,13 +409,14 @@ export default function RollingStockEditorCurves({ }; setCurrentRsEffortCurve(updatedCurrentRsEffortCurve); + dispatchElectricalProfil(value); }; const removeTractionMode = (value: string) => { + if (!currentRsEffortCurve) return; const filteredModesList = Object.fromEntries( Object.entries(currentRsEffortCurve.modes).filter(([key]) => key !== value) ); - const updatedCurrentRsEffortCurve = { default_mode: currentRsEffortCurve.default_mode, modes: filteredModesList, @@ -405,6 +428,7 @@ export default function RollingStockEditorCurves({ value: RollingStockSelectorParam[K], title: K ) => { + if (!currentRsEffortCurve) return; const updatedModesCurves = Object.keys(currentRsEffortCurve.modes).reduce((acc, key) => { const cleanedList = currentRsEffortCurve.modes[key].curves.filter((curve) => { const rsEffortCurveCondKey = effortCurveCondKeys[title as keyof EffortCurveCondKeys]; @@ -479,6 +503,7 @@ export default function RollingStockEditorCurves({ itemsList={rollingstockParams.comfortLevels} selectedItem={selectedComfortLvl} hoveredItem={hoveredRollingstockParam} + permanentItems={[STANDARD_COMFORT_LEVEL]} onItemSelected={dispatchComfortLvl} onItemHovered={setHoveredRollingstockParam} onItemRemoved={confirmRsParamRemoval} @@ -486,6 +511,7 @@ export default function RollingStockEditorCurves({ translationList="comfortTypes" /> )}
- {selectedTractionMode !== THERMAL_TRACTION_IDENTIFIER && ( + {selectedTractionMode && selectedTractionMode !== THERMAL_TRACTION_IDENTIFIER && ( <>
)} -
-
- { - updateSpreadsheet(e); - updateCurrentRs(e); - }} - onBlur={orderSpreadsheetValues} - onKeyDown={(e) => { - if (e.key === 'Enter') orderSpreadsheetValues(); - }} - columnLabels={[t('speed'), t('effort')]} - /> -
-
- {!isEmpty(selectedCurves[selectedTractionMode]?.curves) && rollingstockParams && ( - 1} - hoveredElectricalParam={hoveredRollingstockParam} + {selectedTractionMode && ( +
+
+ { + updateSpreadsheet(e); + updateCurrentRs(e); + }} + onBlur={orderSpreadsheetValues} + onKeyDown={(e) => { + if (e.key === 'Enter') orderSpreadsheetValues(); + }} + columnLabels={[t('speed'), t('effort')]} /> - )} - {data && ( -
-
- +
+
+ {!isEmpty(selectedCurves[selectedTractionMode]?.curves) && rollingstockParams && ( + 1} + hoveredElectricalParam={hoveredRollingstockParam} + /> + )} + {data && ( +
+
+ +
-
- )} + )} +
-
+ )} ); } diff --git a/front/src/modules/rollingStock/components/rollingStockEditor/RollingStockEditorForm.tsx b/front/src/modules/rollingStock/components/rollingStockEditor/RollingStockEditorForm.tsx index 912a38359bf..a5a71b62efb 100644 --- a/front/src/modules/rollingStock/components/rollingStockEditor/RollingStockEditorForm.tsx +++ b/front/src/modules/rollingStock/components/rollingStockEditor/RollingStockEditorForm.tsx @@ -1,6 +1,10 @@ import React, { useEffect, useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { RollingStock, osrdEditoastApi } from 'common/api/osrdEditoastApi'; +import { + RollingStock, + RollingStockUpsertPayload, + osrdEditoastApi, +} from 'common/api/osrdEditoastApi'; import { useModal } from 'common/BootstrapSNCF/ModalSNCF'; import { useDispatch, useSelector } from 'react-redux'; import { setFailure, setSuccess } from 'reducers/main'; @@ -16,6 +20,7 @@ import { RollingStockSchemaProperties, } from 'modules/rollingStock/consts'; import { getTractionMode } from 'reducers/rollingstockEditor/selectors'; +import { updateTractionMode } from 'reducers/rollingstockEditor'; import { RollingStockEditorMetadataForm, RollingStockEditorParameterForm, @@ -42,22 +47,22 @@ const RollingStockEditorForm = ({ 'operationalStudies/manageTrainSchedule', ]); const { openModal } = useModal(); - const [postRollingstock] = osrdEditoastApi.usePostRollingStockMutation(); - const [patchRollingStock] = osrdEditoastApi.usePatchRollingStockByIdMutation(); + const [postRollingstock] = osrdEditoastApi.endpoints.postRollingStock.useMutation(); + const [patchRollingStock] = osrdEditoastApi.endpoints.patchRollingStockById.useMutation(); - const [isCurrentEffortCurveDefault, setIsCurrentEffortCurveDefault] = useState(true); const [isValid, setIsValid] = useState(true); const [optionValue, setOptionValue] = useState(''); const selectedTractionMode = useSelector(getTractionMode); const defaultRollingStockMode = useMemo( - () => getDefaultRollingStockMode(selectedTractionMode), + () => (selectedTractionMode ? getDefaultRollingStockMode(selectedTractionMode) : null), [selectedTractionMode] ); - const [currentRsEffortCurve, setCurrentRsEffortCurve] = - useState(defaultRollingStockMode); + const [currentRsEffortCurve, setCurrentRsEffortCurve] = useState< + RollingStock['effort_curves'] | null + >(defaultRollingStockMode); const defaultValues: RollingStockParametersValues = useMemo( () => getRollingStockEditorDefaultValues(selectedTractionMode, rollingStockData), @@ -66,16 +71,10 @@ const RollingStockEditorForm = ({ const [rollingStockValues, setRollingStockValues] = useState(defaultValues); - const addNewRollingstock = (data: RollingStockParametersValues) => () => { - const queryArg = rollingStockEditorQueryArg( - data, - selectedTractionMode, - currentRsEffortCurve, - isAdding - ); + const addNewRollingstock = (payload: RollingStockUpsertPayload) => () => { postRollingstock({ locked: false, - rollingStockUpsertPayload: queryArg, + rollingStockUpsertPayload: payload, }) .unwrap() .then((res) => { @@ -107,12 +106,11 @@ const RollingStockEditorForm = ({ }); }; - const updateRollingStock = (data: RollingStockParametersValues) => () => { - const queryArg = rollingStockEditorQueryArg(data, selectedTractionMode, currentRsEffortCurve); + const updateRollingStock = (payload: RollingStockUpsertPayload) => () => { if (rollingStockData) { patchRollingStock({ - id: rollingStockData?.id as number, - rollingStockUpsertPayload: queryArg, + id: rollingStockData.id, + rollingStockUpsertPayload: payload, }) .unwrap() .then(() => { @@ -146,14 +144,31 @@ const RollingStockEditorForm = ({ const submit = (e: React.FormEvent, data: RollingStockParametersValues) => { e.preventDefault(); - openModal( - - ); + if (!data.name) { + dispatch( + setFailure({ + name: t('messages.invalidForm'), + message: t('messages.missingName'), + }) + ); + } else if (!selectedTractionMode || !currentRsEffortCurve) { + dispatch( + setFailure({ + name: t('messages.invalidForm'), + message: t('messages.missingEffortCurves'), + }) + ); + } else { + const payload = rollingStockEditorQueryArg(data, currentRsEffortCurve); + openModal( + + ); + } }; const cancel = () => { @@ -183,8 +198,8 @@ const RollingStockEditorForm = ({ useEffect(() => { if (rollingStockData) { + dispatch(updateTractionMode(rollingStockData.effort_curves.default_mode)); setCurrentRsEffortCurve(rollingStockData.effort_curves); - setIsCurrentEffortCurveDefault(false); } }, [rollingStockData]); @@ -214,21 +229,12 @@ const RollingStockEditorForm = ({ withWarning: false, label: t('tabs.rollingStockCurves'), content: ( - <> - {rollingStockData && !isCurrentEffortCurveDefault && ( - - )} - {!rollingStockData && ( - - )} - + ), }; diff --git a/front/src/modules/rollingStock/consts.ts b/front/src/modules/rollingStock/consts.ts index 316abbddaea..ceafc7f0d5c 100644 --- a/front/src/modules/rollingStock/consts.ts +++ b/front/src/modules/rollingStock/consts.ts @@ -5,7 +5,7 @@ export const STANDARD_COMFORT_LEVEL: Comfort = 'STANDARD'; export const DEFAULT_SELECTORS_CLASSNAME = 'selector-SNCF'; -type EffortCurves = { +export type EffortCurves = { modes: { [key: string]: { curves: ConditionalEffortCurve[]; @@ -81,7 +81,7 @@ export type RollingStockParametersValues = { rollingResistanceC: number; electricalPowerStartupTime: number | null; raisePantographTime: number | null; - defaultMode: string; + defaultMode: string | null; effortCurves: EffortCurves; }; diff --git a/front/src/modules/rollingStock/helpers/utils.ts b/front/src/modules/rollingStock/helpers/utils.ts index 6af1c3db391..6e2651f781d 100644 --- a/front/src/modules/rollingStock/helpers/utils.ts +++ b/front/src/modules/rollingStock/helpers/utils.ts @@ -1,11 +1,10 @@ -import { - Comfort, - EffortCurve, - RollingStock, - RollingStockUpsertPayload, -} from 'common/api/osrdEditoastApi'; +import { Comfort, RollingStock, RollingStockUpsertPayload } from 'common/api/osrdEditoastApi'; import { isNull } from 'lodash'; -import { RollingStockParametersValues, STANDARD_COMFORT_LEVEL } from 'modules/rollingStock/consts'; +import { + EffortCurves, + RollingStockParametersValues, + STANDARD_COMFORT_LEVEL, +} from 'modules/rollingStock/consts'; const newRollingStockValues = { railjsonVersion: '', @@ -64,15 +63,24 @@ export const getDefaultRollingStockMode = ( }); const getRollingStockEditorDefaultValues = ( - selectedMode: string, + selectedMode: string | null, rollingStockData?: RollingStock ): RollingStockParametersValues => { - const completeSelectedMode = getDefaultRollingStockMode(selectedMode).modes[selectedMode]; - const formattedSelectedMode = { - curves: completeSelectedMode.curves, - isElectric: completeSelectedMode.is_electric, - defaultCurve: completeSelectedMode.default_curve, - }; + let effortCurves: EffortCurves = { modes: {} }; + const completeSelectedMode = selectedMode + ? getDefaultRollingStockMode(selectedMode).modes[selectedMode] + : null; + if (completeSelectedMode) { + effortCurves = { + modes: { + [selectedMode as string]: { + curves: completeSelectedMode.curves, + isElectric: completeSelectedMode.is_electric, + defaultCurve: completeSelectedMode.default_curve, + }, + }, + }; + } return rollingStockData ? { railjsonVersion: rollingStockData.railjson_version, @@ -100,29 +108,19 @@ const getRollingStockEditorDefaultValues = ( rollingResistanceC: rollingStockData.rolling_resistance.C, electricalPowerStartupTime: rollingStockData.electrical_power_startup_time, raisePantographTime: rollingStockData.raise_pantograph_time, - defaultMode: rollingStockData.effort_curves.default_mode || selectedMode, - effortCurves: { - modes: { - [selectedMode]: formattedSelectedMode, - }, - }, + defaultMode: rollingStockData.effort_curves.default_mode, + effortCurves, } : { ...newRollingStockValues, defaultMode: selectedMode, - effortCurves: { - modes: { - [selectedMode]: formattedSelectedMode, - }, - }, + effortCurves, }; }; export const rollingStockEditorQueryArg = ( data: RollingStockParametersValues, - selectedMode: string, - currentRsEffortCurve: RollingStock['effort_curves'], - isAdding?: boolean + currentRsEffortCurve: RollingStock['effort_curves'] ): RollingStockUpsertPayload => ({ name: data.name, length: data.length, @@ -160,30 +158,18 @@ export const rollingStockEditorQueryArg = ( type: data.type, unit: data.unit, }, - effort_curves: { - default_mode: selectedMode, - modes: { - ...currentRsEffortCurve.modes, - [selectedMode]: { - curves: currentRsEffortCurve.modes[selectedMode].curves, - is_electric: currentRsEffortCurve.modes[selectedMode].is_electric, - default_curve: isAdding - ? (currentRsEffortCurve.modes[selectedMode].curves[0].curve as EffortCurve) - : currentRsEffortCurve.modes[selectedMode].default_curve, - }, - }, - }, + effort_curves: currentRsEffortCurve, }); export const createEmptyCurve = ( comfort: Comfort, - electricalProfile: string | null, - powerRestriction: string | null + electricalProfile: string | null = null, + powerRestriction: string | null = null ) => ({ cond: { comfort: comfort || STANDARD_COMFORT_LEVEL, - electrical_profile_level: electricalProfile || null, - power_restriction_code: powerRestriction || null, + electrical_profile_level: electricalProfile, + power_restriction_code: powerRestriction, }, curve: { speeds: [0], max_efforts: [0] }, }); diff --git a/front/src/reducers/rollingstockEditor/index.ts b/front/src/reducers/rollingstockEditor/index.ts index d54b72284d4..c105c23984d 100644 --- a/front/src/reducers/rollingstockEditor/index.ts +++ b/front/src/reducers/rollingstockEditor/index.ts @@ -13,14 +13,14 @@ export interface RsEditorCurvesState { comfortLvl: Comfort; electricalProfile: string | null; powerRestriction: string | null; - tractionMode: string; + tractionMode: string | null; } export const initialState: RsEditorCurvesState = { comfortLvl: STANDARD_COMFORT_LEVEL, electricalProfile: null, powerRestriction: null, - tractionMode: '', + tractionMode: null, }; export default function reducer(inputState: RsEditorCurvesState | undefined, action: AnyAction) { @@ -54,7 +54,7 @@ export function updateComfortLvl(comfortLvl: Comfort) { }; } -export function updateTractionMode(tractionMode: string) { +export function updateTractionMode(tractionMode: string | null) { return (dispatch: Dispatch) => { dispatch({ type: TRACTION_MODE, diff --git a/front/src/styles/scss/applications/rollingStockEditor/_rollingStockForm.scss b/front/src/styles/scss/applications/rollingStockEditor/_rollingStockForm.scss index 81d52ac7e1b..051788618c1 100644 --- a/front/src/styles/scss/applications/rollingStockEditor/_rollingStockForm.scss +++ b/front/src/styles/scss/applications/rollingStockEditor/_rollingStockForm.scss @@ -19,8 +19,10 @@ } } &-form { - overflow-y: scroll; + height: 100%; + justify-content: space-between; max-height: 88vh; + overflow-y: scroll; > .rollingstock-header { @media screen and (min-width: 1024px) { height: 3rem; @@ -91,6 +93,7 @@ } } &-container { + height: 100%; .tab-content { background-color: var(--white); border-radius: 0.5rem; @@ -208,156 +211,155 @@ width: 38%; } } -} - -.defaultImageRSEditor img { - max-height: 2rem; -} -.rollingstock-editor-spreadsheet { - width: 40%; - height: 20.75rem; - overflow: auto; - tr:not(:first-child) .Spreadsheet__header, - tr:first-child .Spreadsheet__header:first-child { - width: 2rem; - border: solid 1px var(--coolgray3); - } - tr:not(:first-child) .Spreadsheet__header { - background-color: var(--white); + .defaultImageRSEditor img { + max-height: 2rem; } - tr:first-child .Spreadsheet__header { - color: var(--blue); - white-space: normal !important; - } -} -.rollingstock-editor-curves { - background-color: var(--white); - .rollingstock-body { - width: 60%; - border: none; + .rollingstock-editor-spreadsheet { + width: 40%; height: 20.75rem; - .curves-container { - height: 20.75rem; + overflow: auto; + tr:not(:first-child) .Spreadsheet__header, + tr:first-child .Spreadsheet__header:first-child { + width: 2rem; + border: solid 1px var(--coolgray3); + } + tr:not(:first-child) .Spreadsheet__header { + background-color: var(--white); + } + tr:first-child .Spreadsheet__header { + color: var(--blue); + white-space: normal !important; } } -} -@media screen and (max-width: 1040px) { .rollingstock-editor-curves { - flex-direction: column-reverse; + background-color: var(--white); .rollingstock-body { - margin-bottom: 1rem; - width: 100%; + width: 60%; + border: none; + height: 20.75rem; + .curves-container { + height: 20.75rem; + } } } - .rollingstock-editor-spreadsheet { - padding-right: 1rem; - height: 100%; - width: 100%; - } -} + @media screen and (max-width: 1040px) { + .rollingstock-editor-curves { + flex-direction: column-reverse; + .rollingstock-body { + margin-bottom: 1rem; + width: 100%; + } + } -// .rollingstock-editor-selectors { -.selector-SNCF { - width: 13rem; - &-title { - width: 90%; - } - &-itemslist { - z-index: 1; - cursor: pointer; - background-color: var(--coolgray1); - border-radius: 0.3rem 0.3rem 0 0; - height: 10.5rem; - width: 90%; - } - &-trash-icon { - visibility: hidden; + .rollingstock-editor-spreadsheet { + padding-right: 1rem; + height: 100%; + width: 100%; + } } - &-item { - color: var(--coolgray11); - background-color: var(--white); - border-radius: 0.3rem; - justify-content: space-between; - &.selected { - &.selector-blue { - outline: 0.13rem solid var(--blue); - } - &.selector-pink { - outline: 0.13rem solid #fd2f7e; - } + + .selector-SNCF { + width: 13rem; + &-title { + width: 90%; + } + &-itemslist { + z-index: 1; + cursor: pointer; + background-color: var(--coolgray1); + border-radius: 0.3rem 0.3rem 0 0; + height: 10.5rem; + width: 90%; } - &.hovered { - outline: 0.13rem solid #fd2f7e80; - &.selector-blue { - outline: 0.13rem solid #30338380; + &-trash-icon { + visibility: hidden; + } + &-item { + color: var(--coolgray11); + background-color: var(--white); + border-radius: 0.3rem; + justify-content: space-between; + &.selected { + &.selector-blue { + outline: 0.13rem solid var(--blue); + } + &.selector-pink { + outline: 0.13rem solid #fd2f7e; + } } - .selector-SNCF-trash-icon { - color: var(--red); - visibility: visible; - width: 2.5rem; - border-radius: 0 0.3rem 0.3rem 0; - text-align: center; - padding-top: 0.1rem; - &:hover { - color: var(--white); - background-color: var(--red); + &.hovered { + outline: 0.13rem solid #fd2f7e80; + &.selector-blue { + outline: 0.13rem solid #30338380; + } + .selector-SNCF-trash-icon { + color: var(--red); + visibility: visible; + width: 2.5rem; + border-radius: 0 0.3rem 0.3rem 0; + text-align: center; + padding-top: 0.1rem; + &:hover { + color: var(--white); + background-color: var(--red); + } } } } - } - &-arrow { - position: absolute; - display: flex; - align-items: center; - right: -2.7rem; - height: 100%; - font-size: 7rem; - color: var(--coolgray1); - } -} + &-arrow { + position: absolute; + display: flex; + align-items: center; + right: -2.7rem; + height: 100%; + font-size: 7rem; + color: var(--coolgray1); + } -.selector-container { - margin-right: 1rem; - &:last-child { - .selector-SNCF-arrow { - visibility: hidden; + .selector-select { + position: absolute; + z-index: 2; + width: 22rem; } } - &:first-child { - margin-right: 2rem; - .selector-SNCF-arrow { - visibility: hidden; + + .selector-container { + margin-right: 1rem; + &:last-child { + .selector-SNCF-arrow { + visibility: hidden; + } } - } - &:nth-child(4) { - .selector-SNCF-itemslist { - height: 12.75rem; - border-radius: 0.3rem; + &:first-child { + margin-right: 2rem; + .selector-SNCF-arrow { + visibility: hidden; + } + } + &:nth-child(4) { + .selector-SNCF-itemslist { + height: 12.75rem; + border-radius: 0.3rem; + } } } -} -.rollingstock-selector-buttons { - border: none; - cursor: pointer; - background-color: #82be00; - border-radius: 0 0 0.3rem 0.3rem; - width: 90%; - text-align: center; - font-size: x-large; - &.disabled { - background-color: var(--coolgray1); - color: var(--coolgray3); - pointer-events: none; + .rollingstock-selector-buttons { + border: none; + cursor: pointer; + background-color: #82be00; + border-radius: 0 0 0.3rem 0.3rem; + width: 90%; + text-align: center; + font-size: x-large; + &.disabled { + background-color: var(--coolgray1); + color: var(--coolgray3); + pointer-events: none; + } } } - -.rollingstock-editor-select { - position: absolute; - z-index: 2; - width: 22rem; -}