diff --git a/front/public/locales/en/stdcm-simulation-report-sheet.json b/front/public/locales/en/stdcm-simulation-report-sheet.json index d8f2c8e3338..700b7163fc6 100644 --- a/front/public/locales/en/stdcm-simulation-report-sheet.json +++ b/front/public/locales/en/stdcm-simulation-report-sheet.json @@ -5,10 +5,14 @@ "conventionalSign": "conv. sign", "convoy": "convoy", "crossedATE": "crossed ATE", + "displayAll": "Display all operational points", + "displayMain": "Display main operational points", + "downloadSimulationSheet": "Download simulation report sheet", "endStop": "end", "for": "for", "formattedDate": "{{year}}/{{month}}/{{day}} at {{hours}}:{{minutes}}", "from": "from", + "gesicoRequest":"and attach this document to your GESICO DSDM request.", "maxLength": "max. length", "maxSpeed": "max. speed", "maxWeight": "max. weight", @@ -21,8 +25,10 @@ "requestedRoute": "requested route", "scheduledArrival": "scheduled arrival at", "scheduledDeparture": "scheduled departure at", + "selectThisSimulation": "Select this simulation", "serviceStop": "Service stop", "simulation": "Simulation", + "simulationSelected": "You have selected this simulation", "speedLimitByTag": "speed limit by tag", "startStop": "start", "stdcm": "ST DCM", diff --git a/front/public/locales/en/stdcm.json b/front/public/locales/en/stdcm.json index 5d7b99b30c8..a2255520d14 100644 --- a/front/public/locales/en/stdcm.json +++ b/front/public/locales/en/stdcm.json @@ -5,6 +5,7 @@ "consist": "Consist", "tractionEngine": "Traction engine" }, + "formattedCreationDate": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}", "loaderImageLegend": "The TGV Nord line", "notificationTitle": "Phase 1: from D-7 to D-1 5pm, on the Perrigny-Miramas axis.", "pleaseWait": "Please wait…", diff --git a/front/public/locales/fr/stdcm-simulation-report-sheet.json b/front/public/locales/fr/stdcm-simulation-report-sheet.json index 619b7e240be..5e71c28ec3e 100644 --- a/front/public/locales/fr/stdcm-simulation-report-sheet.json +++ b/front/public/locales/fr/stdcm-simulation-report-sheet.json @@ -5,10 +5,14 @@ "conventionalSign": "signe conv.", "convoy": "convoi", "crossedATE": "ATE croisé", + "displayAll": "Afficher tous les jalons", + "displayMain": "Afficher les jalons principaux", + "downloadSimulationSheet": "Télécharger la fiche de simulation", "endStop": "arrivée", "for": "pour", "formattedDate": "le {{day}}/{{month}}/{{year}} à {{hours}}:{{minutes}}", "from": "du", + "gesicoRequest":"et joignez ce document à votre demande GESICO DSDM", "maxLength": "longueur max.", "maxSpeed": "vitesse max.", "maxWeight": "tonnage max.", @@ -21,8 +25,10 @@ "requestedRoute": "parcours demandé", "scheduledArrival": "arrivée prévue à", "scheduledDeparture": "départ prévu à", + "selectThisSimulation": "Retenir cette simulation", "serviceStop": "Arrêt de service", "simulation": "Simulation", + "simulationSelected": "Vous avez retenu cette simulation", "speedLimitByTag": "code de composition", "startStop":"départ", "stdcm": "ST DCM", diff --git a/front/public/locales/fr/stdcm.json b/front/public/locales/fr/stdcm.json index e5382a517f2..137d57a90b2 100644 --- a/front/public/locales/fr/stdcm.json +++ b/front/public/locales/fr/stdcm.json @@ -5,6 +5,7 @@ "consist": "Convoi", "tractionEngine": "Engin de traction" }, + "formattedCreationDate": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}", "loaderImageLegend": "La ligne TGV Nord", "notificationTitle": "Phase 1 : de J-7 à J-1 17h, sur l’axe Perrigny—Miramas.", "pleaseWait": "Veuillez patientez…", diff --git a/front/src/applications/stdcm/views/StdcmConfig.tsx b/front/src/applications/stdcm/views/StdcmConfig.tsx index cf708ad43f1..550b2d74e40 100644 --- a/front/src/applications/stdcm/views/StdcmConfig.tsx +++ b/front/src/applications/stdcm/views/StdcmConfig.tsx @@ -194,7 +194,11 @@ const StdcmConfig = ({ : 'stdcm-map-noSimulation' }`} > - + )} diff --git a/front/src/applications/stdcm/views/StdcmResultsV2.tsx b/front/src/applications/stdcm/views/StdcmResultsV2.tsx index af2fb6109be..fe544fe5fa6 100644 --- a/front/src/applications/stdcm/views/StdcmResultsV2.tsx +++ b/front/src/applications/stdcm/views/StdcmResultsV2.tsx @@ -20,7 +20,7 @@ type StcdmResultsProps = { creationDate?: Date; }; -const codeNumber = generateCodeNumber(); +const simulationReportSheetNumber = generateCodeNumber(); // TODO TS2 : Adapt StdcmResult to trainSchedule v2 (SpaceTimeChart and SpeedSpaceChart) @@ -46,12 +46,12 @@ const StcdmResultsV2 = ({ pathProperties={pathProperties} rollingStockData={rollingStockData} speedLimitByTag={speedLimitByTag} - simulationReportSheetNumber={codeNumber} + simulationReportSheetNumber={simulationReportSheetNumber} mapCanvas={mapCanvas} creationDate={creationDate} /> } - fileName={`STDCM-${codeNumber}.pdf`} + fileName={`STDCM-${simulationReportSheetNumber}.pdf`} > {t('stdcm:stdcmSimulationReport')} diff --git a/front/src/applications/stdcmV2/components/StdcmResults.tsx b/front/src/applications/stdcmV2/components/StdcmResults.tsx new file mode 100644 index 00000000000..d979175d7a1 --- /dev/null +++ b/front/src/applications/stdcmV2/components/StdcmResults.tsx @@ -0,0 +1,81 @@ +import React, { useState } from 'react'; + +import { CheckCircle } from '@osrd-project/ui-icons'; +import { useTranslation } from 'react-i18next'; + +import type { ManageTrainSchedulePathProperties } from 'applications/operationalStudies/types'; +import type { StdcmV2SuccessResponse } from 'applications/stdcm/types'; +import { generateCodeNumber } from 'applications/stdcm/utils'; +import type { RollingStockWithLiveries } from 'common/api/osrdEditoastApi'; +import { Map } from 'modules/trainschedule/components/ManageTrainSchedule'; +import { formatDateToString } from 'utils/date'; + +import StdcmTableResults from './StdcmTableResults'; + +type StcdmResultsV2Props = { + stdcmData: StdcmV2SuccessResponse; + pathProperties?: ManageTrainSchedulePathProperties; + rollingStockData: RollingStockWithLiveries; + speedLimitByTag?: string; + currentStdcmRequestStatus?: string; + creationDate?: Date; +}; + +const StcdmViewResultsV2 = ({ + stdcmData, + pathProperties, + rollingStockData, + speedLimitByTag, + currentStdcmRequestStatus, + creationDate, +}: StcdmResultsV2Props) => { + const { t } = useTranslation('stdcm'); + const date = creationDate && t('formattedCreationDate', formatDateToString(creationDate)); + + const [isSimulationSelected, setIsSimulationSelected] = useState(false); + + const [mapCanvas, setMapCanvas] = useState(); + + const simulationReportSheetNumber = generateCodeNumber(); + + return ( +
+
+
+ Simulation n°1 + {isSimulationSelected && ( +
+ +
+ )} +
+
{date}
+
+
+ +
+ +
+
+
+ ); +}; + +export default StcdmViewResultsV2; diff --git a/front/src/applications/stdcmV2/components/StdcmTableResults.tsx b/front/src/applications/stdcmV2/components/StdcmTableResults.tsx new file mode 100644 index 00000000000..b2fd838f5a7 --- /dev/null +++ b/front/src/applications/stdcmV2/components/StdcmTableResults.tsx @@ -0,0 +1,161 @@ +import React, { useState } from 'react'; + +import { Button } from '@osrd-project/ui-core'; +import { PDFDownloadLink } from '@react-pdf/renderer'; +import { useTranslation } from 'react-i18next'; + +import type { ManageTrainSchedulePathProperties } from 'applications/operationalStudies/types'; +import SimulationReportSheetV2 from 'applications/stdcm/components/SimulationReportSheetV2'; +import type { SimulationReportSheetProps, StdcmV2SuccessResponse } from 'applications/stdcm/types'; +import { getOperationalPointsWithTimes } from 'applications/stdcm/utils'; +import type { RollingStockWithLiveries } from 'common/api/osrdEditoastApi'; + +type SimulationTableProps = { + stdcmData: StdcmV2SuccessResponse; + pathProperties?: ManageTrainSchedulePathProperties; + rollingStockData: RollingStockWithLiveries; + speedLimitByTag?: string; + creationDate?: Date; + simulationReportSheetNumber: string; + mapCanvas?: string; + setIsSimulationSelected?: (simulationSelected: boolean) => void; +}; + +const StcdmTableResults = ({ + stdcmData, + pathProperties, + rollingStockData, + speedLimitByTag, + creationDate, + simulationReportSheetNumber, + mapCanvas, + setIsSimulationSelected, +}: SimulationTableProps) => { + const { t } = useTranslation('stdcm-simulation-report-sheet'); + + const simulationReport: SimulationReportSheetProps = { + stdcmData, + pathProperties, + rollingStockData, + simulationReportSheetNumber, + speedLimitByTag, + creationDate, + }; + + const opList = getOperationalPointsWithTimes(simulationReport); + + const [showAllPR, setShowAllPR] = useState(false); + + const [isSimulation, setIsSimulation] = useState(false); + + const handleSimulationClick = () => { + if (setIsSimulationSelected) { + setIsSimulation(true); + setIsSimulationSelected(true); + } + }; + + const handleShowAllClick = () => { + setShowAllPR((prevState) => !prevState); + }; + + return ( +
+ + + + + + + + + + + + {opList.map((step, index) => { + const isFirstStep = index === 0; + const isLastStep = index === opList.length - 1; + const prevStep = opList[index - 1]; + const shouldRenderRow = isFirstStep || /* step.stop > '0' || */ isLastStep; + if (showAllPR || shouldRenderRow) { + return ( + + + + + + + + + + + ); + } + return null; + })} + +
+ {t('operationalPoint')}{t('code')}{t('endStop')}{t('passageStop')}{t('startStop')}{t('weight')}{t('refEngine')}
+ {index + 1} + + {!isFirstStep && !isLastStep && step.name === prevStep.name + ? '=' + : step.name || 'Unknown'} + {step.ch}{isLastStep ? step.time : ''}{isFirstStep || isLastStep ? '' : step.time}{isFirstStep ? step.departureTime : ''} + {!isFirstStep && !isLastStep + ? '=' + : `${Math.floor(rollingStockData.mass / 1000)} t`} + + {!isFirstStep && !isLastStep ? '=' : rollingStockData.metadata?.reference} +
+
+
+
+
+ {!isSimulation ? ( +
+
+ {isSimulation && ( +
+
+ + } + fileName={`STDCM-${simulationReportSheetNumber}.pdf`} + > +
+
{t('gesicoRequest')}
+
+ )} +
+ ); +}; + +export default StcdmTableResults; diff --git a/front/src/applications/stdcmV2/views/StdcmViewV2.tsx b/front/src/applications/stdcmV2/views/StdcmViewV2.tsx index 516792f591b..d2d14ce3570 100644 --- a/front/src/applications/stdcmV2/views/StdcmViewV2.tsx +++ b/front/src/applications/stdcmV2/views/StdcmViewV2.tsx @@ -1,15 +1,27 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useState } from 'react'; import { Button } from '@osrd-project/ui-core'; // import { Location, ArrowUp, ArrowDown } from '@osrd-project/ui-icons'; +import { compact } from 'lodash'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; +import type { ManageTrainSchedulePathProperties } from 'applications/operationalStudies/types'; import STDCM_REQUEST_STATUS from 'applications/stdcm/consts'; import useStdcm from 'applications/stdcm/hooks/useStdcm'; -import { useOsrdConfActions, useOsrdConfSelectors } from 'common/osrdContext'; +import { osrdEditoastApi } from 'common/api/osrdEditoastApi'; +import type { + PathfindingResultSuccess, + PostV2InfraByInfraIdPathPropertiesApiArg, +} from 'common/api/osrdEditoastApi'; +import { useInfraID, useOsrdConfActions, useOsrdConfSelectors } from 'common/osrdContext'; +import { useStoreDataForSpeedLimitByTagSelector } from 'common/SpeedLimitByTagSelector/useStoreDataForSpeedLimitByTagSelector'; +import { formatSuggestedOperationalPoints, upsertViasInOPs } from 'modules/pathfinding/utils'; +import { useStoreDataForRollingStockSelector } from 'modules/rollingStock/components/RollingStockSelector/useStoreDataForRollingStockSelector'; import { Map } from 'modules/trainschedule/components/ManageTrainSchedule'; +import type { SuggestedOP } from 'modules/trainschedule/components/ManageTrainSchedule/types'; import type { StdcmConfSliceActions } from 'reducers/osrdconf/stdcmConf'; +import { getTrainScheduleV2Activated } from 'reducers/user/userSelectors'; import { useAppDispatch } from 'store'; import StdcmConsist from '../components/StdcmConsist'; @@ -18,19 +30,84 @@ import StdcmDestination from '../components/StdcmDestination'; import StdcmHeader from '../components/StdcmHeader'; import StdcmLoader from '../components/StdcmLoader'; import StdcmOrigin from '../components/StdcmOrigin'; +import StdcmViewResultsV2 from '../components/StdcmResults'; const StdcmViewV2 = () => { const { getScenarioID } = useOsrdConfSelectors(); const scenarioID = useSelector(getScenarioID); - const { launchStdcmRequest, cancelStdcmRequest, currentStdcmRequestStatus } = useStdcm(); + const { launchStdcmRequest, cancelStdcmRequest, currentStdcmRequestStatus, stdcmV2Results } = + useStdcm(); const isPending = currentStdcmRequestStatus === STDCM_REQUEST_STATUS.pending; const loaderRef = useRef(null); const { t } = useTranslation('stdcm'); + const [creationDate, setCreationDate] = useState(); + const { rollingStock } = useStoreDataForRollingStockSelector(); + const trainScheduleV2Activated = useSelector(getTrainScheduleV2Activated); + const { speedLimitByTag } = useStoreDataForSpeedLimitByTagSelector(); + + const handleClick = () => { + const currentDateTime = new Date(); + setCreationDate(currentDateTime); + launchStdcmRequest(); + }; const dispatch = useAppDispatch(); const { updateGridMarginAfter, updateGridMarginBefore, updateStdcmStandardAllowance } = useOsrdConfActions() as StdcmConfSliceActions; + const [pathProperties, setPathProperties] = useState(); + + const [postPathProperties] = + osrdEditoastApi.endpoints.postV2InfraByInfraIdPathProperties.useMutation(); + const { getPathSteps } = useOsrdConfSelectors(); + const pathSteps = useSelector(getPathSteps); + const infraId = useInfraID(); + + useEffect(() => { + const getPathProperties = async (_infraId: number, path: PathfindingResultSuccess) => { + const pathPropertiesParams: PostV2InfraByInfraIdPathPropertiesApiArg = { + infraId: _infraId, + props: ['electrifications', 'geometry', 'operational_points'], + pathPropertiesInput: { + track_section_ranges: path.track_section_ranges, + }, + }; + const { geometry, operational_points, electrifications } = + await postPathProperties(pathPropertiesParams).unwrap(); + + if (geometry && operational_points && electrifications) { + const pathStepsWihPosition = compact(pathSteps).map((step, i) => ({ + ...step, + positionOnPath: path.path_items_positions[i], + })); + + const suggestedOperationalPoints: SuggestedOP[] = formatSuggestedOperationalPoints( + operational_points, + geometry, + path.length + ); + + const updatedSuggestedOPs = upsertViasInOPs( + suggestedOperationalPoints, + pathStepsWihPosition + ); + + setPathProperties({ + electrifications, + geometry, + suggestedOperationalPoints: updatedSuggestedOPs, + allVias: updatedSuggestedOPs, + length: path.length, + }); + } + }; + + if (infraId && stdcmV2Results) { + const { path } = stdcmV2Results; + getPathProperties(infraId, path); + } + }, [stdcmV2Results]); + useEffect(() => { if (isPending) { loaderRef?.current?.scrollIntoView({ behavior: 'smooth' }); @@ -61,10 +138,7 @@ const StdcmViewV2 = () => { {/* } /> */}
-
{isPending && } @@ -75,6 +149,16 @@ const StdcmViewV2 = () => {
)} + {trainScheduleV2Activated && rollingStock && stdcmV2Results && ( + + )} ); }; diff --git a/front/src/modules/trainschedule/components/ManageTrainSchedule/Map.tsx b/front/src/modules/trainschedule/components/ManageTrainSchedule/Map.tsx index a36246f4b01..00428bc01cc 100644 --- a/front/src/modules/trainschedule/components/ManageTrainSchedule/Map.tsx +++ b/front/src/modules/trainschedule/components/ManageTrainSchedule/Map.tsx @@ -57,16 +57,20 @@ type MapProps = { pathProperties?: ManageTrainSchedulePathProperties; setMapCanvas?: (mapCanvas: string) => void; hideAttribution?: boolean; + currentStdcmRequestStatus?: string; }; -const Map = ({ pathProperties, setMapCanvas, hideAttribution = false }: MapProps) => { +const Map = ({ + pathProperties, + setMapCanvas, + hideAttribution = false, + currentStdcmRequestStatus, +}: MapProps) => { const mapBlankStyle = useMapBlankStyle(); const infraID = useInfraID(); const terrain3DExaggeration = useSelector(getTerrain3DExaggeration); const { viewport, mapSearchMarker, mapStyle, showOSM, layersSettings } = useSelector(getMap); - const { getGeojson } = useOsrdConfSelectors(); - const geoJson = useSelector(getGeojson); const trainScheduleV2Activated = useSelector(getTrainScheduleV2Activated); const [mapIsLoaded, setMapIsLoaded] = useState(false); @@ -80,6 +84,8 @@ const Map = ({ pathProperties, setMapCanvas, hideAttribution = false }: MapProps const mapRef = useRef(null); + const [mapCaptured, setMapCaptured] = useState(false); + useEffect(() => { const captureMap = async () => { const mapElement = document.getElementById('map-container'); @@ -92,13 +98,19 @@ const Map = ({ pathProperties, setMapCanvas, hideAttribution = false }: MapProps const canvas = await html2canvas(mapElement); const imageDataURL = canvas.toDataURL(); setMapCanvas(imageDataURL); + setMapCaptured(true); } } catch (error) { console.error('Error capturing map:', error); } }; - if (mapIsLoaded) captureMap(); - }, [mapIsLoaded, geoJson, viewport]); + + if (mapIsLoaded && !mapCaptured && currentStdcmRequestStatus === 'SUCCESS') { + setTimeout(() => { + captureMap(); + }, 500); + } + }, [mapIsLoaded, mapCaptured, pathProperties, setMapCanvas]); const scaleControlStyle = { left: 20, diff --git a/front/src/styles/scss/applications/stdcmV2/_home.scss b/front/src/styles/scss/applications/stdcmV2/_home.scss index da637f342d8..c27b4bb3a93 100644 --- a/front/src/styles/scss/applications/stdcmV2/_home.scss +++ b/front/src/styles/scss/applications/stdcmV2/_home.scss @@ -40,5 +40,136 @@ height: calc(100vh - 64px); } } + .stdcm-v2-results { + background-color: rgb(233, 239, 242); + .simuation-banner { + color: rgb(0, 0, 0); + font-weight: 600; + font-size: 1.125rem; + line-height: 1.5rem; + padding-top: 1.813rem; + margin-left: 2rem; + margin-right: 2rem; + border-bottom: 0.063rem solid rgb(182, 178, 175); + .creation-date { + color: rgb(121, 118, 113); + font-weight: 400; + font-size: 0.875rem; + line-height: 1.25rem; + padding-top: 0.25rem; + padding-bottom: 0.688rem; + } + .simulation-validated { + display: flex; + .check-circle{ + color: rgb(60, 202, 128); + padding-left: 0.5rem; + } + } + } + .simuation-results { + display: flex; + justify-content: space-between; + .table-container { + margin-top: 2.125rem; + margin-left: 2rem; + width: 50.25rem; + } + .table-results { + border-radius: 0.375rem; + background-color: rgba(0, 0, 0, 0.05); + } + th { + height: 2rem; + font-size: 0.875rem; + font-weight: 400; + text-transform: capitalize; + color: rgb(121, 118, 113); + padding-top: 0.313rem; + padding-bottom: 0.313rem; + vertical-align: middle; + } + tbody tr:nth-child(odd) { + background-color: rgb(239, 243, 245); + } + tbody tr:nth-child(even) { + background-color: rgb(246, 248, 249); + } + td { + height: 2rem; + font-size: 0.875; + font-weight: 400; + color: rgb(49, 46, 43); + line-height: 1.25rem; + vertical-align: middle; + } + .index { + padding-left: 2rem; + } + .pr { + padding-right: 12.438rem; + } + .ch { + padding-right: 2.25rem; + } + .stop { + font-size: 14; + font-weight: 600; + color: rgb(0, 0, 0); + line-height: 1.25rem; + } + .weight { + padding-left: 3.063rem; + } + .semi-bold-output { + font-weight: 600; + } + .display-all { + height: 6.5rem; + background-color: rgba(0, 0, 0, 0.05); + display: flex; + justify-content: space-between; + border-bottom-left-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; + .button-display-all-PR { + padding-top: 2rem; + padding-left: 2.25rem; + } + .button-get-simulation { + padding-top: 2rem; + padding-right: 2.25rem; + } + .selected-simulation{ + padding-top: 0.6rem; + } + } + .get-simulation { + height: 10.75rem; + background-color: rgb(255, 255, 255); + border-bottom-left-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; + .download-simulation { + padding-top: 2.813rem; + padding-left: 16.438rem; + } + .gesico-text { + font-weight: 400; + font-size: 1rem; + color: rgb(49, 46, 43); + padding-top: 1.313rem; + padding-left: 12.938rem; + } + } + } + .map-results { + width: 33.75rem; + height: 33.75rem; + margin-top: 2.313rem; + margin-right: 2rem; + border-radius: 0.5rem; + border: 0.063rem solid rgba(255, 255, 255, 1); + box-shadow: 0rem 0rem 0rem 0.125rem rgba(255, 255, 255, 0.75) inset, 0rem 0rem 0rem 0.063rem rgba(0, 0, 0, 0.25) inset; + } + } }