Skip to content

Commit fe1fb78

Browse files
committed
front: lmr: linked train search improvements
Signed-off-by: SarahBellaha <sarah.bellaha@sncf.fr> front: stdcm: fix app crash when using the date picker in debug mode Signed-off-by: SarahBellaha <sarah.bellaha@sncf.fr> front: add trigram of new selected op Signed-off-by: SarahBellaha <sarah.bellaha@sncf.fr> front: stdcm: fix op search payload Signed-off-by: SarahBellaha <sarah.bellaha@sncf.fr> front: lmr: be able to reset linked train search front: lmr: fix date picker issue Signed-off-by: SarahBellaha <sarah.bellaha@sncf.fr> front: change arrivalDate type to Date in isArrivalDateInSearchTimeWindow Signed-off-by: SarahBellaha <sarah.bellaha@sncf.fr>
1 parent 0ce1212 commit fe1fb78

File tree

6 files changed

+146
-66
lines changed

6 files changed

+146
-66
lines changed

front/src/applications/stdcm/components/StdcmForm/StdcmLinkedPathSearch.tsx

+13-9
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,22 @@ const StdcmLinkedPathSearch = ({
3333

3434
const {
3535
displaySearchButton,
36-
hasSearchBeenLaunched,
3736
launchTrainScheduleSearch,
3837
linkedPathDate,
3938
linkedPathResults,
39+
resetLinkedPathSearch,
4040
selectableSlot,
4141
setDisplaySearchButton,
4242
setLinkedPathDate,
4343
setTrainNameInput,
4444
trainNameInput,
4545
} = useLinkedPathSearch();
4646

47+
const removeLinkedPathCard = () => {
48+
setShowLinkedPathSearch(false);
49+
resetLinkedPathSearch();
50+
};
51+
4752
return (
4853
<div className={`stdcm-linked-path-search-container ${className}`}>
4954
{!displayLinkedPathSearch ? (
@@ -60,7 +65,7 @@ const StdcmLinkedPathSearch = ({
6065
disabled={disabled}
6166
name={cardName}
6267
title={
63-
<button type="button" onClick={() => setShowLinkedPathSearch(false)}>
68+
<button type="button" onClick={removeLinkedPathCard}>
6469
{t('translation:common.delete').toLowerCase()}
6570
</button>
6671
}
@@ -99,18 +104,17 @@ const StdcmLinkedPathSearch = ({
99104
{t('find')}
100105
</button>
101106
)}
102-
{!displaySearchButton && !linkedPathResults.length && (
107+
{!displaySearchButton && !linkedPathResults && (
103108
<div className="stdcm-linked-path-button white">
104109
<Gear size="lg" className="stdcm-linked-path-loading" />
105110
</div>
106111
)}
107-
{linkedPathResults.length > 0 ? (
108-
<StdcmLinkedPathResults linkedPathResults={linkedPathResults} linkedOp={linkedOp} />
109-
) : (
110-
hasSearchBeenLaunched && (
112+
{linkedPathResults &&
113+
(linkedPathResults.length > 0 ? (
114+
<StdcmLinkedPathResults linkedPathResults={linkedPathResults} linkedOp={linkedOp} />
115+
) : (
111116
<p className="text-center mb-0">{t('noCorrespondingResults')}</p>
112-
)
113-
)}
117+
))}
114118
</StdcmCard>
115119
)}
116120
</div>

front/src/applications/stdcm/components/StdcmForm/StdcmOpSchedule.tsx

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo } from 'react';
1+
import { useEffect, useMemo } from 'react';
22

33
import { DatePicker, Select, TimePicker, TolerancePicker } from '@osrd-project/ui-core';
44
import { useTranslation } from 'react-i18next';
@@ -61,7 +61,7 @@ const StdcmOpSchedule = ({
6161
useMemo(() => {
6262
const isArrivalDateValid =
6363
opTimingData?.arrivalDate &&
64-
isArrivalDateInSearchTimeWindow(opTimingData.arrivalDate, searchDatetimeWindow);
64+
isArrivalDateInSearchTimeWindow(new Date(opTimingData.arrivalDate), searchDatetimeWindow);
6565
return {
6666
arrivalDate:
6767
opTimingData && isArrivalDateValid
@@ -99,6 +99,19 @@ const StdcmOpSchedule = ({
9999
[t, searchDatetimeWindow]
100100
);
101101

102+
useEffect(() => {
103+
if (
104+
!isArrivalDateInSearchTimeWindow(arrivalDate, searchDatetimeWindow) &&
105+
opScheduleTimeType === 'preciseTime'
106+
) {
107+
onArrivalChange({
108+
date: defaultDate(searchDatetimeWindow?.begin),
109+
hours: arrivalTimeHours || 0,
110+
minutes: arrivalTimeMinutes || 0,
111+
});
112+
}
113+
}, [searchDatetimeWindow]);
114+
102115
return (
103116
<>
104117
<div className="arrival-type-select">

front/src/applications/stdcm/hooks/useLinkedPathSearch.ts

+81-37
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
1-
import { useMemo, useState, useCallback } from 'react';
1+
import { useMemo, useState, useCallback, useEffect } from 'react';
22

3-
import { compact, groupBy } from 'lodash';
3+
import { compact } from 'lodash';
44
import { useSelector } from 'react-redux';
55

6-
import type { PathItem, SearchResultItemTrainSchedule } from 'common/api/osrdEditoastApi';
6+
import type {
7+
PathItem,
8+
SearchQuery,
9+
SearchResultItemOperationalPoint,
10+
SearchResultItemTrainSchedule,
11+
} from 'common/api/osrdEditoastApi';
712
import { osrdEditoastApi } from 'common/api/osrdEditoastApi';
8-
import { useOsrdConfSelectors } from 'common/osrdContext';
9-
import { isEqualDate } from 'utils/date';
13+
import { useInfraID, useOsrdConfSelectors } from 'common/osrdContext';
14+
import { isArrivalDateInSearchTimeWindow, isEqualDate } from 'utils/date';
1015

11-
import type { StdcmLinkedPathResult, StdcmLinkedPathStep } from '../types';
16+
import type { StdcmLinkedPathResult } from '../types';
1217
import computeOpSchedules from '../utils/computeOpSchedules';
1318

1419
const useLinkedPathSearch = () => {
1520
const [postSearch] = osrdEditoastApi.endpoints.postSearch.useMutation();
21+
const [postTrainScheduleSimulationSummary] =
22+
osrdEditoastApi.endpoints.postTrainScheduleSimulationSummary.useLazyQuery();
1623

1724
const { getTimetableID, getSearchDatetimeWindow } = useOsrdConfSelectors();
1825

26+
const infraId = useInfraID();
1927
const timetableId = useSelector(getTimetableID);
2028
const searchDatetimeWindow = useSelector(getSearchDatetimeWindow);
2129

@@ -29,42 +37,60 @@ const useLinkedPathSearch = () => {
2937
}, [searchDatetimeWindow]);
3038

3139
const [displaySearchButton, setDisplaySearchButton] = useState(true);
32-
const [hasSearchBeenLaunched, setHasSearchBeenLaunched] = useState(false);
3340
const [trainNameInput, setTrainNameInput] = useState('');
3441
const [linkedPathDate, setLinkedPathDate] = useState(selectableSlot.start);
35-
const [linkedPathResults, setLinkedPathResults] = useState<StdcmLinkedPathResult[]>([]);
42+
const [linkedPathResults, setLinkedPathResults] = useState<StdcmLinkedPathResult[]>();
3643

37-
const getExtremitiesDetails = useCallback(
38-
async (pathItemList: PathItem[]) => {
39-
const origin = pathItemList.at(0)!;
40-
const destination = pathItemList.at(-1)!;
41-
if (!('operational_point' in origin) || !('operational_point' in destination))
42-
return undefined;
43-
const originId = origin.operational_point;
44-
const destinationId = destination.operational_point;
44+
const getExtremityDetails = useCallback(
45+
async (pathItem: PathItem) => {
46+
if (!('operational_point' in pathItem) && !('uic' in pathItem)) return undefined;
47+
48+
const pathItemQuery =
49+
'operational_point' in pathItem
50+
? ['=', ['obj_id'], pathItem.operational_point]
51+
: ([
52+
'and',
53+
['=', ['uic'], pathItem.uic],
54+
['=', ['ch'], pathItem.secondary_code],
55+
] as SearchQuery);
4556

4657
try {
4758
const payloadOP = {
4859
object: 'operationalpoint',
49-
query: ['or', ['=', ['obj_id'], originId], ['=', ['obj_id'], destinationId]],
50-
};
51-
const resultsOP = await postSearch({ searchPayload: payloadOP, pageSize: 25 }).unwrap();
52-
const groupedResults = groupBy(resultsOP, 'obj_id');
53-
return {
54-
origin: groupedResults[originId][0],
55-
destination: groupedResults[destinationId][0],
60+
query: pathItemQuery,
5661
};
62+
const opDetails = (await postSearch({
63+
searchPayload: payloadOP,
64+
pageSize: 25,
65+
}).unwrap()) as SearchResultItemOperationalPoint[];
66+
return opDetails[0];
5767
} catch (error) {
58-
console.error('Failed to fetch operational points:', error);
68+
console.error('Failed to fetch operational point:', error);
5969
return undefined;
6070
}
6171
},
6272
[postSearch]
6373
);
6474

75+
const getTrainsSummaries = useCallback(
76+
async (trainsIds: number[]) => {
77+
if (!infraId) return undefined;
78+
const trainsSummaries = await postTrainScheduleSimulationSummary({
79+
body: {
80+
infra_id: infraId,
81+
ids: trainsIds,
82+
},
83+
}).unwrap();
84+
return trainsSummaries;
85+
},
86+
[postTrainScheduleSimulationSummary, infraId]
87+
);
88+
6589
const launchTrainScheduleSearch = useCallback(async () => {
90+
setLinkedPathResults(undefined);
91+
if (!trainNameInput) return;
92+
6693
setDisplaySearchButton(false);
67-
setLinkedPathResults([]);
6894
try {
6995
const results = (await postSearch({
7096
searchPayload: {
@@ -86,41 +112,59 @@ const useLinkedPathSearch = () => {
86112
return;
87113
}
88114

115+
const filteredResultsSummaries = await getTrainsSummaries(filteredResults.map((r) => r.id));
116+
89117
const newLinkedPathResults = await Promise.all(
90118
filteredResults.map(async (result) => {
91-
const opDetails = await getExtremitiesDetails(result.path);
92-
const computedOpSchedules = computeOpSchedules(
93-
result.start_time,
94-
result.schedule.at(-1)!.arrival!
95-
);
96-
if (opDetails === undefined) return undefined;
119+
const resultSummary = filteredResultsSummaries && filteredResultsSummaries[result.id];
120+
if (!resultSummary || resultSummary.status !== 'success') return undefined;
121+
const msFromStartTime = resultSummary.path_item_times_final.at(-1)!;
122+
123+
const originDetails = await getExtremityDetails(result.path.at(0)!);
124+
const destinationDetails = await getExtremityDetails(result.path.at(-1)!);
125+
const computedOpSchedules = computeOpSchedules(result.start_time, msFromStartTime);
126+
127+
if (!originDetails || !destinationDetails) return undefined;
97128
return {
98129
trainName: result.train_name,
99-
origin: { ...opDetails.origin, ...computedOpSchedules.origin } as StdcmLinkedPathStep,
130+
origin: { ...originDetails, ...computedOpSchedules.origin },
100131
destination: {
101-
...opDetails.destination,
132+
...destinationDetails,
102133
...computedOpSchedules.destination,
103-
} as StdcmLinkedPathStep,
134+
},
104135
};
105136
})
106137
);
107138
setLinkedPathResults(compact(newLinkedPathResults));
108-
setHasSearchBeenLaunched(true);
109139
} catch (error) {
110140
console.error('Train schedule search failed:', error);
111141
setDisplaySearchButton(true);
112142
}
113-
}, [postSearch, trainNameInput, timetableId, linkedPathDate, getExtremitiesDetails]);
143+
}, [postSearch, trainNameInput, timetableId, linkedPathDate, getExtremityDetails]);
144+
145+
const resetLinkedPathSearch = () => {
146+
setDisplaySearchButton(true);
147+
setLinkedPathResults(undefined);
148+
setTrainNameInput('');
149+
};
150+
151+
useEffect(() => {
152+
if (!isArrivalDateInSearchTimeWindow(linkedPathDate, searchDatetimeWindow)) {
153+
setLinkedPathDate(selectableSlot.start);
154+
resetLinkedPathSearch();
155+
}
156+
}, [selectableSlot]);
114157

115158
return {
116159
displaySearchButton,
117-
hasSearchBeenLaunched,
118160
launchTrainScheduleSearch,
119161
linkedPathDate,
120162
linkedPathResults,
163+
resetLinkedPathSearch,
121164
selectableSlot,
122165
setDisplaySearchButton,
123166
setLinkedPathDate,
167+
setLinkedPathResults,
124168
setTrainNameInput,
125169
trainNameInput,
126170
};

front/src/applications/stdcm/utils/computeOpSchedules.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,26 @@ import {
44
substractDurationToIsoDate,
55
} from 'utils/date';
66

7-
const computeOpSchedules = (startTime: string, secondsFromStartTime: string) => {
7+
/**
8+
* Computes the operation schedules for a given start time and duration.
9+
*
10+
* @param startTime - The ISO string representing the start time.
11+
* @param msFromStartTime - The duration in milliseconds from the start time.
12+
* @returns An object containing the origin and destination schedules.
13+
*
14+
* The function extracts the date and time from the provided ISO start time and calculates the destination arrival time
15+
* by adding the specified duration. It then returns an object with the origin and destination schedules, including
16+
* the date, time, and ISO arrival times.
17+
*
18+
* Note: A margin of 1800 seconds (30 minutes) is applied to the departure and arrival times to allow for necessary
19+
* activities such as preparation for the next departure.
20+
*/
21+
const computeOpSchedules = (startTime: string, msFromStartTime: number) => {
822
const { arrivalDate: originDate, arrivalTime: originTime } = extractDateAndTimefromISO(
923
startTime,
1024
'DD/MM/YY'
1125
);
12-
const destinationArrivalTime = addDurationToIsoDate(startTime, secondsFromStartTime);
26+
const destinationArrivalTime = addDurationToIsoDate(startTime, msFromStartTime, 'millisecond');
1327
const { arrivalDate: destinationDate, arrivalTime: destinationTime } = extractDateAndTimefromISO(
1428
destinationArrivalTime,
1529
'DD/MM/YY'
@@ -19,12 +33,12 @@ const computeOpSchedules = (startTime: string, secondsFromStartTime: string) =>
1933
origin: {
2034
date: originDate,
2135
time: originTime,
22-
isoArrivalTime: substractDurationToIsoDate(startTime, 'PT1800S'),
36+
isoArrivalTime: substractDurationToIsoDate(startTime, 1800),
2337
},
2438
destination: {
2539
date: destinationDate,
2640
time: destinationTime,
27-
isoArrivalTime: addDurationToIsoDate(destinationArrivalTime, 'PT1800S'),
41+
isoArrivalTime: addDurationToIsoDate(destinationArrivalTime, 1800),
2842
},
2943
};
3044
};

front/src/utils/__tests__/date.spec.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe('extractDateAndTimefromISO', () => {
9595

9696
describe('isArrivalDateInSearchTimeWindow', () => {
9797
it('should return true if searchDatetimeWindow is undefined', () => {
98-
const result = isArrivalDateInSearchTimeWindow('2024-08-01T10:00:00Z', undefined);
98+
const result = isArrivalDateInSearchTimeWindow(new Date('2024-08-01T10:00:00Z'), undefined);
9999
expect(result).toBe(true);
100100
});
101101

@@ -104,7 +104,10 @@ describe('isArrivalDateInSearchTimeWindow', () => {
104104
begin: new Date('2024-08-01T00:00:00Z'),
105105
end: new Date('2024-08-02T00:00:00Z'),
106106
};
107-
const result = isArrivalDateInSearchTimeWindow('2024-08-01T10:00:00Z', searchDatetimeWindow);
107+
const result = isArrivalDateInSearchTimeWindow(
108+
new Date('2024-08-01T10:00:00Z'),
109+
searchDatetimeWindow
110+
);
108111
expect(result).toBe(true);
109112
});
110113

@@ -113,7 +116,10 @@ describe('isArrivalDateInSearchTimeWindow', () => {
113116
begin: new Date('2024-08-01T00:00:00Z'),
114117
end: new Date('2024-08-02T00:00:00Z'),
115118
};
116-
const result = isArrivalDateInSearchTimeWindow('2024-07-30T23:59:59Z', searchDatetimeWindow);
119+
const result = isArrivalDateInSearchTimeWindow(
120+
new Date('2024-07-30T23:59:59Z'),
121+
searchDatetimeWindow
122+
);
117123
expect(result).toBe(false);
118124
});
119125
});

0 commit comments

Comments
 (0)