1
1
/* eslint-disable @typescript-eslint/no-explicit-any */
2
- import React , { useEffect , useState } from 'react' ;
2
+ import React , { useEffect , useState , useMemo } from 'react' ;
3
3
4
+ import { Select } from '@osrd-project/ui-core' ;
4
5
import { point } from '@turf/helpers' ;
6
+ import { omit } from 'lodash' ;
5
7
import { useTranslation } from 'react-i18next' ;
6
8
import { IoFlag } from 'react-icons/io5' ;
7
9
import { RiMapPin2Fill , RiMapPin3Fill } from 'react-icons/ri' ;
@@ -13,11 +15,13 @@ import { editoastToEditorEntity } from 'applications/editor/data/api';
13
15
import type { TrackSectionEntity } from 'applications/editor/tools/trackEdition/types' ;
14
16
import { calculateDistanceAlongTrack } from 'applications/editor/tools/utils' ;
15
17
import { useManageTrainScheduleContext } from 'applications/operationalStudies/hooks/useManageTrainScheduleContext' ;
18
+ import { useScenarioContext } from 'applications/operationalStudies/hooks/useScenarioContext' ;
16
19
import type { ManageTrainSchedulePathProperties } from 'applications/operationalStudies/types' ;
17
- import { osrdEditoastApi } from 'common/api/osrdEditoastApi' ;
20
+ import { osrdEditoastApi , type OperationalPoint } from 'common/api/osrdEditoastApi' ;
18
21
import { useOsrdConfSelectors } from 'common/osrdContext' ;
19
22
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' ;
21
25
22
26
import type { FeatureInfoClick } from '../types' ;
23
27
@@ -27,30 +31,50 @@ type AddPathStepPopupProps = {
27
31
resetFeatureInfoClick : ( ) => void ;
28
32
} ;
29
33
30
- function AddPathStepPopup ( {
34
+ const AddPathStepPopup = ( {
31
35
pathProperties,
32
36
featureInfoClick,
33
37
resetFeatureInfoClick,
34
- } : AddPathStepPopupProps ) {
38
+ } : AddPathStepPopupProps ) => {
35
39
const { getInfraID, getOrigin, getDestination } = useOsrdConfSelectors ( ) ;
36
40
const { launchPathfinding } = useManageTrainScheduleContext ( ) ;
37
41
const { t } = useTranslation ( [ 'operationalStudies/manageTrainSchedule' ] ) ;
38
42
const infraId = useSelector ( getInfraID ) ;
39
43
const origin = useSelector ( getOrigin ) ;
40
44
const destination = useSelector ( getDestination ) ;
41
45
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
+
42
53
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
+ } > ( ) ;
43
66
44
- const [ getTrackEntity ] =
67
+ const [ getInfraObjectEntity ] =
45
68
osrdEditoastApi . endpoints . postInfraByInfraIdObjectsAndObjectType . useLazyQuery ( ) ;
46
69
47
70
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 ( {
51
75
infraId : infraId ! ,
52
76
objectType : 'TrackSection' ,
53
- body : [ trackId ] ,
77
+ body : [ objectId ] ,
54
78
} ) . unwrap ( ) ;
55
79
56
80
if ( ! result . length ) {
@@ -67,49 +91,127 @@ function AddPathStepPopup({
67
91
setTrackOffset ( offset ) ;
68
92
} ;
69
93
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
+ }
71
142
} , [ featureInfoClick ] ) ;
72
143
73
144
if ( ! featureInfoClick . feature . properties ) return null ;
74
145
75
146
const { properties : trackProperties } = featureInfoClick . feature ;
76
147
const coordinates = featureInfoClick . coordinates . slice ( 0 , 2 ) ;
77
148
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
+ }
91
176
92
177
return (
93
178
< Popup
94
- longitude = { featureInfoClick . coordinates [ 0 ] }
95
- latitude = { featureInfoClick . coordinates [ 1 ] }
179
+ longitude = { coordinates [ 0 ] }
180
+ latitude = { coordinates [ 1 ] }
96
181
closeButton = { false }
97
182
closeOnClick = { false }
98
183
className = "map-popup-click-select"
99
184
>
100
185
< div className = "details" >
101
186
< 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 >
104
189
</ div >
105
190
< 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
+ ) }
107
199
</ div >
108
200
</ div >
109
201
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
+
110
213
< div className = "actions" >
111
214
< button
112
- data-testid = "map-origin-button"
113
215
className = "btn btn-sm btn-success"
114
216
type = "button"
115
217
onClick = { ( ) =>
@@ -123,22 +225,21 @@ function AddPathStepPopup({
123
225
< button
124
226
className = "btn btn-sm btn-info"
125
227
type = "button"
126
- onClick = { ( ) =>
228
+ onClick = { ( ) => {
127
229
setPointIti (
128
230
'via' ,
129
231
pathStepProperties ,
130
232
launchPathfinding ,
131
233
resetFeatureInfoClick ,
132
234
pathProperties
133
- )
134
- }
235
+ ) ;
236
+ } }
135
237
>
136
238
< RiMapPin3Fill />
137
239
< span className = "d-none" > { t ( 'via' ) } </ span >
138
240
</ button >
139
241
) }
140
242
< button
141
- data-testid = "map-destination-button"
142
243
className = "btn btn-sm btn-warning"
143
244
type = "button"
144
245
onClick = { ( ) =>
@@ -151,6 +252,6 @@ function AddPathStepPopup({
151
252
</ div >
152
253
</ Popup >
153
254
) ;
154
- }
255
+ } ;
155
256
156
257
export default React . memo ( AddPathStepPopup ) ;
0 commit comments