diff --git a/front/public/locales/en/stdcm-simulation-report-sheet.json b/front/public/locales/en/stdcm-simulation-report-sheet.json index 19809495bd0..17f66a428f6 100644 --- a/front/public/locales/en/stdcm-simulation-report-sheet.json +++ b/front/public/locales/en/stdcm-simulation-report-sheet.json @@ -6,10 +6,12 @@ "convoy": "convoy", "crossedATE": "crossed ATE", "endStop": "end", + "stopTime": "stop time", "for": "for", "formattedDate": "{{year}}/{{month}}/{{day}} at {{hours}}:{{minutes}}", "formattedDateScenario": "Simulation sheet generated on {{day}}/{{month}}/{{year}} at {{hours}}:{{minutes}}", "from": "from", + "length": "Length", "maxLength": "max. length", "maxSpeed": "max. speed", "maxWeight": "max. weight", @@ -17,7 +19,7 @@ "passageStop": "passage", "refEngine": "Ref. engine", "referenceEngine": "reference engine", - "referencePath": "Reference path", + "simulationStopType": "Type d'arrêt", "requestedRoute": "requested route", "scheduledArrival": "scheduled arrival on {{date}} at {{time}}", "scheduledDeparture": "scheduled departure on {{date}} at {{time}}", @@ -25,7 +27,7 @@ "simulation": "Simulation", "speedLimitByTag": "speed limit by tag", "startStop": "start", - "stdcm": "ST DCM", + "stdcm": "LMR", "stdcmCreation": "short term path creation", "stopType": "motif", "towedMaterial": "towed material", diff --git a/front/public/locales/fr/stdcm-simulation-report-sheet.json b/front/public/locales/fr/stdcm-simulation-report-sheet.json index 1eeda988b25..212271814aa 100644 --- a/front/public/locales/fr/stdcm-simulation-report-sheet.json +++ b/front/public/locales/fr/stdcm-simulation-report-sheet.json @@ -6,10 +6,12 @@ "convoy": "convoi", "crossedATE": "ATE croisé", "endStop": "arrivée", + "stopTime": "arrêt", "for": "pour", "formattedDate": "le {{day}}/{{month}}/{{year}} à {{hours}}:{{minutes}}", "formattedDateScenario": "Fiche de simulation générée le {{day}}/{{month}}/{{year}} à {{hours}}:{{minutes}}", "from": "du", + "length": "Longueur", "maxLength": "longueur max.", "maxSpeed": "vitesse max.", "maxWeight": "tonnage max.", @@ -17,7 +19,7 @@ "passageStop": "passage", "refEngine": "Engin de réf.", "referenceEngine": "engin de référence", - "referencePath": "Sillon de référence", + "simulationStopType": "Type d'arrêt", "requestedRoute": "parcours demandé", "scheduledArrival": "arrivée prévue le {{date}} à {{time}}", "scheduledDeparture": "départ prévu le {{date}} à {{time}}", @@ -25,7 +27,7 @@ "simulation": "Simulation", "speedLimitByTag": "code de composition", "startStop": "départ", - "stdcm": "ST DCM", + "stdcm": "LMR", "stdcmCreation": "Création de sillon de dernière minute", "stopType": "motif", "towedMaterial": "matériel remorqué", diff --git a/front/src/applications/stdcm/components/StdcmResults/SimulationReportSheet.tsx b/front/src/applications/stdcm/components/StdcmResults/SimulationReportSheet.tsx index 15b7dbfb708..6da532c26c7 100644 --- a/front/src/applications/stdcm/components/StdcmResults/SimulationReportSheet.tsx +++ b/front/src/applications/stdcm/components/StdcmResults/SimulationReportSheet.tsx @@ -1,5 +1,5 @@ import { Table, TR, TH, TD } from '@ag-media/react-pdf-table'; -import { Page, Text, Image, Document, View, Link } from '@react-pdf/renderer'; +import { Page, Text, Image, Document, View } from '@react-pdf/renderer'; import type { TFunction } from 'i18next'; import { useTranslation } from 'react-i18next'; @@ -10,6 +10,7 @@ import type { StdcmPathStep } from 'reducers/osrdconf/types'; import { dateToHHMMSS, formatDateToString, formatDay } from 'utils/date'; import { msToKmh } from 'utils/physics'; import { capitalizeFirstLetter } from 'utils/strings'; +import { secToMin } from 'utils/timeManipulation'; import styles from './SimulationReportStyleSheet'; import type { SimulationReportSheetProps } from '../../types'; @@ -24,8 +25,8 @@ const getStopType = (step: StdcmPathStep, t: TFunction) => { return capitalizeFirstLetter(t(`stdcm:trainPath.stopType.${step.stopType}`)); }; -const getArrivalTime = (step: StdcmPathStep, t: TFunction) => { - if (!step.isVia) { +const getArrivalTimes = (step: StdcmPathStep, t: TFunction, shouldDisplay: boolean) => { + if (shouldDisplay && !step.isVia) { if (step.arrival && step.arrivalType === 'preciseTime') { return dateToHHMMSS(step.arrival, { withoutSeconds: true }); } @@ -51,16 +52,6 @@ const SimulationReportSheet = ({ const convoyLength = consist?.totalLength ?? rollingStock.length; const convoyMaxSpeed = consist?.maxSpeed ?? msToKmh(rollingStock.max_speed); - // TODO: Add RC information when it becomes avalaible, until that, we use fake ones - const fakeInformation = { - rcName: 'Super Fret', - rcPersonName: 'Jane Smith', - rcPhoneNumber: '01 23 45 67 89', - rcMail: 'john.doe@example.com', - path_number1: 'n°XXXXXX', - path_number2: 'n°YYYYYY', - }; - return ( @@ -89,20 +80,11 @@ const SimulationReportSheet = ({ - - {fakeInformation.rcName} - {fakeInformation.rcPersonName} - - - {fakeInformation.rcPhoneNumber} - {fakeInformation.rcMail} - + {t('applicationDate')} {formatDay(departureTime, i18n.language)} - {t('referencePath')} - {fakeInformation.path_number1} @@ -136,19 +118,18 @@ const SimulationReportSheet = ({ {t('requestedRoute')} - {/* TODO: Add path number and date from reference path when it becomes avalaible */} - - - {t('from')} + {anteriorTrain && ( + + + {t('from')} + + {anteriorTrain.trainName} + + {anteriorTrain && + t('scheduledArrival', { date: anteriorTrain.date, time: anteriorTrain.time })} + - - {anteriorTrain?.trainName || fakeInformation.path_number1} - - - {anteriorTrain && - t('scheduledArrival', { date: anteriorTrain.date, time: anteriorTrain.time })} - - + )} + + + @@ -173,6 +157,8 @@ const SimulationReportSheet = ({ {stdcmData.simulationPathSteps.map((step, index) => { renderedIndex += 1; + const isFirstStep = index === 0; + const isLastStep = index === stdcmData.simulationPathSteps.length - 1; return ( @@ -189,13 +175,61 @@ const SimulationReportSheet = ({ - + + + - @@ -208,31 +242,25 @@ const SimulationReportSheet = ({ })}
@@ -164,6 +145,9 @@ const SimulationReportSheet = ({ {t('endStop')} {t('stopTime')} {t('startStop')}
- {getArrivalTime(step, t)} + + + {getArrivalTimes(step, t, isLastStep)} + + {isLastStep && !step.isVia && step.arrivalType === 'preciseTime' && ( + + + {step.tolerances?.before + ? `+${secToMin(step.tolerances?.before)}` + : ''} + + + {step.tolerances?.after + ? `-${secToMin(step.tolerances?.after)}` + : ''} + + + )} + + {step.isVia && step.stopFor ? `${step.stopFor} min` : ''} - {getArrivalTime(step, t)} + + + {getArrivalTimes(step, t, isFirstStep)} + + {isFirstStep && + !step.isVia && + step.tolerances && + step.arrivalType === 'preciseTime' && ( + + + {`+${secToMin(step.tolerances.before)}`} + + + {`-${secToMin(step.tolerances.after)}`} + + + )}
- {/* TODO: Add path number and date from reference path when it becomes avalaible */} - - - {posteriorTrain && - t('scheduledDeparture', { date: posteriorTrain.date, time: posteriorTrain.time })} - - - {posteriorTrain?.trainName || fakeInformation.path_number2} - - - {t('for')} + {posteriorTrain && ( + + + {t('scheduledDeparture', { + date: posteriorTrain.date, + time: posteriorTrain.time, + })} + + {posteriorTrain.trainName} + + {t('for')} + - + )}
{t('simulation')} - - {t('viewSimulation')} - {`${Math.round(stdcmData.path.length / 1000000)} km`} @@ -264,14 +292,14 @@ const SimulationReportSheet = ({ {t('weight')} + + {t('length')} + {t('referenceEngine')} - - {t('conventionalSign')} - - - {t('crossedATE')} + + {t('simulationStopType')} {operationalPointsList.map((step, index) => { @@ -373,16 +401,22 @@ const SimulationReportSheet = ({ {!isFirstStep ? '=' : `${Math.floor(convoyMass)} t`} + + {!isFirstStep ? '=' : `${convoyLength} m`} + {!isFirstStep ? '=' : rollingStock.metadata?.reference} - - - - - + + {(isFirstStep || isLastStep || step.stopType) && ( + + {isFirstStep || isLastStep + ? t('serviceStop') + : capitalizeFirstLetter(t(`stdcm:trainPath.stopType.${step.stopType}`))} + + )} ); diff --git a/front/src/applications/stdcm/components/StdcmResults/SimulationReportStyleSheet.ts b/front/src/applications/stdcm/components/StdcmResults/SimulationReportStyleSheet.ts index 387f173c9f4..fcf033b9303 100644 --- a/front/src/applications/stdcm/components/StdcmResults/SimulationReportStyleSheet.ts +++ b/front/src/applications/stdcm/components/StdcmResults/SimulationReportStyleSheet.ts @@ -140,24 +140,6 @@ const styles = { color: '#005BC1', letterSpacing: '-0.22', }, - rcPersonName: { - fontSize: '16', - marginLeft: '40', - color: '#005BC1', - letterSpacing: '-0.2', - }, - rcPhoneNumber: { - fontSize: '18', - marginTop: '22', - marginBottom: '3', - color: '#005BC1', - letterSpacing: '-0.22', - }, - rcMail: { - fontSize: '16', - color: '#005BC1', - letterSpacing: '-0.2', - }, stdcmApplication: { marginTop: '25', }, @@ -170,10 +152,6 @@ const styles = { fontWeight: 'semibold', marginBottom: '25', }, - referencePath: { - fontSize: '14', - color: '#797671', - }, pathNumber: { fontSize: '24', marginBottom: '29', @@ -295,12 +273,35 @@ const styles = { stopTableChWidth: { width: '112', }, + stopForWidth: { + width: '112', + }, stopTableEndWidth: { width: '118', }, stopTableStartWidth: { width: '168', }, + tolerancesWidth: { + width: '25', + height: '25', + marginLeft: '5', + marginBottom: '3', + }, + stopTolerancesView: { + width: '168', + fontSize: '14', + lineHeight: '14', + marginLeft: '25', + }, + tolerancesText: { + fontSize: '14', + }, + stopForText: { + fontSize: '14', + fontWeight: 'semibold', + textAlign: 'center', + }, stopTableStopTypeWidth: { width: '216', }, @@ -466,7 +467,13 @@ const styles = { weightWidth: { width: '96', }, + length: { + width: '96', + }, refEngineWidth: { + width: '145', + }, + stopType: { width: '155', }, convSignWidth: { diff --git a/front/src/applications/stdcm/hooks/useStdcmResults.ts b/front/src/applications/stdcm/hooks/useStdcmResults.ts index 3df45dfa60a..0efca49e639 100644 --- a/front/src/applications/stdcm/hooks/useStdcmResults.ts +++ b/front/src/applications/stdcm/hooks/useStdcmResults.ts @@ -2,6 +2,8 @@ import { useEffect, useMemo } from 'react'; import { useSelector } from 'react-redux'; +import { getEntities } from 'applications/editor/data/api'; +import type { TrackSectionEntity } from 'applications/editor/tools/trackEdition/types'; import type { StdcmPathProperties, StdcmResponse } from 'applications/stdcm/types'; import { osrdEditoastApi, @@ -14,6 +16,7 @@ import { formatSuggestedOperationalPoints } from 'modules/pathfinding/utils'; import useSpeedSpaceChart from 'modules/simulationResult/components/SpeedSpaceChart/useSpeedSpaceChart'; import type { SuggestedOP } from 'modules/trainschedule/components/ManageTrainSchedule/types'; import { getSelectedTrainId } from 'reducers/simulationResults/selectors'; +import { useAppDispatch } from 'store'; import { STDCM_TRAIN_ID } from '../consts'; @@ -24,6 +27,7 @@ const useStdcmResults = ( ) => { const infraId = useInfraID(); const selectedTrainId = useSelector(getSelectedTrainId); + const dispatch = useAppDispatch(); const [postPathProperties] = osrdEditoastApi.endpoints.postInfraByInfraIdPathProperties.useLazyQuery(); @@ -67,23 +71,52 @@ const useStdcmResults = ( await postPathProperties(pathPropertiesParams).unwrap(); if (geometry && operational_points && zones) { - const operationalPointsWithUniqueIds = operational_points.map((op, index) => ({ - ...op, - id: `${op.id}-${op.position}-${index}`, - })); - - const suggestedOperationalPoints: SuggestedOP[] = formatSuggestedOperationalPoints( - operational_points, - geometry, - path.length - ); - - setPathProperties({ - manchetteOperationalPoints: operationalPointsWithUniqueIds, - geometry, - suggestedOperationalPoints, - zones, - }); + if (infraId) { + const trackIds = operational_points.map((op) => op.part.track); + const trackSections = await getEntities( + infraId, + trackIds, + 'TrackSection', + dispatch + ); + const operationalPointsWithMetadata = operational_points.map((op) => { + const associatedTrackSection = trackSections[op.part.track]; + const sncf = associatedTrackSection?.properties?.extensions?.sncf; + + const metadata = + sncf && Object.values(sncf).every((value) => value !== undefined) + ? { + lineCode: sncf.line_code!, + lineName: sncf.line_name!, + trackName: sncf.track_name!, + trackNumber: sncf.track_number!, + } + : undefined; + + return { + ...op, + metadata, + }; + }); + + const operationalPointsWithUniqueIds = operational_points.map((op, index) => ({ + ...op, + id: `${op.id}-${op.position}-${index}`, + })); + + const suggestedOperationalPoints: SuggestedOP[] = formatSuggestedOperationalPoints( + operationalPointsWithMetadata, // Pass the operational points with metadata + geometry, + path.length + ); + + setPathProperties({ + manchetteOperationalPoints: operationalPointsWithUniqueIds, + geometry, + suggestedOperationalPoints, + zones, + }); + } } }; diff --git a/front/src/applications/stdcm/types.ts b/front/src/applications/stdcm/types.ts index 31de952c32c..b674f446072 100644 --- a/front/src/applications/stdcm/types.ts +++ b/front/src/applications/stdcm/types.ts @@ -57,6 +57,7 @@ export type SimulationReportSheetProps = { consist: StdcmSimulationInputs['consist']; simulationReportSheetNumber: string; operationalPointsList: StdcmResultsOperationalPoint[]; + userName?: string; }; export type StdcmResultsOperationalPoint = { @@ -69,6 +70,7 @@ export type StdcmResultsOperationalPoint = { duration: number; stopEndTime: string; trackName?: string; + stopType?: string; }; export type ConsistErrors = { diff --git a/front/src/applications/stdcm/utils/formatSimulationReportSheet.ts b/front/src/applications/stdcm/utils/formatSimulationReportSheet.ts index 778ab44690b..3a94f305583 100644 --- a/front/src/applications/stdcm/utils/formatSimulationReportSheet.ts +++ b/front/src/applications/stdcm/utils/formatSimulationReportSheet.ts @@ -1,4 +1,5 @@ import type { SimulationResponse } from 'common/api/osrdEditoastApi'; +import { matchPathStepAndOp } from 'modules/pathfinding/utils'; import { interpolateValue } from 'modules/simulationResult/SimulationResultExport/utils'; import type { SuggestedOP } from 'modules/trainschedule/components/ManageTrainSchedule/types'; import type { StdcmPathStep } from 'reducers/osrdconf/types'; @@ -135,6 +136,16 @@ export function getOperationalPointsWithTimes( const durationToString = secondsToTimeString(durationInSeconds); const stopEndTime = computeStopDepartureTime(formattedTime, durationToString); + // Find the corresponding stopType from pathSteps + const correspondingStep = simulationPathSteps.find( + (step) => step.location && matchPathStepAndOp(step.location, op) + ); + let stopType; + if (correspondingStep) { + stopType = !correspondingStep.isVia ? 'serviceStop' : correspondingStep.stopType; + } + const stopFor = correspondingStep?.isVia ? correspondingStep.stopFor : undefined; + return { opId: op.opId!, positionOnPath: op.positionOnPath, @@ -144,6 +155,8 @@ export function getOperationalPointsWithTimes( duration: durationInSeconds, stopEndTime, trackName: op.metadata?.trackName, + stopType, + stopFor, }; }); diff --git a/front/src/modules/pathfinding/utils.ts b/front/src/modules/pathfinding/utils.ts index d8b6b9e377b..0ede09223d5 100644 --- a/front/src/modules/pathfinding/utils.ts +++ b/front/src/modules/pathfinding/utils.ts @@ -17,7 +17,11 @@ import { getPointCoordinates } from 'utils/geometry'; import getStepLocation from './helpers/getStepLocation'; export const formatSuggestedOperationalPoints = ( - operationalPoints: NonNullable>, + operationalPoints: Array< + NonNullable>[number] & { + metadata?: NonNullable; + } + >, geometry: GeoJsonLineString, pathLength: number ): SuggestedOP[] => @@ -32,10 +36,11 @@ export const formatSuggestedOperationalPoints = ( track: op.part.track, positionOnPath: op.position, coordinates: getPointCoordinates(geometry, pathLength, op.position), + metadata: op?.metadata, })); export const matchPathStepAndOp = ( - step: PathStep, + step: PathItemLocation, op: Pick ) => { if ('operational_point' in step) { diff --git a/front/src/reducers/osrdconf/stdcmConf/index.ts b/front/src/reducers/osrdconf/stdcmConf/index.ts index 503e169ae7a..3d707711c15 100644 --- a/front/src/reducers/osrdconf/stdcmConf/index.ts +++ b/front/src/reducers/osrdconf/stdcmConf/index.ts @@ -59,6 +59,7 @@ export const stdcmConfSlice = createSlice({ state.totalMass = stdcmConfInitialState.totalMass; state.maxSpeed = stdcmConfInitialState.maxSpeed; state.speedLimitByTag = stdcmConfInitialState.speedLimitByTag; + state.linkedTrains = stdcmConfInitialState.linkedTrains; }, updateTotalMass( state: Draft,