diff --git a/front/public/locales/en/operational-studies.json b/front/public/locales/en/operational-studies.json index f33e04ee37b..b5dd627d27b 100644 --- a/front/public/locales/en/operational-studies.json +++ b/front/public/locales/en/operational-studies.json @@ -344,15 +344,22 @@ "inverseOD": "Reverse itinerary", "itineraryModal": { "addLocationOnMap": "Add a location on the map", + "alertInvalidOP": "At least one of the waypoints could not be recognized", "focusLocationOnMap": "Focus the map on this operational point", "frameAll": "Center on all path steps on map", + "invalidOP": "Non recognized point : ", "moveLocationOnMap": "Move location on the map", "next": "Next", + "opId": "Operational point identifier", "opName": "Operational point name", "openItineraryModal": "New interface for itinerary edition", + "requestedPoint": "Point on map", "secondaryCode": "Code", "track": "Track", - "trainName": "Train name" + "trackId": "Track identifier", + "trainName": "Train name", + "trigram": "Trigram", + "uic": "UIC" }, "launchPathFinding": "Launch pathfinding", "manageVias": "Steps management", diff --git a/front/public/locales/fr/operational-studies.json b/front/public/locales/fr/operational-studies.json index c452855206a..9dab0236d8d 100644 --- a/front/public/locales/fr/operational-studies.json +++ b/front/public/locales/fr/operational-studies.json @@ -344,15 +344,22 @@ "inverseOD": "Inverser l'itinéraire", "itineraryModal": { "addLocationOnMap": "Ajouter un point remarquable sur la carte", + "alertInvalidOP": "Au moins un des points de passage n'a pas été reconnu", "focusLocationOnMap": "Centrer la carte sur ce point remarquable", "frameAll": "Centrer sur tous les points de passage sur la carte", + "invalidOP": "Point non reconnu : ", "moveLocationOnMap": "Déplacer le point remarquable sur la carte", "next": "Suivant", + "opId": "Identifiant de point", "opName": "Nom du point remarquable", "openItineraryModal": "Nouvelle interface d'édition d'itinéraire", + "requestedPoint": "Point sur la carte", "secondaryCode": "CH", "track": "Voie", - "trainName": "Nom du train" + "trackId": "Identifiant de voie", + "trainName": "Nom du train", + "trigram": "Trigramme", + "uic": "UIC" }, "launchPathFinding": "Lancer la recherche d'itinéraire", "manageVias": "Gestion des étapes", diff --git a/front/src/applications/operationalStudies/types.ts b/front/src/applications/operationalStudies/types.ts index 6a8d88ea19b..2cc86d9eaf4 100644 --- a/front/src/applications/operationalStudies/types.ts +++ b/front/src/applications/operationalStudies/types.ts @@ -154,3 +154,8 @@ export type StudyCardDetails = SearchResultItemStudy | StudyWithScenarios; export type ScenarioCardDetails = SearchResultItemScenario | ScenarioWithDetails; export type CategoryColors = { normal: string; hovered: string; background: string }; + +export type ItineraryPathProperties = PathProperties & { + length: number; + incompatibleConstraints?: IncompatibleConstraints; +}; diff --git a/front/src/applications/operationalStudies/views/ProjectList/styles/_projectList.scss b/front/src/applications/operationalStudies/views/ProjectList/styles/_projectList.scss index a92bac7b698..d6019be1641 100644 --- a/front/src/applications/operationalStudies/views/ProjectList/styles/_projectList.scss +++ b/front/src/applications/operationalStudies/views/ProjectList/styles/_projectList.scss @@ -24,39 +24,3 @@ max-width: 640px; } } - -.alert-box { - width: 100%; - min-height: 56px; - display: flex; - align-items: center; - padding: 0 24px 0 40px; - border-top: 1px solid; - border-bottom: 1px solid; - margin-top: -1px; - margin-bottom: 16px; - - &--warning { - border-color: var(--black10); - background-color: var(--warning5); - - .alert-box__icon--warning { - color: var(--warning30); - margin-right: 12px; - width: 24px; - height: 24px; - } - - .alert-box__text--warning { - font-family: 'IBM Plex Sans'; - color: var(--warning60); - font-size: 16px; - font-weight: 400; - } - - .alert-box__close-button { - color: var(--warning60); - margin-left: auto; - } - } -} diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/ItineraryModal.tsx b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/ItineraryModal.tsx index 819500cba32..c0c88771b6a 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/ItineraryModal.tsx +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/ItineraryModal.tsx @@ -13,6 +13,7 @@ import { useScenarioContext } from 'applications/operationalStudies/hooks/useSce import AlertBox from 'common/AlertBox'; import type { PathProperties } from 'common/api/osrdEditoastApi'; import { computeBBoxViewport } from 'common/Map/WarpedMap/core/helpers'; +import IncompatibleConstraints from 'modules/pathfinding/components/IncompatibleConstraints'; import usePathfindingV2 from 'modules/pathfinding/hooks/usePathfindingV2'; import { useMapSettings, useMapSettingsActions } from 'reducers/commonMap'; import { @@ -63,9 +64,12 @@ const ItineraryModal = ({ const [categoryWarning, setCategoryWarning] = useState(undefined); const { pathStepsMetadataById } = usePathStepsMetadata(pathSteps); - const { launchPathfindingV2, pathProperties } = usePathfindingV2(); + const { launchPathfindingV2, pathProperties, pathfindingError } = usePathfindingV2(); const isMapDisabled = window.matchMedia('(max-width: 1028px)').matches; + const hasInvalidPathStep = Array.from(pathStepsMetadataById.values()).some( + (metadata) => metadata.isInvalid + ); const frameAllPathSteps = () => { if (pathProperties && pathProperties.geometry) { @@ -186,6 +190,10 @@ const ItineraryModal = ({
{categoryWarning && } + {hasInvalidPathStep && } + {!hasInvalidPathStep && pathfindingError && ( + + )}
- {pathSteps.map((pathStep, i) => ( - - ))} + {pathSteps.map((pathStep, i) => { + const pathStepMetadata = pathStepsMetadataById.get(pathStep.id); + const previousPathStepMetadata = pathStepsMetadataById.get(pathSteps[i - 1]?.id); + return ( + 0 && (pathStepMetadata?.isInvalid || previousPathStepMetadata?.isInvalid) + } + /> + ); + })} + > + +
)} diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/ItineraryModalMap.tsx b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/ItineraryModalMap.tsx index a83cd58f0fe..f8f2a67cca6 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/ItineraryModalMap.tsx +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/ItineraryModalMap.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useRef, useState } from 'react'; +import { useCallback, useMemo, useRef, useState, type PropsWithChildren } from 'react'; import type { Position } from 'geojson'; import { useTranslation } from 'react-i18next'; @@ -42,7 +42,8 @@ const ItineraryModalMap = ({ pathSteps, pathStepsMetadata, pathProperties, -}: ItineraryModalMapProps) => { + children, +}: PropsWithChildren) => { const { t } = useTranslation('operational-studies', { keyPrefix: 'main', }); @@ -222,6 +223,7 @@ const ItineraryModalMap = ({ /> ); })} + {children} ); diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/PathStepItem.tsx b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/PathStepItem.tsx index dcc73accd68..534fe26701b 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/PathStepItem.tsx +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/PathStepItem.tsx @@ -39,6 +39,39 @@ const PathStepItem = ({ const mapSettings = useMapSettings(); const { updateViewport } = useMapSettingsActions(); + const getInvalidMessage = () => { + let message = t('invalidOP'); + if (!pathStepMetadata?.isInvalid || !pathStep?.location) return message; + + const { location } = pathStep; + + if ('track' in location) { + return (message += t('requestedPoint')); + } + + const trackInfo = location.track_reference + ? 'track_name' in location.track_reference + ? `, ${t('track')} ${location.track_reference.track_name}` + : `, ${t('trackId')}` + : ''; + + if ('operational_point' in location) { + return (message += t('opId') + trackInfo); + } + + const secondaryCodeInfo = location.secondary_code ? `/${location.secondary_code}` : ''; + + if ('trigram' in location) { + message += t('trigram') + ' ' + location.trigram; + } + + if ('uic' in location) { + message += t('uic') + ' ' + location.uic; + } + + return (message += secondaryCodeInfo + trackInfo); + }; + const secondaryCodeSuggestions = useMemo(() => { if (!isOpRefMetadata(pathStepMetadata)) return []; return [ @@ -134,20 +167,29 @@ const PathStepItem = ({ >
{index}
-
+
) : ( <> - option.label} - getOptionValue={(option) => option.id} - onChange={() => {}} - small - narrow - readOnly - /> +
+ option.label} + getOptionValue={(option) => option.id} + onChange={() => {}} + small + narrow + readOnly + /> +
)}
@@ -217,6 +271,9 @@ const PathStepItem = ({
+ {pathStepMetadata?.isInvalid && ( + {getInvalidMessage()} + )}
); }; diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/hooks/usePathStepsMetadata.ts b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/hooks/usePathStepsMetadata.ts index c1a00091d12..92a148b2270 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/hooks/usePathStepsMetadata.ts +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/Itinerary/hooks/usePathStepsMetadata.ts @@ -142,11 +142,22 @@ export const usePathStepsMetadata = (pathSteps: PathStepV2[]) => { if ('track' in location) { // TODO : replace the name by the track offset label when provided by backend const correspondingTrack = trackSectionsById[location.track]; - const coordinates = getPointOnTrackCoordinates( - correspondingTrack.geo, - correspondingTrack.length, - location.offset - ); + + const coordinates = correspondingTrack + ? getPointOnTrackCoordinates( + correspondingTrack.geo, + correspondingTrack.length, + location.offset + ) + : null; + + if (!correspondingTrack || !coordinates) { + // Can happen in case of track offset id does not exist in infra or + // if its offset is greater than the track length + newPathStepsMetadataById.set(pathStep.id, { isInvalid: true }); + return; + } + newPathStepsMetadataById.set(pathStep.id, { type: 'trackOffset', isInvalid: false, @@ -173,8 +184,20 @@ export const usePathStepsMetadata = (pathSteps: PathStepV2[]) => { ); }); - // If no op is found, it means the path step is invalid - if (!matchedOp) { + const trackRef = location.track_reference; + const isValidTrackReference = trackRef + ? matchedOp?.parts.some((part) => { + const track = trackSectionsById[part.track]; + if (!track) return false; + if ('track_name' in trackRef) { + return trackRef.track_name === track.extensions?.sncf?.track_name; + } + return trackRef.track_id === track.id; + }) + : true; + + // If no op is found or if its track_reference is invalid, it means the path step is invalid + if (!isValidTrackReference || !matchedOp) { newPathStepsMetadataById.set(pathStep.id, { isInvalid: true }); return; } diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/ManageTimetableItem.tsx b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/ManageTimetableItem.tsx index 0c0ed8f9cca..1367a0e502f 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/ManageTimetableItem.tsx +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/ManageTimetableItem.tsx @@ -173,7 +173,11 @@ const ManageTimetableItem = () => { simulationPathSteps={markersInformation} pathStepsAndSuggestedOPs={pathStepsAndSuggestedOPs} > - +
), diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/ManageTimetableItemMap/AddPathStepPopup.tsx b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/ManageTimetableItemMap/AddPathStepPopup.tsx index c671a6fd9c8..aec54a6da2b 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/ManageTimetableItemMap/AddPathStepPopup.tsx +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTimetableItem/ManageTimetableItemMap/AddPathStepPopup.tsx @@ -126,7 +126,7 @@ const AddPathStepPopup = ({ tracks[part.track]?.geo, tracks[part.track]?.length, part.position - ), + )!, })); trackPartCoordinates.unshift({ diff --git a/front/src/common/AlertBox.tsx b/front/src/common/AlertBox.tsx index 27e8e1f473b..68bf061d687 100644 --- a/front/src/common/AlertBox.tsx +++ b/front/src/common/AlertBox.tsx @@ -1,8 +1,8 @@ import { useState } from 'react'; -import { Alert, X } from '@osrd-project/ui-icons'; +import { Alert, Stop, X } from '@osrd-project/ui-icons'; -type AlertType = 'warning'; +type AlertType = 'warning' | 'error'; type AlertBoxProps = { type?: AlertType; @@ -11,7 +11,8 @@ type AlertBoxProps = { }; const iconByType = { - warning: , + warning: , + error: , }; const AlertBox = ({ type = 'warning', message, closeable }: AlertBoxProps) => { @@ -21,11 +22,15 @@ const AlertBox = ({ type = 'warning', message, closeable }: AlertBoxProps) => { if (!visible) return null; return ( -
+
{icon} - {message} + {message} {closeable && ( - )} diff --git a/front/src/modules/pathfinding/components/IncompatibleConstraints/IncompatibleConstraints.tsx b/front/src/modules/pathfinding/components/IncompatibleConstraints/IncompatibleConstraints.tsx index 6225ff3cb81..7680b863f01 100644 --- a/front/src/modules/pathfinding/components/IncompatibleConstraints/IncompatibleConstraints.tsx +++ b/front/src/modules/pathfinding/components/IncompatibleConstraints/IncompatibleConstraints.tsx @@ -10,7 +10,10 @@ import { isArray } from 'lodash'; import { useTranslation } from 'react-i18next'; import { useMap, type MapLayerMouseEvent } from 'react-map-gl/maplibre'; -import type { ManageTimetableItemPathProperties } from 'applications/operationalStudies/types'; +import type { + GeoJsonLineString, + IncompatibleConstraints as IncompatibleConstraintsType, +} from 'common/api/osrdEditoastApi'; import Collapsable from 'common/Collapsable'; import { getMapMouseEventNearestFeature } from 'utils/mapHelper'; @@ -26,10 +29,16 @@ import { import { getSegmentsConstraints, getSizeOfEnabledFilters } from './utils'; type IncompatibleConstraintsProps = { - pathProperties?: ManageTimetableItemPathProperties; + geometry?: GeoJsonLineString; + pathLength?: number; + incompatibleConstraints?: IncompatibleConstraintsType; }; -const IncompatibleConstraints = ({ pathProperties }: IncompatibleConstraintsProps) => { +const IncompatibleConstraints = ({ + geometry, + pathLength, + incompatibleConstraints, +}: IncompatibleConstraintsProps) => { const { t } = useTranslation('operational-studies', { keyPrefix: 'manageTimetableItem' }); const map = useMap(); @@ -130,10 +139,7 @@ const IncompatibleConstraints = ({ pathProperties }: IncompatibleConstraintsProp // When pathProperties changes // => reset state useEffect(() => { - const data = - pathProperties?.geometry && pathProperties?.incompatibleConstraints - ? pathProperties.incompatibleConstraints - : undefined; + const data = geometry && incompatibleConstraints ? incompatibleConstraints : undefined; const dataPairs = Object.entries(data || {}); @@ -147,10 +153,10 @@ const IncompatibleConstraints = ({ pathProperties }: IncompatibleConstraintsProp ); // compute distance ratio between data & geometry - const turfLength = pathProperties?.geometry - ? length(feature(pathProperties?.geometry as LineString), { units: 'millimeters' }) + const turfLength = geometry + ? length(feature(geometry as LineString), { units: 'millimeters' }) : 0; - const ratio = turfLength / (pathProperties?.length || turfLength); + const ratio = turfLength / (pathLength || turfLength); const nextConstraints = dataPairs .map(([key, value]) => value.map((e) => { @@ -162,14 +168,9 @@ const IncompatibleConstraints = ({ pathProperties }: IncompatibleConstraintsProp end: e.range.end * ratio, value: 'value' in e ? `${e.value}` : undefined, bbox: bbox( - lineSliceAlong( - pathProperties?.geometry as LineString, - e.range.start * ratio, - e.range.end * ratio, - { - units: 'millimeters', - } - ) + lineSliceAlong(geometry as LineString, e.range.start * ratio, e.range.end * ratio, { + units: 'millimeters', + }) ) as [number, number, number, number], }; }) @@ -179,7 +180,7 @@ const IncompatibleConstraints = ({ pathProperties }: IncompatibleConstraintsProp setTotal(nextConstraints.length); setSelectedConstraint(new Set([])); setHoveredConstraint(new Set([])); - }, [pathProperties?.incompatibleConstraints]); + }, [incompatibleConstraints]); const filteredConstraints = useMemo( () => constraints.filter((c) => filtersConstraintState[c.type]?.enabled), @@ -187,10 +188,10 @@ const IncompatibleConstraints = ({ pathProperties }: IncompatibleConstraintsProp ); const filteredGeojson = useMemo(() => { - if (!pathProperties?.geometry || filteredConstraints.length === 0) + if (!geometry || filteredConstraints.length === 0) return featureCollection([]); - return getSegmentsConstraints(pathProperties?.geometry as LineString, filteredConstraints); - }, [pathProperties?.geometry, filteredConstraints]); + return getSegmentsConstraints(geometry as LineString, filteredConstraints); + }, [geometry, filteredConstraints]); if (total === 0) return null; return ( @@ -237,7 +238,7 @@ const IncompatibleConstraints = ({ pathProperties }: IncompatibleConstraintsProp />
- {pathProperties?.geometry && ( + {geometry && ( { + const { t } = useTranslation('operational-studies', { keyPrefix: 'manageTimetableItem' }); + const { infraId } = useScenarioContext(); const { rollingStocks } = useRollingStockContext(); - const [pathProperties, setPathProperties] = useState(); + const [pathProperties, setPathProperties] = useState(); + const [pathfindingError, setPathfindingError] = useState(''); const [postPathfindingBlocks] = osrdEditoastApi.endpoints.postInfraByInfraIdPathfindingBlocks.useLazyQuery(); @@ -73,13 +78,59 @@ const usePathfindingV2 = () => { }; const pathPropertiesResult = await postPathProperties(pathPropertiesParams).unwrap(); - setPathProperties(pathPropertiesResult); + setPathProperties({ ...pathPropertiesResult, length: pathfindingResult.length }); + return; + } + + const incompatibleConstraintsCheck = + pathfindingResult.failed_status === 'pathfinding_not_found' && + pathfindingResult.error_type === 'incompatible_constraints'; + + if (incompatibleConstraintsCheck) { + const pathPropertiesParams: PostInfraByInfraIdPathPropertiesApiArg = { + infraId, + pathPropertiesInput: { + track_section_ranges: + pathfindingResult.relaxed_constraints_path.path.track_section_ranges, + }, + }; + const pathPropertiesResult = await postPathProperties(pathPropertiesParams).unwrap(); + + setPathProperties({ + ...pathPropertiesResult, + length: pathfindingResult.relaxed_constraints_path.length, + incompatibleConstraints: pathfindingResult.incompatible_constraints, + }); + setPathfindingError(t(`pathfindingErrors.${pathfindingResult.error_type}`)); + return; + } + + const hasInvalidPathItems = + pathfindingResult.failed_status === 'pathfinding_input_error' && + pathfindingResult.error_type === 'invalid_path_items'; + + if (hasInvalidPathItems) { + setPathfindingError(t('missingPathSteps')); + return; + } + + let error: string; + if (pathfindingResult.failed_status === 'internal_error') { + const translationKey = pathfindingResult.core_error.type.startsWith('core:') + ? pathfindingResult.core_error.type.replace('core:', '') + : pathfindingResult.core_error.type; + error = t(`coreErrors.${translationKey}`, { + defaultValue: pathfindingResult.core_error.message, + }); + } else { + error = t(`pathfindingErrors.${pathfindingResult.error_type}`); } + setPathfindingError(error); }, [infraId] ); - return { launchPathfindingV2, pathProperties }; + return { launchPathfindingV2, pathProperties, pathfindingError }; }; export default usePathfindingV2; diff --git a/front/src/modules/pathfinding/utils.ts b/front/src/modules/pathfinding/utils.ts index 971f5707625..f32b406292e 100644 --- a/front/src/modules/pathfinding/utils.ts +++ b/front/src/modules/pathfinding/utils.ts @@ -38,7 +38,7 @@ export const formatSuggestedOperationalPoints = ( offsetOnTrack: op.part.position, track: op.part.track, positionOnPath: op.position, - coordinates: getPointOnTrackCoordinates(geometry, pathLength, op.position), + coordinates: getPointOnTrackCoordinates(geometry, pathLength, op.position)!, metadata: op?.metadata, })); diff --git a/front/src/styles/scss/applications/operationalStudies/_itineraryModal.scss b/front/src/styles/scss/applications/operationalStudies/_itineraryModal.scss index 3cb8942ecd3..c593059bf14 100644 --- a/front/src/styles/scss/applications/operationalStudies/_itineraryModal.scss +++ b/front/src/styles/scss/applications/operationalStudies/_itineraryModal.scss @@ -3,6 +3,7 @@ --footer-height: 96px; --footer-box-shadow-height: 1px; --path-step-row-gap: 8px; + --path-step-wrapper-padding-left: 12px; --path-step-wrapper-padding-right: 10px; --path-step-secondary-code-width: 60px; --path-step-track-name-width: 76px; @@ -148,7 +149,8 @@ // this wrapper is being used to display the "adding location on map" style .path-step-wrapper { - padding-inline: 12px var(--path-step-wrapper-padding-right); + padding-inline: var(--path-step-wrapper-padding-left) + var(--path-step-wrapper-padding-right); border-radius: 6px; padding-bottom: 4px; @@ -223,10 +225,28 @@ top: -2px; // relative element border width } } + + &.invalid { + background-color: var(--error60); + border-color: var(--error30); + } } .path-step-op-name { margin-left: 4px; + + &.invalid { + border-radius: 3px; + box-shadow: 0 0 0 1.5px rgba(255, 104, 104, 1); + } + } + + .secondary-code, + .track-name { + &.invalid { + border-radius: 4px; + box-shadow: 0 0 0 1.5px rgba(255, 104, 104, 1); + } } .requested-point-block { @@ -260,6 +280,11 @@ } } } + + .invalid-step-message { + color: var(--error60); + padding-left: calc(51px - var(--path-step-wrapper-padding-left)); + } } } } diff --git a/front/src/styles/scss/common/_components.scss b/front/src/styles/scss/common/_components.scss index 3d5617fac02..f76b4e02259 100644 --- a/front/src/styles/scss/common/_components.scss +++ b/front/src/styles/scss/common/_components.scss @@ -1,3 +1,4 @@ +@use 'components/alertBox'; @use 'components/debouncedNumberInput'; @use 'components/infraLoadingState'; @use 'components/intervalsEditor'; diff --git a/front/src/styles/scss/common/components/_alertBox.scss b/front/src/styles/scss/common/components/_alertBox.scss new file mode 100644 index 00000000000..624166617de --- /dev/null +++ b/front/src/styles/scss/common/components/_alertBox.scss @@ -0,0 +1,56 @@ +.alert-box { + width: 100%; + min-height: 56px; + display: flex; + align-items: center; + padding: 0 24px 0 40px; + border-top: 1px solid; + border-bottom: 1px solid; + margin-top: -1px; + margin-bottom: 16px; + border-color: var(--black10); + + &.warning { + background-color: var(--warning5); + } + + &.error { + background-color: var(--error5); + } + + .alert-box-icon { + margin-right: 12px; + width: 24px; + height: 24px; + + &.warning { + color: var(--warning30); + } + + &.error { + color: var(--error30); + } + } + + .alert-box-text { + font-family: 'IBM Plex Sans'; + font-size: 16px; + font-weight: 400; + + &.warning { + color: var(--warning60); + } + + &.error { + color: var(--error60); + } + } + + .alert-box-close-button { + margin-left: auto; + + &.warning { + color: var(--warning60); + } + } +} diff --git a/front/src/utils/geometry.ts b/front/src/utils/geometry.ts index aaf999b32ba..274d7b6fb1e 100644 --- a/front/src/utils/geometry.ts +++ b/front/src/utils/geometry.ts @@ -151,10 +151,15 @@ export function getPointOnTrackCoordinates( geometry: GeoJsonLineString, trackLength: number, // in mm infraPositionOnTrack: number // in mm -): Position { +): Position | null { const pathLineString = lineString(geometry.coordinates); const geometryTrackLength = length(pathLineString, { units: 'millimeters' }); const infraTrackLength = trackLength; + + // Should only happen when importing an json that has been manually altered + if (infraPositionOnTrack > trackLength) { + return null; + } // TODO TS2 : when adapting train update check that this computation works properly const geometryDistanceAlongTrack = infraPositionOnTrack * (geometryTrackLength / infraTrackLength);