Skip to content

Commit b1bbf6e

Browse files
committed
front: select op with map when add train
-bold pr on hover (for PRs on same track) -select pr on map and choose track Signed-off-by: theocrsb <theo_crosbie@yahoo.fr>
1 parent f02812f commit b1bbf6e

File tree

9 files changed

+229
-58
lines changed

9 files changed

+229
-58
lines changed

front/public/locales/en/operationalStudies/manageTrainSchedule.json

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"addVia": "Add this waypoint",
33
"addVias": "Add waypoints",
44
"addTrainSchedule": "Add one or several trains",
5+
"anyTrack": "Any track",
56
"blocktype": "Signalling block type",
67
"BoundsAreLinked": "Both bounds are linked",
78
"BoundsAreNotLinked": "Both bounds are not linked",

front/public/locales/fr/operationalStudies/manageTrainSchedule.json

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"addVia": "Ajouter ce point de passage",
33
"addVias": "Ajout de points de passage",
44
"addTrainSchedule": "Ajouter un ou plusieurs trains",
5+
"anyTrack": "Toutes voies",
56
"blocktype": "Type de block",
67
"BoundsAreLinked": "Les deux bornes sont liées",
78
"BoundsAreNotLinked": "Les deux bornes ne sont pas liées",

front/src/common/Map/Layers/OperationalPoints.tsx

+19-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@ interface Props {
1010
colors: Theme;
1111
layerOrder: number;
1212
infraID: number | undefined;
13+
operationnalPointId?: string;
1314
}
1415

15-
export default function OperationalPoints({ colors, layerOrder, infraID }: Props) {
16+
export default function OperationalPoints({
17+
colors,
18+
layerOrder,
19+
infraID,
20+
operationnalPointId,
21+
}: Props) {
1622
const point: LayerProps = {
1723
type: 'circle',
1824
'source-layer': 'operational_points',
@@ -42,7 +48,12 @@ export default function OperationalPoints({ colors, layerOrder, infraID }: Props
4248
['concat', ' ', ['get', 'extensions_sncf_ch']],
4349
],
4450
],
45-
'text-font': ['Roboto Condensed'],
51+
'text-font': [
52+
'case',
53+
['==', ['get', 'id'], operationnalPointId || ''],
54+
['literal', ['Roboto Bold']],
55+
['literal', ['Roboto Condensed']],
56+
],
4657
'text-size': 12,
4758
'text-anchor': 'left',
4859
'text-justify': 'left',
@@ -100,7 +111,12 @@ export default function OperationalPoints({ colors, layerOrder, infraID }: Props
100111
['get', 'extensions_sncf_ch'],
101112
],
102113
],
103-
'text-font': ['Roboto Condensed'],
114+
'text-font': [
115+
'case',
116+
['==', ['get', 'id'], operationnalPointId || ''],
117+
['literal', ['Roboto Bold']],
118+
['literal', ['Roboto Condensed']],
119+
],
104120
'text-size': 11,
105121
'text-anchor': 'left',
106122
'text-allow-overlap': false,
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
1-
import type { PathItemLocation } from 'common/api/osrdEditoastApi';
1+
import type { OperationalPointReference, PathItemLocation } from 'common/api/osrdEditoastApi';
22
import { mToMm } from 'utils/physics';
33

44
const getStepLocation = (step: PathItemLocation): PathItemLocation => {
5+
const trackReference: OperationalPointReference['track_reference'] =
6+
!('track' in step) && step.track_reference && 'track_name' in step.track_reference
7+
? { track_name: step.track_reference.track_name }
8+
: null;
9+
510
if ('track' in step) {
611
// TODO: step offset should be in mm in the store /!\
712
// pathfinding blocks endpoint requires offsets in mm
813
return { track: step.track, offset: mToMm(step.offset) };
914
}
1015
if ('operational_point' in step) {
11-
return { operational_point: step.operational_point };
16+
return { operational_point: step.operational_point, track_reference: trackReference };
1217
}
1318
if ('trigram' in step) {
14-
return { trigram: step.trigram, secondary_code: step.secondary_code };
19+
return {
20+
trigram: step.trigram,
21+
secondary_code: step.secondary_code,
22+
track_reference: trackReference,
23+
};
24+
}
25+
if (step.uic === -1) {
26+
throw new Error('Invalid UIC');
1527
}
16-
return { uic: step.uic, secondary_code: step.secondary_code };
28+
return {
29+
uic: step.uic,
30+
secondary_code: step.secondary_code,
31+
track_reference: trackReference,
32+
};
1733
};
1834

1935
export default getStepLocation;
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import React, { useEffect, useState } from 'react';
2+
import React, { useEffect, useState, useMemo } from 'react';
33

4+
import { Select } from '@osrd-project/ui-core';
45
import { point } from '@turf/helpers';
6+
import { omit } from 'lodash';
57
import { useTranslation } from 'react-i18next';
68
import { IoFlag } from 'react-icons/io5';
79
import { RiMapPin2Fill, RiMapPin3Fill } from 'react-icons/ri';
@@ -13,11 +15,13 @@ import { editoastToEditorEntity } from 'applications/editor/data/api';
1315
import type { TrackSectionEntity } from 'applications/editor/tools/trackEdition/types';
1416
import { calculateDistanceAlongTrack } from 'applications/editor/tools/utils';
1517
import { useManageTrainScheduleContext } from 'applications/operationalStudies/hooks/useManageTrainScheduleContext';
18+
import { useScenarioContext } from 'applications/operationalStudies/hooks/useScenarioContext';
1619
import type { ManageTrainSchedulePathProperties } from 'applications/operationalStudies/types';
17-
import { osrdEditoastApi } from 'common/api/osrdEditoastApi';
20+
import { osrdEditoastApi, type OperationalPoint } from 'common/api/osrdEditoastApi';
1821
import { useOsrdConfSelectors } from 'common/osrdContext';
1922
import { setPointIti } from 'modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/setPointIti';
20-
import type { PathStep } from 'reducers/osrdconf/types';
23+
import { type PathStep } from 'reducers/osrdconf/types';
24+
import { getPointCoordinates } from 'utils/geometry';
2125

2226
import type { FeatureInfoClick } from '../types';
2327

@@ -27,30 +31,50 @@ type AddPathStepPopupProps = {
2731
resetFeatureInfoClick: () => void;
2832
};
2933

30-
function AddPathStepPopup({
34+
const AddPathStepPopup = ({
3135
pathProperties,
3236
featureInfoClick,
3337
resetFeatureInfoClick,
34-
}: AddPathStepPopupProps) {
38+
}: AddPathStepPopupProps) => {
3539
const { getInfraID, getOrigin, getDestination } = useOsrdConfSelectors();
3640
const { launchPathfinding } = useManageTrainScheduleContext();
3741
const { t } = useTranslation(['operationalStudies/manageTrainSchedule']);
3842
const infraId = useSelector(getInfraID);
3943
const origin = useSelector(getOrigin);
4044
const destination = useSelector(getDestination);
4145

46+
const isOperationalPoint = useMemo(() => {
47+
const properties = featureInfoClick?.feature?.properties;
48+
return !!properties?.track_id || !!properties?.track_name;
49+
}, [featureInfoClick]);
50+
51+
const { getTrackSectionsByIds } = useScenarioContext();
52+
4253
const [trackOffset, setTrackOffset] = useState(0);
54+
const [clickedOp, setClickedOp] = useState<
55+
Extract<PathStep, { uic: number }> & {
56+
tracks: {
57+
trackName?: string;
58+
coordinates?: number[];
59+
}[];
60+
}
61+
>();
62+
const [selectedTrack, setSelectedTrack] = useState<{
63+
trackName?: string;
64+
coordinates?: number[];
65+
}>();
4366

44-
const [getTrackEntity] =
67+
const [getInfraObjectEntity] =
4568
osrdEditoastApi.endpoints.postInfraByInfraIdObjectsAndObjectType.useLazyQuery();
4669

4770
useEffect(() => {
48-
const calculateOffset = async () => {
49-
const trackId = featureInfoClick.feature.properties?.id;
50-
const result = await getTrackEntity({
71+
const handleTrack = async () => {
72+
const objectId = featureInfoClick.feature.properties?.id;
73+
74+
const result = await getInfraObjectEntity({
5175
infraId: infraId!,
5276
objectType: 'TrackSection',
53-
body: [trackId],
77+
body: [objectId],
5478
}).unwrap();
5579

5680
if (!result.length) {
@@ -67,49 +91,127 @@ function AddPathStepPopup({
6791
setTrackOffset(offset);
6892
};
6993

70-
calculateOffset();
94+
const handleOperationalPoint = async () => {
95+
const objectId = featureInfoClick.feature.properties?.id;
96+
97+
const result = await getInfraObjectEntity({
98+
infraId: infraId!,
99+
objectType: 'OperationalPoint',
100+
body: [objectId],
101+
}).unwrap();
102+
103+
if (!result.length) {
104+
console.error('No operational point found');
105+
return;
106+
}
107+
108+
const operationalPoint = result[0].railjson as OperationalPoint;
109+
const trackIds = operationalPoint.parts.map((part) => part.track);
110+
const tracks = await getTrackSectionsByIds(trackIds);
111+
112+
const trackPartCoordinates = operationalPoint.parts.map((part) => ({
113+
trackName: tracks[part.track]?.extensions?.sncf?.track_name,
114+
coordinates: getPointCoordinates(
115+
tracks[part.track]?.geo,
116+
tracks[part.track]?.length,
117+
part.position
118+
),
119+
}));
120+
121+
trackPartCoordinates.unshift({
122+
trackName: undefined,
123+
coordinates: result[0].geographic.coordinates as number[],
124+
});
125+
126+
setClickedOp({
127+
id: nextId(),
128+
secondary_code: operationalPoint.extensions!.sncf!.ch,
129+
uic: operationalPoint.extensions!.identifier!.uic,
130+
tracks: trackPartCoordinates,
131+
});
132+
setSelectedTrack(trackPartCoordinates[0]);
133+
};
134+
135+
setClickedOp(undefined);
136+
137+
if (isOperationalPoint) {
138+
handleOperationalPoint();
139+
} else {
140+
handleTrack();
141+
}
71142
}, [featureInfoClick]);
72143

73144
if (!featureInfoClick.feature.properties) return null;
74145

75146
const { properties: trackProperties } = featureInfoClick.feature;
76147
const coordinates = featureInfoClick.coordinates.slice(0, 2);
77148

78-
const pathStepProperties: PathStep = {
79-
id: nextId(),
80-
coordinates,
81-
track: trackProperties.id,
82-
offset: Math.round(trackOffset), // offset needs to be an integer
83-
kp: trackProperties.kp,
84-
metadata: {
85-
lineCode: trackProperties.extensions_sncf_line_code,
86-
lineName: trackProperties.extensions_sncf_line_name,
87-
trackName: trackProperties.extensions_sncf_track_name,
88-
trackNumber: trackProperties.extensions_sncf_track_number,
89-
},
90-
};
149+
let pathStepProperties: PathStep;
150+
if (isOperationalPoint && clickedOp && selectedTrack) {
151+
const newPathStep: PathStep = {
152+
...omit(clickedOp, ['tracks']),
153+
coordinates: selectedTrack.coordinates,
154+
track_reference: selectedTrack.trackName
155+
? { track_name: selectedTrack.trackName }
156+
: undefined,
157+
};
158+
pathStepProperties = {
159+
...newPathStep,
160+
};
161+
} else {
162+
pathStepProperties = {
163+
id: nextId(),
164+
coordinates,
165+
track: trackProperties.id,
166+
offset: Math.round(trackOffset), // offset needs to be an integer
167+
kp: trackProperties.kp,
168+
metadata: {
169+
lineCode: trackProperties.extensions_sncf_line_code,
170+
lineName: trackProperties.extensions_sncf_line_name,
171+
trackName: trackProperties.extensions_sncf_track_name,
172+
trackNumber: trackProperties.extensions_sncf_track_number,
173+
},
174+
};
175+
}
91176

92177
return (
93178
<Popup
94-
longitude={featureInfoClick.coordinates[0]}
95-
latitude={featureInfoClick.coordinates[1]}
179+
longitude={coordinates[0]}
180+
latitude={coordinates[1]}
96181
closeButton={false}
97182
closeOnClick={false}
98183
className="map-popup-click-select"
99184
>
100185
<div className="details">
101186
<div className="details-track">
102-
{featureInfoClick.feature.properties.extensions_sncf_track_name}
103-
<small>{featureInfoClick.feature.properties.extensions_sncf_line_code}</small>
187+
{isOperationalPoint && trackProperties.extensions_sncf_track_name}
188+
<small>{trackProperties.extensions_sncf_line_code}</small>
104189
</div>
105190
<div className="details-line">
106-
{featureInfoClick.feature.properties.extensions_sncf_line_name}
191+
{isOperationalPoint ? (
192+
<>
193+
{trackProperties.extensions_identifier_name} <br />
194+
{trackProperties.extensions_sncf_trigram} {trackProperties.extensions_sncf_ch}
195+
</>
196+
) : (
197+
trackProperties.extensions_sncf_line_name
198+
)}
107199
</div>
108200
</div>
109201

202+
{isOperationalPoint && clickedOp?.tracks && (
203+
<Select
204+
getOptionLabel={(option) => option?.trackName || t('anyTrack')}
205+
getOptionValue={(option) => option?.trackName || ''}
206+
id="select-track"
207+
onChange={(selectedOption) => setSelectedTrack(selectedOption)}
208+
options={clickedOp.tracks}
209+
value={selectedTrack}
210+
/>
211+
)}
212+
110213
<div className="actions">
111214
<button
112-
data-testid="map-origin-button"
113215
className="btn btn-sm btn-success"
114216
type="button"
115217
onClick={() =>
@@ -123,22 +225,21 @@ function AddPathStepPopup({
123225
<button
124226
className="btn btn-sm btn-info"
125227
type="button"
126-
onClick={() =>
228+
onClick={() => {
127229
setPointIti(
128230
'via',
129231
pathStepProperties,
130232
launchPathfinding,
131233
resetFeatureInfoClick,
132234
pathProperties
133-
)
134-
}
235+
);
236+
}}
135237
>
136238
<RiMapPin3Fill />
137239
<span className="d-none">{t('via')}</span>
138240
</button>
139241
)}
140242
<button
141-
data-testid="map-destination-button"
142243
className="btn btn-sm btn-warning"
143244
type="button"
144245
onClick={() =>
@@ -151,6 +252,6 @@ function AddPathStepPopup({
151252
</div>
152253
</Popup>
153254
);
154-
}
255+
};
155256

156257
export default React.memo(AddPathStepPopup);

front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/setPointIti.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { addElementAtIndex, replaceElementAtIndex } from 'utils/array';
77

88
export function setPointIti(
99
pointType: 'origin' | 'destination' | 'via',
10-
pathStep: PathStep,
10+
pathStep: Extract<PathStep, { track: string } | { uic: number }>,
1111
launchPathfinding: (newPathSteps: (PathStep | null)[]) => void,
1212
resetFeatureInfoClick: () => void,
1313
pathProperties?: ManageTrainSchedulePathProperties

0 commit comments

Comments
 (0)