From 6f2bbd4c12f7d4a1958ada838e0a905d92dc19cb Mon Sep 17 00:00:00 2001 From: theocrsb Date: Wed, 20 Nov 2024 13:44:18 +0100 Subject: [PATCH] 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 --- .../manageTrainSchedule.json | 1 + .../manageTrainSchedule.json | 1 + .../common/Map/Layers/OperationalPoints.tsx | 22 +- .../pathfinding/helpers/getStepLocation.ts | 17 +- .../AddPathStepPopup.tsx | 190 +++++++++++++----- .../OperationalPointPopupDetails.tsx | 60 ++++++ .../components/ManageTrainSchedule/Map.tsx | 39 +++- .../components/ManageTrainSchedule/types.ts | 6 +- front/src/reducers/osrdconf/helpers.ts | 22 +- front/src/styles/scss/common/map/_popup.scss | 6 + 10 files changed, 294 insertions(+), 70 deletions(-) create mode 100644 front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/OperationalPointPopupDetails.tsx diff --git a/front/public/locales/en/operationalStudies/manageTrainSchedule.json b/front/public/locales/en/operationalStudies/manageTrainSchedule.json index 6b71204c39f..db1e4a9c614 100644 --- a/front/public/locales/en/operationalStudies/manageTrainSchedule.json +++ b/front/public/locales/en/operationalStudies/manageTrainSchedule.json @@ -2,6 +2,7 @@ "addVia": "Add this waypoint", "addVias": "Add waypoints", "addTrainSchedule": "Add one or several trains", + "anyTrack": "Any track", "blocktype": "Signalling block type", "BoundsAreLinked": "Both bounds are linked", "BoundsAreNotLinked": "Both bounds are not linked", diff --git a/front/public/locales/fr/operationalStudies/manageTrainSchedule.json b/front/public/locales/fr/operationalStudies/manageTrainSchedule.json index 80ba3657a5c..556a291cc9c 100644 --- a/front/public/locales/fr/operationalStudies/manageTrainSchedule.json +++ b/front/public/locales/fr/operationalStudies/manageTrainSchedule.json @@ -2,6 +2,7 @@ "addVia": "Ajouter ce point de passage", "addVias": "Ajout de points de passage", "addTrainSchedule": "Ajouter un ou plusieurs trains", + "anyTrack": "Toutes voies", "blocktype": "Type de block", "BoundsAreLinked": "Les deux bornes sont liées", "BoundsAreNotLinked": "Les deux bornes ne sont pas liées", diff --git a/front/src/common/Map/Layers/OperationalPoints.tsx b/front/src/common/Map/Layers/OperationalPoints.tsx index 9c9a1c4c805..b65029b7497 100644 --- a/front/src/common/Map/Layers/OperationalPoints.tsx +++ b/front/src/common/Map/Layers/OperationalPoints.tsx @@ -10,9 +10,15 @@ interface Props { colors: Theme; layerOrder: number; infraID: number | undefined; + operationnalPointId?: string; } -export default function OperationalPoints({ colors, layerOrder, infraID }: Props) { +export default function OperationalPoints({ + colors, + layerOrder, + infraID, + operationnalPointId, +}: Props) { const point: LayerProps = { type: 'circle', 'source-layer': 'operational_points', @@ -42,7 +48,12 @@ export default function OperationalPoints({ colors, layerOrder, infraID }: Props ['concat', ' ', ['get', 'extensions_sncf_ch']], ], ], - 'text-font': ['Roboto Condensed'], + 'text-font': [ + 'case', + ['==', ['get', 'id'], operationnalPointId || ''], + ['literal', ['Roboto Bold']], + ['literal', ['Roboto Condensed']], + ], 'text-size': 12, 'text-anchor': 'left', 'text-justify': 'left', @@ -100,7 +111,12 @@ export default function OperationalPoints({ colors, layerOrder, infraID }: Props ['get', 'extensions_sncf_ch'], ], ], - 'text-font': ['Roboto Condensed'], + 'text-font': [ + 'case', + ['==', ['get', 'id'], operationnalPointId || ''], + ['literal', ['Roboto Bold']], + ['literal', ['Roboto Condensed']], + ], 'text-size': 11, 'text-anchor': 'left', 'text-allow-overlap': false, diff --git a/front/src/modules/pathfinding/helpers/getStepLocation.ts b/front/src/modules/pathfinding/helpers/getStepLocation.ts index 676c8487b53..020f21318cd 100644 --- a/front/src/modules/pathfinding/helpers/getStepLocation.ts +++ b/front/src/modules/pathfinding/helpers/getStepLocation.ts @@ -8,12 +8,23 @@ const getStepLocation = (step: PathItemLocation): PathItemLocation => { return { track: step.track, offset: mToMm(step.offset) }; } if ('operational_point' in step) { - return { operational_point: step.operational_point }; + return { operational_point: step.operational_point, track_reference: step.track_reference }; } if ('trigram' in step) { - return { trigram: step.trigram, secondary_code: step.secondary_code }; + return { + trigram: step.trigram, + secondary_code: step.secondary_code, + track_reference: step.track_reference, + }; } - return { uic: step.uic, secondary_code: step.secondary_code }; + if (step.uic === -1) { + throw new Error('Invalid UIC'); + } + return { + uic: step.uic, + secondary_code: step.secondary_code, + track_reference: step.track_reference, + }; }; export default getStepLocation; diff --git a/front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/AddPathStepPopup.tsx b/front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/AddPathStepPopup.tsx index 3f479d42e5e..3153ec90b32 100644 --- a/front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/AddPathStepPopup.tsx +++ b/front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/AddPathStepPopup.tsx @@ -13,13 +13,16 @@ import { editoastToEditorEntity } from 'applications/editor/data/api'; import type { TrackSectionEntity } from 'applications/editor/tools/trackEdition/types'; import { calculateDistanceAlongTrack } from 'applications/editor/tools/utils'; import { useManageTrainScheduleContext } from 'applications/operationalStudies/hooks/useManageTrainScheduleContext'; +import { useScenarioContext } from 'applications/operationalStudies/hooks/useScenarioContext'; import type { ManageTrainSchedulePathProperties } from 'applications/operationalStudies/types'; -import { osrdEditoastApi } from 'common/api/osrdEditoastApi'; +import { osrdEditoastApi, type OperationalPoint } from 'common/api/osrdEditoastApi'; import { useOsrdConfSelectors } from 'common/osrdContext'; import { setPointIti } from 'modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/setPointIti'; -import type { PathStep } from 'reducers/osrdconf/types'; +import { type PathStep } from 'reducers/osrdconf/types'; +import { getPointCoordinates } from 'utils/geometry'; import type { FeatureInfoClick } from '../types'; +import OperationalPointPopupDetails from './OperationalPointPopupDetails'; type AddPathStepPopupProps = { pathProperties?: ManageTrainSchedulePathProperties; @@ -27,11 +30,11 @@ type AddPathStepPopupProps = { resetFeatureInfoClick: () => void; }; -function AddPathStepPopup({ +const AddPathStepPopup = ({ pathProperties, featureInfoClick, resetFeatureInfoClick, -}: AddPathStepPopupProps) { +}: AddPathStepPopupProps) => { const { getInfraID, getOrigin, getDestination } = useOsrdConfSelectors(); const { launchPathfinding } = useManageTrainScheduleContext(); const { t } = useTranslation(['operationalStudies/manageTrainSchedule']); @@ -39,18 +42,33 @@ function AddPathStepPopup({ const origin = useSelector(getOrigin); const destination = useSelector(getDestination); - const [trackOffset, setTrackOffset] = useState(0); - - const [getTrackEntity] = + const { getTrackSectionsByIds } = useScenarioContext(); + + const [clickedOp, setClickedOp] = useState< + PathStep & { + tracks: { + trackName?: string; + coordinates?: number[]; + }[]; + } + >(); + const [selectedTrack, setSelectedTrack] = useState<{ + trackName?: string; + coordinates?: number[]; + }>(); + const [newPathStep, setNewPathStep] = useState(); + + const [getInfraObjectEntity] = osrdEditoastApi.endpoints.postInfraByInfraIdObjectsAndObjectType.useLazyQuery(); useEffect(() => { - const calculateOffset = async () => { - const trackId = featureInfoClick.feature.properties?.id; - const result = await getTrackEntity({ + const handleTrack = async () => { + const objectId = featureInfoClick.feature.properties?.id; + + const result = await getInfraObjectEntity({ infraId: infraId!, objectType: 'TrackSection', - body: [trackId], + body: [objectId], }).unwrap(); if (!result.length) { @@ -64,56 +82,133 @@ function AddPathStepPopup({ point(featureInfoClick.coordinates.slice(0, 2)).geometry, 'millimeters' ); - setTrackOffset(offset); + + if (!featureInfoClick.feature.properties) return; + + const { properties } = featureInfoClick.feature; + setNewPathStep({ + id: nextId(), + coordinates: featureInfoClick.coordinates.slice(0, 2), + track: properties.id, + offset: Math.round(offset), + kp: properties.kp, + metadata: { + lineCode: properties.extensions_sncf_line_code, + lineName: properties.extensions_sncf_line_name, + trackName: properties.extensions_sncf_track_name, + trackNumber: properties.extensions_sncf_track_number, + }, + }); }; - calculateOffset(); + const handleOperationalPoint = async () => { + const objectId = featureInfoClick.feature.properties?.id; + + const result = await getInfraObjectEntity({ + infraId: infraId!, + objectType: 'OperationalPoint', + body: [objectId], + }).unwrap(); + + if (!result.length) { + console.error('No operational point found'); + return; + } + + const operationalPoint = result[0].railjson as OperationalPoint; + const trackIds = operationalPoint.parts.map((part) => part.track); + const tracks = await getTrackSectionsByIds(trackIds); + + const trackPartCoordinates = operationalPoint.parts.map((part) => ({ + trackName: tracks[part.track]?.extensions?.sncf?.track_name, + coordinates: getPointCoordinates( + tracks[part.track]?.geo, + tracks[part.track]?.length, + part.position + ), + })); + + trackPartCoordinates.unshift({ + trackName: undefined, + coordinates: result[0].geographic.coordinates as number[], + }); + + setClickedOp({ + id: nextId(), + secondary_code: operationalPoint.extensions!.sncf!.ch, + uic: operationalPoint.extensions!.identifier!.uic, + tracks: trackPartCoordinates, + }); + setSelectedTrack(trackPartCoordinates[0]); + }; + + setClickedOp(undefined); + + if (featureInfoClick.isOperationalPoint) { + handleOperationalPoint(); + } else { + handleTrack(); + } }, [featureInfoClick]); - if (!featureInfoClick.feature.properties) return null; + useEffect(() => { + if (!clickedOp || !selectedTrack) { + setNewPathStep(undefined); + return; + } + + const { tracks: _tracks, ...opWithoutTracks } = clickedOp; + setNewPathStep({ + ...opWithoutTracks, + coordinates: selectedTrack.coordinates, + track_reference: selectedTrack.trackName + ? { track_name: selectedTrack.trackName } + : undefined, + }); + }, [clickedOp, selectedTrack]); + + if ( + !newPathStep || + !featureInfoClick.feature.properties || + (featureInfoClick.isOperationalPoint && !clickedOp) + ) + return null; - const { properties: trackProperties } = featureInfoClick.feature; const coordinates = featureInfoClick.coordinates.slice(0, 2); - const pathStepProperties: PathStep = { - id: nextId(), - coordinates, - track: trackProperties.id, - offset: Math.round(trackOffset), // offset needs to be an integer - kp: trackProperties.kp, - metadata: { - lineCode: trackProperties.extensions_sncf_line_code, - lineName: trackProperties.extensions_sncf_line_name, - trackName: trackProperties.extensions_sncf_track_name, - trackNumber: trackProperties.extensions_sncf_track_number, - }, - }; - return ( -
-
- {featureInfoClick.feature.properties.extensions_sncf_track_name} - {featureInfoClick.feature.properties.extensions_sncf_line_code} -
-
- {featureInfoClick.feature.properties.extensions_sncf_line_name} + {featureInfoClick.isOperationalPoint ? ( + + ) : ( +
+
+ {featureInfoClick.feature.properties.extensions_sncf_track_name} + {featureInfoClick.feature.properties.extensions_sncf_line_code} +
+
+ {featureInfoClick.feature.properties.extensions_sncf_line_name} +
-
+ )}
)}
); -} +}; export default React.memo(AddPathStepPopup); diff --git a/front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/OperationalPointPopupDetails.tsx b/front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/OperationalPointPopupDetails.tsx new file mode 100644 index 00000000000..19a961373fd --- /dev/null +++ b/front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/OperationalPointPopupDetails.tsx @@ -0,0 +1,60 @@ +import React, { type SetStateAction } from 'react'; + +import { Select } from '@osrd-project/ui-core'; +import { useTranslation } from 'react-i18next'; + +import type { PathStep } from 'reducers/osrdconf/types'; + +import type { FeatureInfoClick } from '../types'; + +type OperationalPointPopupDetailsProps = { + operationalPoint: FeatureInfoClick; + clickedOp: PathStep & { + tracks: { + trackName?: string; + coordinates?: number[]; + }[]; + }; + selectedTrack: { + trackName?: string; + coordinates?: number[]; + }; + setSelectedTrack: React.Dispatch< + SetStateAction<{ trackName?: string; coordinates?: number[] } | undefined> + >; +}; + +const OperationalPointPopupDetails = ({ + operationalPoint, + clickedOp, + selectedTrack, + setSelectedTrack, +}: OperationalPointPopupDetailsProps) => { + const { t } = useTranslation(['operationalStudies/manageTrainSchedule']); + + return ( + <> +
+
+ {operationalPoint.feature.properties!.extensions_sncf_track_name} + {operationalPoint.feature.properties!.extensions_sncf_line_code} +
+
+ {operationalPoint.feature.properties!.extensions_identifier_name}
+ {operationalPoint.feature.properties!.extensions_sncf_trigram}{' '} + {operationalPoint.feature.properties!.extensions_sncf_ch} +
+
+