Skip to content

Commit 6690d02

Browse files
committed
front: align margins display and computation to spec in timestops table
1 parent 1db8246 commit 6690d02

File tree

7 files changed

+83
-40
lines changed

7 files changed

+83
-40
lines changed

front/src/modules/timesStops/helpers/computeMargins.ts

+51-34
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,51 @@
11
import type { TrainScheduleResult } from 'common/api/osrdEditoastApi';
2-
import type { TrainScheduleWithDetails } from 'modules/trainschedule/components/Timetable/types';
2+
import type {
3+
TheoreticalMarginsRecord,
4+
TrainScheduleWithDetails,
5+
} from 'modules/trainschedule/components/Timetable/types';
36
import { ms2sec } from 'utils/timeManipulation';
47

58
import { formatDigitsAndUnit } from './utils';
9+
import type { ScheduleEntry } from '../types';
610

7-
function getTheoreticalMargin(selectedTrainSchedule: TrainScheduleResult, pathStepId: string) {
8-
if (selectedTrainSchedule.path.length === 0) {
9-
return undefined;
10-
}
11-
// pathStep is starting point => we take the first margin
12-
if (selectedTrainSchedule.path[0].id === pathStepId) {
13-
return selectedTrainSchedule.margins?.values[0];
14-
}
15-
const theoreticalMarginBoundaryIndex = selectedTrainSchedule.margins?.boundaries?.findIndex(
16-
(id) => id === pathStepId
17-
);
18-
if (
19-
theoreticalMarginBoundaryIndex === undefined ||
20-
theoreticalMarginBoundaryIndex < 0 ||
21-
theoreticalMarginBoundaryIndex > selectedTrainSchedule.margins!.values.length - 2
22-
) {
23-
return undefined;
24-
}
25-
26-
return selectedTrainSchedule.margins!.values[theoreticalMarginBoundaryIndex + 1];
11+
export function getTheoreticalMargins(selectedTrainSchedule: TrainScheduleResult) {
12+
const theoreticalMargins: TheoreticalMarginsRecord = {};
13+
let marginIndex = 0;
14+
selectedTrainSchedule.path.forEach((step, index) => {
15+
if (step.id === selectedTrainSchedule.margins?.boundaries[marginIndex]) {
16+
marginIndex += 1;
17+
theoreticalMargins[step.id] = {
18+
theoreticalMargin: selectedTrainSchedule.margins?.values[marginIndex],
19+
isBoundary: true,
20+
};
21+
} else {
22+
theoreticalMargins[step.id] = {
23+
theoreticalMargin: selectedTrainSchedule.margins?.values[marginIndex],
24+
isBoundary: index === 0,
25+
};
26+
}
27+
});
28+
return theoreticalMargins;
2729
}
2830

2931
function computeMargins(
32+
theoreticalMargins: TheoreticalMarginsRecord,
3033
selectedTrainSchedule: TrainScheduleResult,
34+
scheduleByAt: Record<string, ScheduleEntry>,
3135
pathStepIndex: number,
3236
pathItemTimes: NonNullable<TrainScheduleWithDetails['pathItemTimes']> // in ms
3337
) {
3438
const { path, margins } = selectedTrainSchedule;
39+
const pathStepId = path[pathStepIndex].id;
3540
if (
3641
!margins ||
37-
(margins.values.length === 1 && margins.values[0] === '0%') ||
38-
pathStepIndex === selectedTrainSchedule.path.length - 1
42+
pathStepIndex === selectedTrainSchedule.path.length - 1 ||
43+
!theoreticalMargins[pathStepId] ||
44+
!(
45+
scheduleByAt[pathStepId]?.arrival ||
46+
scheduleByAt[pathStepId]?.stop_for ||
47+
theoreticalMargins[pathStepId].isBoundary
48+
)
3949
) {
4050
return {
4151
theoreticalMargin: undefined,
@@ -45,15 +55,21 @@ function computeMargins(
4555
};
4656
}
4757

48-
const pathStepId = path[pathStepIndex].id;
49-
const theoreticalMargin = getTheoreticalMargin(selectedTrainSchedule, pathStepId);
58+
const { theoreticalMargin, isBoundary } = theoreticalMargins[pathStepId];
59+
60+
// find the next pathStep where constraints are defined
61+
let nextIndex = path.length - 1;
5062

51-
// find the previous pathStep where margin was defined
52-
let prevIndex = 0;
53-
// eslint-disable-next-line no-plusplus
54-
for (let index = 1; index < pathStepIndex; index++) {
55-
if (margins.boundaries.includes(path[index].id)) {
56-
prevIndex = index;
63+
for (let index = pathStepIndex + 1; index < path.length; index += 1) {
64+
const curStepId = path[index].id;
65+
const curStepSchedule = scheduleByAt[curStepId];
66+
if (
67+
theoreticalMargins[curStepId]?.isBoundary ||
68+
curStepSchedule?.arrival ||
69+
curStepSchedule?.stop_for
70+
) {
71+
nextIndex = index;
72+
break;
5773
}
5874
}
5975

@@ -62,16 +78,17 @@ function computeMargins(
6278
// provisional = margins
6379
// final = margin + requested arrival times
6480
const { base, provisional, final } = pathItemTimes;
65-
const baseDuration = ms2sec(base[pathStepIndex + 1] - base[prevIndex]);
66-
const provisionalDuration = ms2sec(provisional[pathStepIndex + 1] - provisional[prevIndex]);
67-
const finalDuration = ms2sec(final[pathStepIndex + 1] - final[prevIndex]);
81+
const baseDuration = ms2sec(base[nextIndex] - base[pathStepIndex]);
82+
const provisionalDuration = ms2sec(provisional[nextIndex] - provisional[pathStepIndex]);
83+
const finalDuration = ms2sec(final[nextIndex] - final[pathStepIndex]);
6884

6985
// how much longer it took (s) with the margin than without
7086
const provisionalLostTime = provisionalDuration - baseDuration;
7187
const finalLostTime = finalDuration - baseDuration;
7288

7389
return {
7490
theoreticalMargin: formatDigitsAndUnit(theoreticalMargin),
91+
isTheoreticalMarginBoundary: isBoundary,
7592
theoreticalMarginSeconds: `${Math.round(provisionalLostTime)} s`,
7693
calculatedMargin: `${Math.round(finalLostTime)} s`,
7794
diffMargins: `${Math.round(finalLostTime - provisionalLostTime)} s`,

front/src/modules/timesStops/helpers/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ const getDigits = (unit: string | undefined) =>
135135
unit === MarginUnit.second || unit === MarginUnit.percent ? 0 : 1;
136136

137137
export function formatDigitsAndUnit(fullValue: string | number | undefined, unit?: string) {
138-
if (fullValue === undefined || fullValue === '0%') {
138+
if (fullValue === undefined) {
139139
return '';
140140
}
141141
if (typeof fullValue === 'number') {

front/src/modules/timesStops/hooks/useOutputTableData.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { TrainScheduleWithDetails } from 'modules/trainschedule/components/
1414
import { dateToHHMM } from 'utils/date';
1515

1616
import { computeInputDatetimes } from '../helpers/arrivalTime';
17-
import computeMargins from '../helpers/computeMargins';
17+
import computeMargins, { getTheoreticalMargins } from '../helpers/computeMargins';
1818
import { formatSchedule } from '../helpers/scheduleData';
1919
import { type ScheduleEntry, type TimeStopsRow } from '../types';
2020

@@ -28,6 +28,7 @@ const useOutputTableData = (
2828
const { t } = useTranslation('timesStops');
2929

3030
const scheduleByAt: Record<string, ScheduleEntry> = keyBy(selectedTrainSchedule.schedule, 'at');
31+
const theoreticalMargins = getTheoreticalMargins(selectedTrainSchedule);
3132

3233
const startDatetime = new Date(selectedTrainSchedule.start_time);
3334

@@ -49,8 +50,19 @@ const useOutputTableData = (
4950
computedArrival,
5051
schedule
5152
);
52-
const { theoreticalMargin, theoreticalMarginSeconds, calculatedMargin, diffMargins } =
53-
computeMargins(selectedTrainSchedule, index, trainSummary.pathItemTimes);
53+
const {
54+
theoreticalMargin,
55+
isTheoreticalMarginBoundary,
56+
theoreticalMarginSeconds,
57+
calculatedMargin,
58+
diffMargins,
59+
} = computeMargins(
60+
theoreticalMargins,
61+
selectedTrainSchedule,
62+
scheduleByAt,
63+
index,
64+
trainSummary.pathItemTimes
65+
);
5466

5567
const inputs = computeInputDatetimes(startDatetime, previousTime, schedule, {
5668
isDeparture: index === 0,
@@ -70,6 +82,7 @@ const useOutputTableData = (
7082
onStopSignal,
7183
shortSlipDistance,
7284
theoreticalMargin,
85+
isTheoreticalMarginBoundary,
7386

7487
theoreticalMarginSeconds,
7588
calculatedMargin,

front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export const useTimeStopsColumns = <T extends TimeStopsRow>(
158158
continuousUpdates: false,
159159
placeholder: !isOutputTable ? t('theoreticalMarginPlaceholder') : '',
160160
formatBlurredInput: (value) => {
161-
if (!value || value === '0%') return '';
161+
if (!value) return '';
162162
if (!isOutputTable && !marginRegExValidation.test(value)) {
163163
return `${value}${t('theoreticalMarginPlaceholder')}`;
164164
}
@@ -168,7 +168,10 @@ export const useTimeStopsColumns = <T extends TimeStopsRow>(
168168
})
169169
),
170170
cellClassName: ({ rowData }) =>
171-
cx({ invalidCell: !isOutputTable && !rowData.isMarginValid }),
171+
cx({
172+
invalidCell: !isOutputTable && !rowData.isMarginValid,
173+
repeatedValue: rowData.isTheoreticalMarginBoundary === false, // the class should be added on false but not undefined
174+
}),
172175
title: t('theoreticalMargin'),
173176
headerClassName: 'padded-header',
174177
minWidth: 100,

front/src/modules/timesStops/styles/_timesStopsDatasheet.scss

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
color: red !important;
2424
}
2525

26+
.repeatedValue {
27+
color: var(--grey30);
28+
}
29+
2630
.warning-schedule {
2731
background: var(--warning30);
2832
}

front/src/modules/timesStops/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type TimeStopsRow = {
2121
onStopSignal?: boolean;
2222
shortSlipDistance?: boolean;
2323
theoreticalMargin?: string; // value asked by user
24+
isTheoreticalMarginBoundary?: boolean; // tells whether the theoreticalMargin value was inputted for this line or if it is repeated from a previous line
2425

2526
theoreticalMarginSeconds?: string;
2627
calculatedMargin?: string;

front/src/modules/trainschedule/components/Timetable/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@ export type InvalidReason =
3939
| Extract<SimulationSummaryResult['status'], 'pathfinding_failure' | 'simulation_failed'>
4040
| PathfindingNotFound['error_type']
4141
| PathfindingInputError['error_type'];
42+
43+
export type TheoreticalMarginsRecord = Record<
44+
string,
45+
{ theoreticalMargin: string | undefined; isBoundary: boolean } | undefined
46+
>;

0 commit comments

Comments
 (0)