From d7e5438ef9987c5354942d83c47ed9da6a76cc16 Mon Sep 17 00:00:00 2001 From: Math_R_ Date: Tue, 10 Dec 2024 15:30:37 +0100 Subject: [PATCH] front: update train after drag in spacetimechart Signed-off-by: Math_R_ front: implement new function to update departureTime after train drag Signed-off-by: Clara Ni front: fix order to prevent hook to trigger an error Signed-off-by: Math_R_ front: use a new props to pass the selectedTrain update dispatch - the component keeps the logic to know which element is clicked and what action should be performed Signed-off-by: Math_R_ front: add a new util function to determine the style of an hovered path Signed-off-by: Math_R_ --- .../components/Scenario/ScenarioContent.tsx | 19 +- .../helpers/formatTrainScheduleSummaries.ts | 1 + .../hooks/useScenarioData.ts | 203 ++++++++++++------ .../views/SimulationResults.tsx | 47 +++- .../ManchetteWithSpaceTimeChart.tsx | 102 ++++++++- .../ManchetteWithSpaceTimeChart/utils.ts | 18 ++ front/src/modules/simulationResult/consts.ts | 4 + front/src/modules/timesStops/ReadOnlyTime.tsx | 3 +- .../components/Timetable/Timetable.tsx | 8 +- 9 files changed, 316 insertions(+), 89 deletions(-) create mode 100644 front/src/modules/simulationResult/components/ManchetteWithSpaceTimeChart/utils.ts diff --git a/front/src/applications/operationalStudies/components/Scenario/ScenarioContent.tsx b/front/src/applications/operationalStudies/components/Scenario/ScenarioContent.tsx index 48a81b07da4..801106b57ec 100644 --- a/front/src/applications/operationalStudies/components/Scenario/ScenarioContent.tsx +++ b/front/src/applications/operationalStudies/components/Scenario/ScenarioContent.tsx @@ -18,12 +18,12 @@ import useScenarioData from 'applications/operationalStudies/hooks/useScenarioDa import ImportTrainSchedule from 'applications/operationalStudies/views/ImportTrainSchedule'; import ManageTrainSchedule from 'applications/operationalStudies/views/ManageTrainSchedule'; import SimulationResults from 'applications/operationalStudies/views/SimulationResults'; -import { - osrdEditoastApi, - type InfraWithState, - type ScenarioResponse, - type TimetableDetailedResult, - type TrainScheduleResult, +import { osrdEditoastApi } from 'common/api/osrdEditoastApi'; +import type { + InfraWithState, + ScenarioResponse, + TimetableDetailedResult, + TrainScheduleResult, } from 'common/api/osrdEditoastApi'; import ScenarioLoaderMessage from 'modules/scenario/components/ScenarioLoaderMessage'; import TimetableManageTrainSchedule from 'modules/trainschedule/components/ManageTrainSchedule/TimetableManageTrainSchedule'; @@ -56,14 +56,13 @@ const ScenarioContent = ({ const [trainIdToEdit, setTrainIdToEdit] = useState(); const [isMacro, setIsMacro] = useState(false); const { - selectedTrainId, - selectedTrainSummary, trainScheduleSummaries, trainSchedules, projectionData, conflicts, upsertTrainSchedules, removeTrains, + updateTrainDepartureTime, } = useScenarioData(scenario, timetable, infra); const macroEditorState = useRef(); const [ngeDto, setNgeDto] = useState(); @@ -161,7 +160,6 @@ const ScenarioContent = ({ ) )} diff --git a/front/src/applications/operationalStudies/helpers/formatTrainScheduleSummaries.ts b/front/src/applications/operationalStudies/helpers/formatTrainScheduleSummaries.ts index f8d41485485..9bdb350a138 100644 --- a/front/src/applications/operationalStudies/helpers/formatTrainScheduleSummaries.ts +++ b/front/src/applications/operationalStudies/helpers/formatTrainScheduleSummaries.ts @@ -23,6 +23,7 @@ const formatTrainScheduleSummaries = ( const trainScheduleWithDetails = relevantTrainSchedules.map((trainSchedule) => { const rollingStock = rollingStocks.find((rs) => rs.name === trainSchedule.rolling_stock_name); + const trainSummary = rawSummaries[trainSchedule.id]; if (!trainSummary) return null; diff --git a/front/src/applications/operationalStudies/hooks/useScenarioData.ts b/front/src/applications/operationalStudies/hooks/useScenarioData.ts index 199203f417c..c3ece829f60 100644 --- a/front/src/applications/operationalStudies/hooks/useScenarioData.ts +++ b/front/src/applications/operationalStudies/hooks/useScenarioData.ts @@ -10,37 +10,33 @@ import { type TimetableDetailedResult, type TrainScheduleResult, } from 'common/api/osrdEditoastApi'; -import { setFailure } from 'reducers/main'; -import { - getSelectedTrainId, - getTrainIdUsedForProjection, -} from 'reducers/simulationResults/selectors'; -import { useAppDispatch } from 'store'; -import { castErrorToFailure } from 'utils/error'; +import { useOsrdConfSelectors } from 'common/osrdContext'; +import { getTrainIdUsedForProjection } from 'reducers/simulationResults/selectors'; import { mapBy } from 'utils/types'; import useAutoUpdateProjection from './useAutoUpdateProjection'; import useLazyLoadTrains from './useLazyLoadTrains'; import usePathProjection from './usePathProjection'; +import formatTrainScheduleSummaries from '../helpers/formatTrainScheduleSummaries'; const useScenarioData = ( scenario: ScenarioResponse, timetable: TimetableDetailedResult, infra: InfraWithState ) => { - const dispatch = useAppDispatch(); + const { getElectricalProfileSetId } = useOsrdConfSelectors(); + const electricalProfileSetId = useSelector(getElectricalProfileSetId); const trainIdUsedForProjection = useSelector(getTrainIdUsedForProjection); - const selectedTrainId = useSelector(getSelectedTrainId); const [trainSchedules, setTrainSchedules] = useState(); const [trainIdsToFetch, setTrainIdsToFetch] = useState(); - const { data: rawTrainSchedules, error: fetchTrainSchedulesError } = - osrdEditoastApi.endpoints.postTrainSchedule.useQuery({ - body: { - ids: timetable.train_ids, - }, - }); + const [fetchTrainSchedules] = osrdEditoastApi.endpoints.postTrainSchedule.useLazyQuery(); + const [putTrainScheduleById] = osrdEditoastApi.endpoints.putTrainScheduleById.useMutation(); + const [postTrainScheduleSimulationSummary] = + osrdEditoastApi.endpoints.postTrainScheduleSimulationSummary.useLazyQuery(); + const { data: { results: rollingStocks } = { results: null } } = + osrdEditoastApi.endpoints.getLightRollingStock.useQuery({ pageSize: 1000 }); const projectionPath = usePathProjection(infra); @@ -59,15 +55,16 @@ const useScenarioData = ( trainSchedules, }); - const { data: conflicts } = osrdEditoastApi.endpoints.getTimetableByIdConflicts.useQuery( - { - id: scenario.timetable_id, - infraId: scenario.infra_id, - }, - { - skip: !allTrainsLoaded, - } - ); + const { data: conflicts, refetch: refetchConflicts } = + osrdEditoastApi.endpoints.getTimetableByIdConflicts.useQuery( + { + id: scenario.timetable_id, + infraId: scenario.infra_id, + }, + { + skip: !allTrainsLoaded, + } + ); const trainScheduleSummaries = useMemo( () => sortBy(Array.from(trainScheduleSummariesById.values()), 'startTime'), @@ -87,13 +84,18 @@ const useScenarioData = ( useAutoUpdateProjection(infra, timetable.train_ids, trainScheduleSummaries); useEffect(() => { - if (!rawTrainSchedules) { - setTrainSchedules(undefined); - } else { + const fetchTrains = async () => { + const rawTrainSchedules = await fetchTrainSchedules({ + body: { + ids: timetable.train_ids, + }, + }).unwrap(); const sortedTrainSchedules = sortBy(rawTrainSchedules, 'start_time'); setTrainSchedules(sortedTrainSchedules); - } - }, [rawTrainSchedules]); + }; + + fetchTrains(); + }, []); // first load of the trainScheduleSummaries useEffect(() => { @@ -103,23 +105,8 @@ const useScenarioData = ( } }, [trainSchedules, infra.state]); - useEffect(() => { - if (fetchTrainSchedulesError) { - dispatch(setFailure(castErrorToFailure(fetchTrainSchedulesError))); - } - }, [fetchTrainSchedulesError]); - const upsertTrainSchedules = useCallback( (trainSchedulesToUpsert: TrainScheduleResult[]) => { - setTrainSchedules((prev) => { - const newTrainSchedulesById = { - ...keyBy(prev, 'id'), - ...keyBy(trainSchedulesToUpsert, 'id'), - }; - const newTrainSchedules = sortBy(Object.values(newTrainSchedulesById), 'start_time'); - return newTrainSchedules; - }); - setProjectedTrainsById((prev) => { const newProjectedTrainsById = new Map(prev); trainSchedulesToUpsert.forEach((trainSchedule) => { @@ -128,6 +115,14 @@ const useScenarioData = ( return newProjectedTrainsById; }); + setTrainSchedules((prev) => { + const newTrainSchedulesById = { + ...keyBy(prev, 'id'), + ...keyBy(trainSchedulesToUpsert, 'id'), + }; + return sortBy(Object.values(newTrainSchedulesById), 'start_time'); + }); + const sortedTrainSchedulesToUpsert = sortBy(trainSchedulesToUpsert, 'start_time'); setTrainIdsToFetch(sortedTrainSchedulesToUpsert.map((trainSchedule) => trainSchedule.id)); }, @@ -160,29 +155,103 @@ const useScenarioData = ( }); }, []); - return { - selectedTrainId, - selectedTrainSummary: selectedTrainId - ? trainScheduleSummariesById.get(selectedTrainId) - : undefined, - trainScheduleSummaries, - trainSchedules, - projectionData: - trainScheduleUsedForProjection && projectionPath - ? { - trainSchedule: trainScheduleUsedForProjection, - ...projectionPath, - projectedTrains, - projectionLoaderData: { - allTrainsProjected, - totalTrains: timetable.train_ids.length, - }, - } - : undefined, - conflicts, - removeTrains, - upsertTrainSchedules, - }; + /** Update only depature time of a train */ + const updateTrainDepartureTime = useCallback( + async (trainId: number, newDeparture: Date) => { + const trainSchedule = trainSchedules?.find((train) => train.id === trainId); + + if (!trainSchedule) { + throw new Error('Train non trouvé'); + } + + const trainScheduleResult = await putTrainScheduleById({ + id: trainId, + trainScheduleForm: { + ...trainSchedule, + start_time: newDeparture.toISOString(), + }, + }).unwrap(); + + setProjectedTrainsById((prev) => { + const newProjectedTrainsById = new Map(prev); + newProjectedTrainsById.set(trainScheduleResult.id, { + ...newProjectedTrainsById.get(trainScheduleResult.id)!, + departureTime: newDeparture, + }); + return newProjectedTrainsById; + }); + + setTrainSchedules((prev) => { + const newTrainSchedulesById = { + ...keyBy(prev, 'id'), + ...keyBy([trainScheduleResult], 'id'), + }; + return sortBy(Object.values(newTrainSchedulesById), 'start_time'); + }); + + // update its summary + const rawSummaries = await postTrainScheduleSimulationSummary({ + body: { + infra_id: scenario.infra_id, + ids: [trainId], + electrical_profile_set_id: electricalProfileSetId, + }, + }).unwrap(); + const summaries = formatTrainScheduleSummaries( + [trainId], + rawSummaries, + mapBy([trainScheduleResult], 'id'), + rollingStocks! + ); + setTrainScheduleSummariesById((prev) => { + const newTrainScheduleSummariesById = new Map(prev); + newTrainScheduleSummariesById.set(trainId, summaries.get(trainId)!); + return newTrainScheduleSummariesById; + }); + + // fetch conflicts + refetchConflicts(); + }, + [trainSchedules, rollingStocks] + ); + + const results = useMemo( + () => ({ + trainScheduleSummaries, + trainSchedules, + projectionData: + trainScheduleUsedForProjection && projectionPath + ? { + trainSchedule: trainScheduleUsedForProjection, + ...projectionPath, + projectedTrains, + projectionLoaderData: { + allTrainsProjected, + totalTrains: timetable.train_ids.length, + }, + } + : undefined, + conflicts, + removeTrains, + upsertTrainSchedules, + updateTrainDepartureTime, + }), + [ + trainScheduleSummaries, + trainSchedules, + trainScheduleUsedForProjection, + projectionPath, + projectedTrains, + allTrainsProjected, + timetable.train_ids.length, + conflicts, + removeTrains, + upsertTrainSchedules, + updateTrainDepartureTime, + ] + ); + + return results; }; export default useScenarioData; diff --git a/front/src/applications/operationalStudies/views/SimulationResults.tsx b/front/src/applications/operationalStudies/views/SimulationResults.tsx index fc6adb38010..07de46cd2b4 100644 --- a/front/src/applications/operationalStudies/views/SimulationResults.tsx +++ b/front/src/applications/operationalStudies/views/SimulationResults.tsx @@ -4,7 +4,7 @@ import { ChevronLeft, ChevronRight } from '@osrd-project/ui-icons'; import cx from 'classnames'; import { useTranslation } from 'react-i18next'; -import type { Conflict } from 'common/api/osrdEditoastApi'; +import { type Conflict } from 'common/api/osrdEditoastApi'; import SimulationWarpedMap from 'common/Map/WarpedMap/SimulationWarpedMap'; import ResizableSection from 'common/ResizableSection'; import ManchetteWithSpaceTimeChartWrapper, { @@ -22,10 +22,12 @@ import type { ProjectionData } from 'modules/simulationResult/types'; import TimesStopsOutput from 'modules/timesStops/TimesStopsOutput'; import type { TrainScheduleWithDetails } from 'modules/trainschedule/components/Timetable/types'; import { updateViewport, type Viewport } from 'reducers/map'; +import { updateSelectedTrainId } from 'reducers/simulationResults'; import { useAppDispatch } from 'store'; import { getPointCoordinates } from 'utils/geometry'; import useSimulationResults from '../hooks/useSimulationResults'; +import type { TrainSpaceTimeData } from '../types'; const SPEED_SPACE_CHART_HEIGHT = 521.5; const HANDLE_TAB_RESIZE_HEIGHT = 20; @@ -36,8 +38,9 @@ type SimulationResultsProps = { collapsedTimetable: boolean; infraId?: number; projectionData?: ProjectionData; - selectedTrainSummary?: TrainScheduleWithDetails; + trainScheduleSummaries?: TrainScheduleWithDetails[]; conflicts?: Conflict[]; + updateTrainDepartureTime: (trainId: number, newDepartureTime: Date) => void; }; const SimulationResults = ({ @@ -45,8 +48,9 @@ const SimulationResults = ({ collapsedTimetable, infraId, projectionData, - selectedTrainSummary, + trainScheduleSummaries, conflicts = [], + updateTrainDepartureTime, }: SimulationResultsProps) => { const { t } = useTranslation('simulation'); const dispatch = useAppDispatch(); @@ -77,8 +81,15 @@ const SimulationResults = ({ pathProperties ); - // Compute path items coordinates in order to place them on the map + const [projectPathTrainResult, setProjectPathTrainResult] = useState([]); + useEffect(() => { + if (projectionData?.projectedTrains) { + setProjectPathTrainResult(projectionData?.projectedTrains || []); + } + }, [projectionData]); + + // Compute path items coordinates in order to place them on the map const pathItemsCoordinates = path && pathProperties && @@ -106,6 +117,30 @@ const SimulationResults = ({ const conflictZones = useProjectedConflicts(infraId, conflicts, projectionData?.path); + const selectedTrainSummary = useMemo( + () => trainScheduleSummaries?.find((train) => train.id === selectedTrainSchedule?.id), + [trainScheduleSummaries, selectedTrainSchedule] + ); + + const handleTrainDrag = async ( + draggedTrainId: number, + newDepartureTime: Date, + { stopPanning }: { stopPanning: boolean } + ) => { + if (stopPanning) { + // update in the database + dispatch(updateSelectedTrainId(draggedTrainId)); + updateTrainDepartureTime(draggedTrainId, newDepartureTime); + } else { + // update in the state + setProjectPathTrainResult( + projectPathTrainResult.map((train) => + train.id === draggedTrainId ? { ...train, departureTime: newDepartureTime } : train + ) + ); + } + }; + useEffect(() => { if (extViewport !== undefined) { dispatch( @@ -170,7 +205,7 @@ const SimulationResults = ({
dispatch(updateSelectedTrainId(trainId))} />
diff --git a/front/src/modules/simulationResult/components/ManchetteWithSpaceTimeChart/ManchetteWithSpaceTimeChart.tsx b/front/src/modules/simulationResult/components/ManchetteWithSpaceTimeChart/ManchetteWithSpaceTimeChart.tsx index 53d3921dbcf..e8fb53ff5b8 100644 --- a/front/src/modules/simulationResult/components/ManchetteWithSpaceTimeChart/ManchetteWithSpaceTimeChart.tsx +++ b/front/src/modules/simulationResult/components/ManchetteWithSpaceTimeChart/ManchetteWithSpaceTimeChart.tsx @@ -1,4 +1,4 @@ -import { useMemo, useRef, useState } from 'react'; +import { useMemo, useRef, useState, useCallback, useEffect } from 'react'; import { Slider } from '@osrd-project/ui-core'; import { KebabHorizontal, Iterations } from '@osrd-project/ui-icons'; @@ -16,6 +16,10 @@ import { OccupancyBlockLayer, } from '@osrd-project/ui-spacetimechart'; import type { Conflict } from '@osrd-project/ui-spacetimechart'; +import type { + SpaceTimeChartProps, + HoveredItem, +} from '@osrd-project/ui-spacetimechart/dist/lib/types'; import cx from 'classnames'; import { compact } from 'lodash'; import { createPortal } from 'react-dom'; @@ -31,8 +35,11 @@ import type { LayerRangeData, WaypointsPanelData, } from 'modules/simulationResult/types'; +import { updateSelectedTrainId } from 'reducers/simulationResults'; +import { useAppDispatch } from 'store'; import SettingsPanel from './SettingsPanel'; +import { getIdFromTrainPath, getPathStyle } from './utils'; import ManchetteMenuButton from '../SpaceTimeChart/ManchetteMenuButton'; import ProjectionLoadingMessage from '../SpaceTimeChart/ProjectionLoadingMessage'; import useWaypointMenu from '../SpaceTimeChart/useWaypointMenu'; @@ -49,7 +56,13 @@ type ManchetteWithSpaceTimeChartProps = { totalTrains: number; allTrainsProjected: boolean; }; + handleTrainDrag?: ( + draggedTrainId: number, + newDepartureTime: Date, + { stopPanning }: { stopPanning: boolean } + ) => Promise; height?: number; + onTrainClick?: (trainId: number) => void; }; export const MANCHETTE_WITH_SPACE_TIME_CHART_DEFAULT_HEIGHT = 561; @@ -65,13 +78,28 @@ const ManchetteWithSpaceTimeChartWrapper = ({ workSchedules, projectionLoaderData: { totalTrains, allTrainsProjected }, height = MANCHETTE_WITH_SPACE_TIME_CHART_DEFAULT_HEIGHT, + handleTrainDrag, + onTrainClick, }: ManchetteWithSpaceTimeChartProps) => { + const dispatch = useAppDispatch(); + const manchetteWithSpaceTimeCharWrappertRef = useRef(null); const manchetteWithSpaceTimeChartRef = useRef(null); + + const [hoveredItem, setHoveredItem] = useState(null); + const [draggingState, setDraggingState] = useState<{ + draggedTrain: TrainSpaceTimeData; + initialDepartureTime: Date; + }>(); const spaceTimeChartRef = useRef(null); const [waypointsPanelIsOpen, setWaypointsPanelIsOpen] = useState(false); + const [tmpSelectedTrain, setTmpSelectedTrain] = useState(selectedTrainScheduleId); + useEffect(() => { + setTmpSelectedTrain(selectedTrainScheduleId); + }, [selectedTrainScheduleId]); + // Cut the space time chart curves if the first or last waypoints are hidden const { filteredProjectPathTrainResult: cutProjectedTrains, filteredConflicts: cutConflicts } = useMemo(() => { @@ -170,7 +198,7 @@ const ManchetteWithSpaceTimeChartWrapper = ({ manchetteWaypoints, cutProjectedTrains, manchetteWithSpaceTimeChartRef, - selectedTrainScheduleId, + tmpSelectedTrain, height, spaceTimeChartRef ); @@ -193,6 +221,51 @@ const ManchetteWithSpaceTimeChartWrapper = ({ })); }); + const onPanOverloaded: SpaceTimeChartProps['onPan'] = async (payload) => { + const { isPanning } = payload; + + if (!handleTrainDrag) { + // if no handleTrainDrag, we pan normally + spaceTimeChartProps.onPan(payload); + return; + } + + // if dragging + if (draggingState) { + const { draggedTrain, initialDepartureTime } = draggingState; + dispatch(updateSelectedTrainId(draggedTrain.id)); + + const timeDiff = payload.data.time - payload.initialData.time; + const newDeparture = new Date(initialDepartureTime.getTime() + timeDiff); + + await handleTrainDrag(draggedTrain.id, newDeparture, { stopPanning: !isPanning }); + + // stop dragging if necessary + if (!isPanning) { + setDraggingState(undefined); + } + return; + } + + // if not dragging, we check if we should start dragging + if (hoveredItem && 'pathId' in hoveredItem.element) { + const hoveredTrainId = getIdFromTrainPath(hoveredItem.element.pathId); + const train = projectPathTrainResult.find((res) => res.id === hoveredTrainId); + if (train) { + setTmpSelectedTrain(train.id); + setDraggingState({ + draggedTrain: train, + initialDepartureTime: train.departureTime, + }); + } else { + console.error(`No train found with id ${hoveredTrainId}`); + } + } + + // if no hovered train, we pan normally + spaceTimeChartProps.onPan(payload); + }; + const waypointMenuData = useWaypointMenu(waypointsPanelData); const manchettePropsWithWaypointMenu = useMemo( @@ -211,6 +284,22 @@ const ManchetteWithSpaceTimeChartWrapper = ({ [manchetteProps, waypointMenuData] ); + const handleHoveredChildUpdate: SpaceTimeChartProps['onHoveredChildUpdate'] = useCallback( + ({ item }: { item: HoveredItem | null }) => { + setHoveredItem(item); + }, + [setHoveredItem] + ); + + const handleClick: SpaceTimeChartProps['onClick'] = () => { + if (!draggingState && hoveredItem && 'pathId' in hoveredItem.element) { + if (selectedTrainScheduleId !== Number(hoveredItem.element.pathId)) { + const trainId = getIdFromTrainPath(hoveredItem.element.pathId); + onTrainClick?.(trainId); + } + } + }; + return (
{waypointMenuData.activeWaypointId && @@ -299,9 +388,16 @@ const ManchetteWithSpaceTimeChartWrapper = ({ } timeOrigin={Math.min(...projectPathTrainResult.map((p) => +p.departureTime))} {...spaceTimeChartProps} + onPan={onPanOverloaded} + onClick={handleClick} + onHoveredChildUpdate={handleHoveredChildUpdate} > {spaceTimeChartProps.paths.map((path) => ( - + ))} {workSchedules && ( +trainPath.split('-')[0]; + +export const getPathStyle = ( + hovered: HoveredItem | null, + path: { color: string; id: string }, + dragging: boolean +): { color: string; level?: PathLevel } => { + if (hovered && 'pathId' in hovered.element && path.id === hovered?.element.pathId && !dragging) { + return { color: PATH_COLORS.HOVERED_PATH, level: 1 }; + } + return { color: path.color }; +}; diff --git a/front/src/modules/simulationResult/consts.ts b/front/src/modules/simulationResult/consts.ts index f06b316fc00..971fa202b28 100644 --- a/front/src/modules/simulationResult/consts.ts +++ b/front/src/modules/simulationResult/consts.ts @@ -65,3 +65,7 @@ export const ASPECT_LABELS_COLORS: Record = { '080A': OCCUPANCY_BLOCKS_COLORS.GREY, '000': OCCUPANCY_BLOCKS_COLORS.GREY, }; + +export const PATH_COLORS = { + HOVERED_PATH: '#6B532E', +}; diff --git a/front/src/modules/timesStops/ReadOnlyTime.tsx b/front/src/modules/timesStops/ReadOnlyTime.tsx index 2c47adda927..182be78f407 100644 --- a/front/src/modules/timesStops/ReadOnlyTime.tsx +++ b/front/src/modules/timesStops/ReadOnlyTime.tsx @@ -9,10 +9,11 @@ type ReadOnlyTimeProps = CellProps; const ReadOnlyTime = ({ rowData }: ReadOnlyTimeProps) => { const { time, daySinceDeparture, dayDisplayed } = rowData || {}; + const { t } = useTranslation('timesStops'); + if (!time) { return null; } - const { t } = useTranslation('timesStops'); const fullString = daySinceDeparture !== undefined && dayDisplayed ? `${time}${NO_BREAK_SPACE}${t('dayCounter', { count: daySinceDeparture })}` diff --git a/front/src/modules/trainschedule/components/Timetable/Timetable.tsx b/front/src/modules/trainschedule/components/Timetable/Timetable.tsx index adcf528bd99..e4d6e1cfeaa 100644 --- a/front/src/modules/trainschedule/components/Timetable/Timetable.tsx +++ b/front/src/modules/trainschedule/components/Timetable/Timetable.tsx @@ -10,7 +10,10 @@ import type { Conflict, InfraState, TrainScheduleResult } from 'common/api/osrdE import i18n from 'i18n'; import ConflictsList from 'modules/conflict/components/ConflictsList'; import { updateSelectedTrainId } from 'reducers/simulationResults'; -import { getTrainIdUsedForProjection } from 'reducers/simulationResults/selectors'; +import { + getSelectedTrainId, + getTrainIdUsedForProjection, +} from 'reducers/simulationResults/selectors'; import { useAppDispatch } from 'store'; import TimetableToolbar from './TimetableToolbar'; @@ -20,7 +23,6 @@ import type { TrainScheduleWithDetails } from './types'; type TimetableProps = { setDisplayTrainScheduleManagement: (mode: string) => void; infraState: InfraState; - selectedTrainId?: number; conflicts?: Conflict[]; upsertTrainSchedules: (trainSchedules: TrainScheduleResult[]) => void; setTrainIdToEdit: (trainId?: number) => void; @@ -36,7 +38,6 @@ const formatDepartureDate = (d: Date) => dayjs(d).locale(i18n.language).format(' const Timetable = ({ setDisplayTrainScheduleManagement, infraState, - selectedTrainId, conflicts, upsertTrainSchedules, removeTrains, @@ -54,6 +55,7 @@ const Timetable = ({ const [conflictsListExpanded, setConflictsListExpanded] = useState(false); const [selectedTrainIds, setSelectedTrainIds] = useState([]); const [showTrainDetails, setShowTrainDetails] = useState(false); + const selectedTrainId = useSelector(getSelectedTrainId); const trainIdUsedForProjection = useSelector(getTrainIdUsedForProjection); const dispatch = useAppDispatch();