diff --git a/front/src/applications/stdcmV2/components/StdcmDestination.tsx b/front/src/applications/stdcmV2/components/StdcmDestination.tsx index a15eb35b25c..fa67259671f 100644 --- a/front/src/applications/stdcmV2/components/StdcmDestination.tsx +++ b/front/src/applications/stdcmV2/components/StdcmDestination.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -8,12 +8,12 @@ import type { StdcmConfSliceActions } from 'reducers/osrdconf/stdcmConf'; import type { PathStep } from 'reducers/osrdconf/types'; import { useAppDispatch } from 'store'; import { replaceElementAtIndex } from 'utils/array'; -import { extractDateAndTimefromISO } from 'utils/date'; +import { extractDateAndTimefromISO, generateISODateFromDateTime } from 'utils/date'; import StdcmCard from './StdcmCard'; import StdcmOperationalPoint from './StdcmOperationalPoint'; import StdcmOpSchedule from './StdcmOpSchedule'; -import { ArrivalTimeTypes, type StdcmConfigCardProps } from '../types'; +import { ArrivalTimeTypes, type ScheduleConstraint, type StdcmConfigCardProps } from '../types'; const StdcmDestination = ({ setCurrentSimulationInputs, @@ -24,6 +24,11 @@ const StdcmDestination = ({ }) => { const { t } = useTranslation('stdcm'); const dispatch = useAppDispatch(); + + const [arrivalScheduleConstraint, setArrivalScheduleConstraint] = useState< + ScheduleConstraint | undefined + >(); + const { updateDestination, updateDestinationArrival, @@ -58,11 +63,23 @@ const StdcmDestination = ({ }, [destination]); const updateDestinationPoint = (pathStep: PathStep | null) => { - dispatch(updateDestination(pathStep)); + if (!pathStep || !arrivalScheduleConstraint) { + dispatch(updateDestination(pathStep)); + } else { + dispatch( + updateDestination({ + ...pathStep, + arrival: generateISODateFromDateTime(arrivalScheduleConstraint), + }) + ); + } }; - const onDestinationArrivalChange = (arrival: string) => { - dispatch(updateDestinationArrival(arrival)); + const onDestinationArrivalChange = (schedule: ScheduleConstraint) => { + setArrivalScheduleConstraint(schedule); + + const newOpArrival = generateISODateFromDateTime(schedule); + dispatch(updateDestinationArrival(newOpArrival)); }; const onDestinationArrivalTypeChange = (arrivalType: ArrivalTimeTypes) => { diff --git a/front/src/applications/stdcmV2/components/StdcmOpSchedule.tsx b/front/src/applications/stdcmV2/components/StdcmOpSchedule.tsx index 922089d7eda..1e30ff8c827 100644 --- a/front/src/applications/stdcmV2/components/StdcmOpSchedule.tsx +++ b/front/src/applications/stdcmV2/components/StdcmOpSchedule.tsx @@ -6,13 +6,13 @@ import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { useOsrdConfSelectors } from 'common/osrdContext'; -import { formatLocaleDateToIsoDate, isArrivalDateInSearchTimeWindow } from 'utils/date'; +import { isArrivalDateInSearchTimeWindow } from 'utils/date'; -import type { ArrivalTimeTypes } from '../types'; +import type { ArrivalTimeTypes, ScheduleConstraint } from '../types'; type StdcmOpScheduleProps = { disabled: boolean; - onArrivalChange: (arrival: string) => void; + onArrivalChange: ({ date, hours, minutes }: ScheduleConstraint) => void; onArrivalTypeChange: (arrivalType: ArrivalTimeTypes) => void; onArrivalToleranceChange: ({ toleranceBefore, @@ -77,13 +77,6 @@ const StdcmOpSchedule = ({ }; }, [opTimingData, opToleranceValues, searchDatetimeWindow]); - const updateOpArrival = (date: Date, { hours, minutes }: { hours: number; minutes: number }) => { - date.setHours(hours); - date.setMinutes(minutes); - const newOpArrival = formatLocaleDateToIsoDate(date); - onArrivalChange(newOpArrival); - }; - return (
@@ -121,7 +114,8 @@ const StdcmOpSchedule = ({ : undefined, }} onDateChange={(e) => { - updateOpArrival(e, { + onArrivalChange({ + date: e, hours: arrivalTimeHours || 0, minutes: arrivalTimeMinutes || 0, }); @@ -135,10 +129,7 @@ const StdcmOpSchedule = ({ hours={arrivalTimeHours} minutes={arrivalTimeMinutes} onTimeChange={({ hours, minutes }) => { - updateOpArrival(arrivalDate, { - hours, - minutes, - }); + onArrivalChange({ date: arrivalDate, hours, minutes }); }} disabled={disabled} value={arrivalTime} diff --git a/front/src/applications/stdcmV2/components/StdcmOrigin.tsx b/front/src/applications/stdcmV2/components/StdcmOrigin.tsx index 21b5d88efb8..a566116202d 100644 --- a/front/src/applications/stdcmV2/components/StdcmOrigin.tsx +++ b/front/src/applications/stdcmV2/components/StdcmOrigin.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -8,12 +8,12 @@ import type { StdcmConfSliceActions } from 'reducers/osrdconf/stdcmConf'; import type { PathStep } from 'reducers/osrdconf/types'; import { useAppDispatch } from 'store'; import { replaceElementAtIndex } from 'utils/array'; -import { extractDateAndTimefromISO } from 'utils/date'; +import { extractDateAndTimefromISO, generateISODateFromDateTime } from 'utils/date'; import StdcmCard from './StdcmCard'; import StdcmOperationalPoint from './StdcmOperationalPoint'; import StdcmOpSchedule from './StdcmOpSchedule'; -import { ArrivalTimeTypes, type StdcmConfigCardProps } from '../types'; +import { ArrivalTimeTypes, type ScheduleConstraint, type StdcmConfigCardProps } from '../types'; const StdcmOrigin = ({ setCurrentSimulationInputs, @@ -25,6 +25,10 @@ const StdcmOrigin = ({ const { t } = useTranslation('stdcm'); const dispatch = useAppDispatch(); + const [arrivalScheduleConstraint, setArrivalScheduleConstraint] = useState< + ScheduleConstraint | undefined + >(); + const { updateOrigin, updateOriginArrival, updateOriginArrivalType, updateOriginTolerances } = useOsrdConfActions() as StdcmConfSliceActions; @@ -49,11 +53,23 @@ const StdcmOrigin = ({ }, [origin]); const updateOriginPoint = (pathStep: PathStep | null) => { - dispatch(updateOrigin(pathStep)); + if (!pathStep || !arrivalScheduleConstraint) { + dispatch(updateOrigin(pathStep)); + } else { + dispatch( + updateOrigin({ + ...pathStep, + arrival: generateISODateFromDateTime(arrivalScheduleConstraint), + }) + ); + } }; - const onOriginArrivalChange = (arrival: string) => { - dispatch(updateOriginArrival(arrival)); + const onOriginArrivalChange = (schedule: ScheduleConstraint) => { + setArrivalScheduleConstraint(schedule); + + const newOpArrival = generateISODateFromDateTime(schedule); + dispatch(updateOriginArrival(newOpArrival)); }; const onOriginArrivalTypeChange = (arrivalType: ArrivalTimeTypes) => { diff --git a/front/src/applications/stdcmV2/types.ts b/front/src/applications/stdcmV2/types.ts index ca737de59d6..f67bacf44aa 100644 --- a/front/src/applications/stdcmV2/types.ts +++ b/front/src/applications/stdcmV2/types.ts @@ -46,3 +46,9 @@ export type StdcmConfigErrors = { errorType: StdcmConfigErrorTypes; errorDetails?: { originTime: string; destinationTime: string }; }; + +export type ScheduleConstraint = { + date: Date; + hours: number; + minutes: number; +}; diff --git a/front/src/reducers/osrdconf/osrdConfCommon/index.ts b/front/src/reducers/osrdconf/osrdConfCommon/index.ts index 738a030939d..04f674a1cf6 100644 --- a/front/src/reducers/osrdconf/osrdConfCommon/index.ts +++ b/front/src/reducers/osrdconf/osrdConfCommon/index.ts @@ -302,19 +302,21 @@ export function buildCommonConfReducers(): CommonConfRe state.startTime = action.payload; }, updateOrigin(state: Draft, action: PayloadAction>) { + const prevOriginArrivalType = state.pathSteps.at(0)?.arrivalType; const newPoint = action.payload ? { ...action.payload, - arrivalType: ArrivalTimeTypes.PRECISE_TIME, + arrivalType: prevOriginArrivalType || ArrivalTimeTypes.PRECISE_TIME, } : null; state.pathSteps = updateOriginPathStep(state.pathSteps, newPoint, true); }, updateDestination(state: Draft, action: PayloadAction>) { + const prevDestinationArrivalType = state.pathSteps.at(-1)?.arrivalType; const newPoint = action.payload ? { ...action.payload, - arrivalType: ArrivalTimeTypes.ASAP, + arrivalType: prevDestinationArrivalType || ArrivalTimeTypes.ASAP, } : null; state.pathSteps = updateDestinationPathStep(state.pathSteps, newPoint, true); diff --git a/front/src/utils/__tests__/date.spec.ts b/front/src/utils/__tests__/date.spec.ts index 2199132bbae..450c3c621da 100644 --- a/front/src/utils/__tests__/date.spec.ts +++ b/front/src/utils/__tests__/date.spec.ts @@ -6,6 +6,8 @@ import { parseDateTime, extractDateAndTimefromISO, isArrivalDateInSearchTimeWindow, + formatLocaleDateToIsoDate, + generateISODateFromDateTime, } from 'utils/date'; describe('dateTimeToIso', () => { @@ -134,3 +136,18 @@ describe('isArrivalDateInSearchTimeWindow', () => { expect(result).toBe(false); }); }); + +describe('generateISODateFromDateTime', () => { + it('should correctly set hours and minutes and return ISO string', () => { + const schedule = { + date: new Date('2024-08-01T00:00:00Z'), + hours: 10, + minutes: 30, + }; + const expectedISODate = formatLocaleDateToIsoDate(new Date('2024-08-01T10:30:00Z')); + + const result = generateISODateFromDateTime(schedule); + + expect(result).toBe(expectedISODate); + }); +}); diff --git a/front/src/utils/date.ts b/front/src/utils/date.ts index 07b14482cc7..d4dde56d744 100644 --- a/front/src/utils/date.ts +++ b/front/src/utils/date.ts @@ -4,6 +4,7 @@ import customParseFormat from 'dayjs/plugin/customParseFormat'; import timezone from 'dayjs/plugin/timezone'; import utc from 'dayjs/plugin/utc'; +import type { ScheduleConstraint } from 'applications/stdcmV2/types'; import type { IsoDateTimeString, IsoDurationString } from 'common/types'; import i18n from 'i18n'; @@ -229,3 +230,14 @@ export function isArrivalDateInSearchTimeWindow( const arrivalDate = new Date(arrivalTime); return arrivalDate >= searchDatetimeWindow.begin && arrivalDate <= searchDatetimeWindow.end; } + +/** + * Generates an ISO date string from a given date and time. + * @param {ScheduleConstraint} - An object containing the base date, the hours, and the minutes. + * @returns {string} The ISO formatted date string. + */ +export const generateISODateFromDateTime = ({ date, hours, minutes }: ScheduleConstraint) => { + date.setHours(hours); + date.setMinutes(minutes); + return formatLocaleDateToIsoDate(date); +};