diff --git a/front/src/modules/timesStops/helpers/computeMargins.ts b/front/src/modules/timesStops/helpers/computeMargins.ts index 4e01a822109..fd8b5540857 100644 --- a/front/src/modules/timesStops/helpers/computeMargins.ts +++ b/front/src/modules/timesStops/helpers/computeMargins.ts @@ -1,41 +1,51 @@ import type { TrainScheduleResult } from 'common/api/osrdEditoastApi'; -import type { TrainScheduleWithDetails } from 'modules/trainschedule/components/Timetable/types'; +import type { + TheoreticalMarginsRecord, + TrainScheduleWithDetails, +} from 'modules/trainschedule/components/Timetable/types'; import { ms2sec } from 'utils/timeManipulation'; import { formatDigitsAndUnit } from './utils'; +import type { ScheduleEntry } from '../types'; -function getTheoreticalMargin(selectedTrainSchedule: TrainScheduleResult, pathStepId: string) { - if (selectedTrainSchedule.path.length === 0) { - return undefined; - } - // pathStep is starting point => we take the first margin - if (selectedTrainSchedule.path[0].id === pathStepId) { - return selectedTrainSchedule.margins?.values[0]; - } - const theoreticalMarginBoundaryIndex = selectedTrainSchedule.margins?.boundaries?.findIndex( - (id) => id === pathStepId - ); - if ( - theoreticalMarginBoundaryIndex === undefined || - theoreticalMarginBoundaryIndex < 0 || - theoreticalMarginBoundaryIndex > selectedTrainSchedule.margins!.values.length - 2 - ) { - return undefined; - } - - return selectedTrainSchedule.margins!.values[theoreticalMarginBoundaryIndex + 1]; +export function getTheoreticalMargins(selectedTrainSchedule: TrainScheduleResult) { + const theoreticalMargins: TheoreticalMarginsRecord = {}; + let marginIndex = 0; + selectedTrainSchedule.path.forEach((step, index) => { + if (step.id === selectedTrainSchedule.margins?.boundaries[marginIndex]) { + marginIndex += 1; + theoreticalMargins[step.id] = { + theoreticalMargin: selectedTrainSchedule.margins?.values[marginIndex], + isBoundary: true, + }; + } else { + theoreticalMargins[step.id] = { + theoreticalMargin: selectedTrainSchedule.margins?.values[marginIndex], + isBoundary: index === 0, + }; + } + }); + return theoreticalMargins; } function computeMargins( + theoreticalMargins: TheoreticalMarginsRecord, selectedTrainSchedule: TrainScheduleResult, + scheduleByAt: Record, pathStepIndex: number, pathItemTimes: NonNullable // in ms ) { const { path, margins } = selectedTrainSchedule; + const pathStepId = path[pathStepIndex].id; if ( !margins || - (margins.values.length === 1 && margins.values[0] === '0%') || - pathStepIndex === selectedTrainSchedule.path.length - 1 + pathStepIndex === selectedTrainSchedule.path.length - 1 || + !theoreticalMargins[pathStepId] || + !( + scheduleByAt[pathStepId]?.arrival || + scheduleByAt[pathStepId]?.stop_for || + theoreticalMargins[pathStepId].isBoundary + ) ) { return { theoreticalMargin: undefined, @@ -45,15 +55,21 @@ function computeMargins( }; } - const pathStepId = path[pathStepIndex].id; - const theoreticalMargin = getTheoreticalMargin(selectedTrainSchedule, pathStepId); + const { theoreticalMargin, isBoundary } = theoreticalMargins[pathStepId]; + + // find the next pathStep where constraints are defined + let nextIndex = path.length - 1; - // find the previous pathStep where margin was defined - let prevIndex = 0; - // eslint-disable-next-line no-plusplus - for (let index = 1; index < pathStepIndex; index++) { - if (margins.boundaries.includes(path[index].id)) { - prevIndex = index; + for (let index = pathStepIndex + 1; index < path.length; index += 1) { + const curStepId = path[index].id; + const curStepSchedule = scheduleByAt[curStepId]; + if ( + theoreticalMargins[curStepId]?.isBoundary || + curStepSchedule?.arrival || + curStepSchedule?.stop_for + ) { + nextIndex = index; + break; } } @@ -62,9 +78,9 @@ function computeMargins( // provisional = margins // final = margin + requested arrival times const { base, provisional, final } = pathItemTimes; - const baseDuration = ms2sec(base[pathStepIndex + 1] - base[prevIndex]); - const provisionalDuration = ms2sec(provisional[pathStepIndex + 1] - provisional[prevIndex]); - const finalDuration = ms2sec(final[pathStepIndex + 1] - final[prevIndex]); + const baseDuration = ms2sec(base[nextIndex] - base[pathStepIndex]); + const provisionalDuration = ms2sec(provisional[nextIndex] - provisional[pathStepIndex]); + const finalDuration = ms2sec(final[nextIndex] - final[pathStepIndex]); // how much longer it took (s) with the margin than without const provisionalLostTime = provisionalDuration - baseDuration; @@ -72,6 +88,7 @@ function computeMargins( return { theoreticalMargin: formatDigitsAndUnit(theoreticalMargin), + isTheoreticalMarginBoundary: isBoundary, theoreticalMarginSeconds: `${Math.round(provisionalLostTime)} s`, calculatedMargin: `${Math.round(finalLostTime)} s`, diffMargins: `${Math.round(finalLostTime - provisionalLostTime)} s`, diff --git a/front/src/modules/timesStops/helpers/utils.ts b/front/src/modules/timesStops/helpers/utils.ts index a3ce59e9637..31bb4a2afce 100644 --- a/front/src/modules/timesStops/helpers/utils.ts +++ b/front/src/modules/timesStops/helpers/utils.ts @@ -135,7 +135,7 @@ const getDigits = (unit: string | undefined) => unit === MarginUnit.second || unit === MarginUnit.percent ? 0 : 1; export function formatDigitsAndUnit(fullValue: string | number | undefined, unit?: string) { - if (fullValue === undefined || fullValue === '0%') { + if (fullValue === undefined) { return ''; } if (typeof fullValue === 'number') { diff --git a/front/src/modules/timesStops/hooks/useOutputTableData.ts b/front/src/modules/timesStops/hooks/useOutputTableData.ts index 8d4387a2dee..48dc89d4071 100644 --- a/front/src/modules/timesStops/hooks/useOutputTableData.ts +++ b/front/src/modules/timesStops/hooks/useOutputTableData.ts @@ -14,7 +14,7 @@ import type { TrainScheduleWithDetails } from 'modules/trainschedule/components/ import { dateToHHMM } from 'utils/date'; import { computeInputDatetimes } from '../helpers/arrivalTime'; -import computeMargins from '../helpers/computeMargins'; +import computeMargins, { getTheoreticalMargins } from '../helpers/computeMargins'; import { formatSchedule } from '../helpers/scheduleData'; import { type ScheduleEntry, type TimeStopsRow } from '../types'; @@ -28,6 +28,7 @@ const useOutputTableData = ( const { t } = useTranslation('timesStops'); const scheduleByAt: Record = keyBy(selectedTrainSchedule.schedule, 'at'); + const theoreticalMargins = getTheoreticalMargins(selectedTrainSchedule); const startDatetime = new Date(selectedTrainSchedule.start_time); @@ -49,8 +50,19 @@ const useOutputTableData = ( computedArrival, schedule ); - const { theoreticalMargin, theoreticalMarginSeconds, calculatedMargin, diffMargins } = - computeMargins(selectedTrainSchedule, index, trainSummary.pathItemTimes); + const { + theoreticalMargin, + isTheoreticalMarginBoundary, + theoreticalMarginSeconds, + calculatedMargin, + diffMargins, + } = computeMargins( + theoreticalMargins, + selectedTrainSchedule, + scheduleByAt, + index, + trainSummary.pathItemTimes + ); const inputs = computeInputDatetimes(startDatetime, previousTime, schedule, { isDeparture: index === 0, @@ -70,6 +82,7 @@ const useOutputTableData = ( onStopSignal, shortSlipDistance, theoreticalMargin, + isTheoreticalMarginBoundary, theoreticalMarginSeconds, calculatedMargin, diff --git a/front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx b/front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx index fc37daa8673..e4f3f7bdc33 100644 --- a/front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx +++ b/front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx @@ -158,7 +158,7 @@ export const useTimeStopsColumns = ( continuousUpdates: false, placeholder: !isOutputTable ? t('theoreticalMarginPlaceholder') : '', formatBlurredInput: (value) => { - if (!value || value === '0%') return ''; + if (!value) return ''; if (!isOutputTable && !marginRegExValidation.test(value)) { return `${value}${t('theoreticalMarginPlaceholder')}`; } @@ -168,7 +168,10 @@ export const useTimeStopsColumns = ( }) ), cellClassName: ({ rowData }) => - cx({ invalidCell: !isOutputTable && !rowData.isMarginValid }), + cx({ + invalidCell: !isOutputTable && !rowData.isMarginValid, + repeatedValue: !rowData.isTheoreticalMarginBoundary, + }), title: t('theoreticalMargin'), headerClassName: 'padded-header', minWidth: 100, diff --git a/front/src/modules/timesStops/styles/_timesStopsDatasheet.scss b/front/src/modules/timesStops/styles/_timesStopsDatasheet.scss index 9c70e2007c1..e6def4ba976 100644 --- a/front/src/modules/timesStops/styles/_timesStopsDatasheet.scss +++ b/front/src/modules/timesStops/styles/_timesStopsDatasheet.scss @@ -23,6 +23,10 @@ color: red !important; } + .repeatedValue { + color: var(--grey30); + } + .warning-schedule { background: var(--warning30); } diff --git a/front/src/modules/timesStops/types.ts b/front/src/modules/timesStops/types.ts index cb59a099aef..9b65ec563d3 100644 --- a/front/src/modules/timesStops/types.ts +++ b/front/src/modules/timesStops/types.ts @@ -21,6 +21,7 @@ export type TimeStopsRow = { onStopSignal?: boolean; shortSlipDistance?: boolean; theoreticalMargin?: string; // value asked by user + isTheoreticalMarginBoundary?: boolean; // tells whether the theoreticalMargin value was inputted for this line or if it is repeated from a previous line theoreticalMarginSeconds?: string; calculatedMargin?: string; diff --git a/front/src/modules/trainschedule/components/Timetable/types.ts b/front/src/modules/trainschedule/components/Timetable/types.ts index 0895db56428..3cbc282f1ef 100644 --- a/front/src/modules/trainschedule/components/Timetable/types.ts +++ b/front/src/modules/trainschedule/components/Timetable/types.ts @@ -39,3 +39,8 @@ export type InvalidReason = | Extract | PathfindingNotFound['error_type'] | PathfindingInputError['error_type']; + +export type TheoreticalMarginsRecord = Record< + string, + { theoreticalMargin: string | undefined; isBoundary: boolean } | undefined +>;