diff --git a/front/package-lock.json b/front/package-lock.json index 2a16a464a46..bb97be5117a 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -17,7 +17,7 @@ "@ag-media/react-pdf-table": "^2.0.3", "@nivo/core": "^0.99.0", "@nivo/line": "^0.99.0", - "@osrd-project/netzgrafik-frontend": "0.0.0-snapshot.5eafed2a3fd1e8818d84679046e2c2274a948bc1", + "@osrd-project/netzgrafik-frontend": "0.0.0-snapshot.09bd436ff2dd9d6422a08dc05f9a551c7382e642", "@osrd-project/ui-charts": "0.0.1-dev", "@osrd-project/ui-core": "0.0.1-dev", "@osrd-project/ui-icons": "0.0.1-dev", @@ -3444,9 +3444,9 @@ "link": true }, "node_modules/@osrd-project/netzgrafik-frontend": { - "version": "0.0.0-snapshot.5eafed2a3fd1e8818d84679046e2c2274a948bc1", - "resolved": "https://registry.npmjs.org/@osrd-project/netzgrafik-frontend/-/netzgrafik-frontend-0.0.0-snapshot.5eafed2a3fd1e8818d84679046e2c2274a948bc1.tgz", - "integrity": "sha512-j9+TYrlcW6sdJdsoK43X0nEIeu+c9/qe6SnacHGgVT0fG8Vez9otKLkLR8fzUpTpMZaAoAuXK+rGJuXysUgNAw==" + "version": "0.0.0-snapshot.09bd436ff2dd9d6422a08dc05f9a551c7382e642", + "resolved": "https://registry.npmjs.org/@osrd-project/netzgrafik-frontend/-/netzgrafik-frontend-0.0.0-snapshot.09bd436ff2dd9d6422a08dc05f9a551c7382e642.tgz", + "integrity": "sha512-T87X6n5+nw1X3ehW8YPjiaXetoBWgfXDbsmid6JzDD/T7tNd6HJJ/VKZSzau1qMwuWnbQeN5oL1HTY3D8J2Ojg==" }, "node_modules/@osrd-project/storybook": { "resolved": "ui/storybook", diff --git a/front/package.json b/front/package.json index 852d82c295e..171aa88ccb0 100644 --- a/front/package.json +++ b/front/package.json @@ -14,7 +14,7 @@ "@ag-media/react-pdf-table": "^2.0.3", "@nivo/core": "^0.99.0", "@nivo/line": "^0.99.0", - "@osrd-project/netzgrafik-frontend": "0.0.0-snapshot.5eafed2a3fd1e8818d84679046e2c2274a948bc1", + "@osrd-project/netzgrafik-frontend": "0.0.0-snapshot.09bd436ff2dd9d6422a08dc05f9a551c7382e642", "@osrd-project/ui-charts": "0.0.1-dev", "@osrd-project/ui-core": "0.0.1-dev", "@osrd-project/ui-icons": "0.0.1-dev", diff --git a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/ngeToOsrd.ts b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/ngeToOsrd.ts index cfe642996af..10d46d9b9d7 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/ngeToOsrd.ts +++ b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/ngeToOsrd.ts @@ -52,6 +52,7 @@ const handleLabelOperation = async ({ await handleUpdateTimetableItem({ netzgrafikDto, trainrun, + tags: ['labelIds'], trainScheduleSetId, infraId, state, @@ -104,9 +105,8 @@ export const handleOperation = async ({ break; case 'trainrun': { await handleTrainrunOperation({ - type, netzgrafikDto, - trainrunId: event.trainrun.id, + trainrunEvent: event, trainScheduleSetId, infraId, state, diff --git a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/trainrun.ts b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/trainrun.ts index 2d715f70ecb..db6405629b5 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/trainrun.ts +++ b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/trainrun.ts @@ -19,11 +19,12 @@ import { isPacedTrainId } from 'utils/trainId'; import { checkChangeGroups } from '../../ManageTimetableItem/helpers/buildPacedTrainException'; import type { NetzgrafikDto, - NGEEvent, TrainrunSectionDto, NodeDto, TimeLockDto, TrainrunDto, + NGETrainrunEvent, + TrainrunUpdateTag, } from '../../NGE/types'; import { DEFAULT_TRAIN_SCHEDULE_PAYLOAD, @@ -388,12 +389,25 @@ export const createPacedAttributesFromTrainrun = ( const handleCreateTimetableItem = async ( netzgrafikDto: NetzgrafikDto, trainrun: TrainrunDto, + duplicatedTrainrunId: number | undefined, trainScheduleSetId: number, infraId: number, state: MacroEditorState, dispatch: AppDispatch, addUpsertedTimetableItems: (timetableItems: TimetableItem[]) => void ) => { + const duplicatedTimetableItemIds = duplicatedTrainrunId + ? state.timetableItemIdByNgeId.get(duplicatedTrainrunId) + : undefined; + const duplicatedForwardTimetableItem = duplicatedTimetableItemIds?.[0] + ? await fetchTimetableItem(duplicatedTimetableItemIds[0], dispatch) + : { id: undefined, train_name: undefined }; + const duplicatedReturnTimetableItem = duplicatedTimetableItemIds?.[1] + ? await fetchTimetableItem(duplicatedTimetableItemIds[1], dispatch) + : { id: undefined, train_name: undefined }; + const { id: _id, train_name: _name, ...duplicatedForwardBase } = duplicatedForwardTimetableItem; + const { id: __id, train_name: __name, ...duplicatedReturnBase } = duplicatedReturnTimetableItem; + const trainrunSections = getContinuousTrainrunSectionsByTrainrunId(netzgrafikDto, trainrun.id); const labels = getTrainrunLabels(netzgrafikDto, trainrun); @@ -431,11 +445,14 @@ const handleCreateTimetableItem = async ( train_name: trainrun.name, labels, category, + ...duplicatedForwardBase, ...pathAndSchedule, }; const returnTrip = - trainrun.direction === 'round_trip' ? { ...forwardTrip, ...returnPathAndSchedule } : undefined; + trainrun.direction === 'round_trip' + ? { ...forwardTrip, ...duplicatedReturnBase, ...returnPathAndSchedule } + : undefined; const timetableItemsToCreate = returnTrip ? [forwardTrip, returnTrip] : [forwardTrip]; @@ -494,6 +511,8 @@ const handleDeleteTimetableItem = async ( export const handleUpdateTimetableItem = async ({ netzgrafikDto, trainrun, + tags, + oneWayDirection, trainScheduleSetId, infraId, state, @@ -503,6 +522,8 @@ export const handleUpdateTimetableItem = async ({ }: { netzgrafikDto: NetzgrafikDto; trainrun: TrainrunDto; + tags: TrainrunUpdateTag[]; + oneWayDirection?: 'forward' | 'backward'; infraId: number; trainScheduleSetId: number; state: MacroEditorState; @@ -511,17 +532,24 @@ export const handleUpdateTimetableItem = async ({ addDeletedTimetableItemIds: (timetableItemIds: TimetableItemId[]) => void; }) => { const timetableItemIds = state.timetableItemIdByNgeId.get(trainrun.id)!; - const oldForwardTimetableItem = await fetchTimetableItem(timetableItemIds[0], dispatch); + let oldForwardId = timetableItemIds[0]; + if (oneWayDirection === 'backward') { + // Case 1: Switching from round trip to the return trip (now forward) + if (timetableItemIds[1]) oldForwardId = timetableItemIds[1]; + // Case 2: Inverting the direction of a one way train (we sadly don't store the old return) + else tags.push('nodes', 'times'); + } + const oldForwardTimetableItem = await fetchTimetableItem(oldForwardId, dispatch); const trainrunSections = getContinuousTrainrunSectionsByTrainrunId(netzgrafikDto, trainrun.id); const labels = getTrainrunLabels(netzgrafikDto, trainrun); - const forwardPathAndSchedule = generatePathAndSchedule( + const { path: forwardPath, ...forwardSchedule } = generatePathAndSchedule( trainrunSections, netzgrafikDto.nodes, new Date(oldForwardTimetableItem.start_time), TRAINRUN_DIRECTIONS.FORWARD, state ); - await populateSecondaryCodesInPath(forwardPathAndSchedule.path, infraId, dispatch); + await populateSecondaryCodesInPath(forwardPath, infraId, dispatch); const { id: _id, ...timetableItemBase } = oldForwardTimetableItem; @@ -534,13 +562,13 @@ export const handleUpdateTimetableItem = async ({ const newForwardTrainBase: Omit = { ...timetableItemBase, - train_name: trainrun.name, - labels, - // Reset margins because they contain references to path items - margins: undefined, - paced, - category, - ...forwardPathAndSchedule, + ...(tags.includes('name') && { train_name: trainrun.name }), + ...(tags.includes('labelIds') && { labels }), + // Reset margins if the path changed because they contain references to path items + ...(tags.includes('nodes') && { path: forwardPath, margins: undefined, ...forwardSchedule }), + ...(tags.includes('times') && { ...forwardSchedule }), + ...(tags.includes('frequencyId') && { paced }), + ...(tags.includes('categoryId') && { category }), }; if (paced && oldForwardTimetableItem.paced) { @@ -562,17 +590,17 @@ export const handleUpdateTimetableItem = async ({ if (trainrun.direction === 'one_way') { if (timetableItemIds[1]) { - // NGE always selects the forward trip by default when going from round trip to one way trip, - // thus the trip that needs to be deleted is always the return trip await storeRoundTrip(dispatch, newForwardTimetableItem.id); - await deleteTimetableItemById(timetableItemIds[1], dispatch, addDeletedTimetableItemIds); + const oldReturnId = + oneWayDirection !== 'backward' ? timetableItemIds[1] : timetableItemIds[0]; + await deleteTimetableItemById(oldReturnId, dispatch, addDeletedTimetableItemIds); } state.timetableItemIdByNgeId.set(trainrun.id, [newForwardTimetableItem.id, null]); return; } - const returnPathAndSchedule = generatePathAndSchedule( + const { path: returnPath, ...returnSchedule } = generatePathAndSchedule( trainrunSections, netzgrafikDto.nodes, new Date(oldForwardTimetableItem.start_time), @@ -580,7 +608,7 @@ export const handleUpdateTimetableItem = async ({ state ); - await populateSecondaryCodesInPath(returnPathAndSchedule.path, infraId, dispatch); + await populateSecondaryCodesInPath(returnPath, infraId, dispatch); let newReturnTimetableItem: TimetableItem; const returnPaced: TrainSchedule['paced'] = paced ? { ...paced, exceptions: [] } : null; @@ -591,13 +619,16 @@ export const handleUpdateTimetableItem = async ({ const { id: _return_id, ...oldReturnTrainBase } = oldReturnTimetableItem; const newReturnTrainBase: Omit = { ...oldReturnTrainBase, - train_name: trainrun.name, - labels, - // Reset margins because they contain references to path items - margins: undefined, - paced: returnPaced, - category, - ...returnPathAndSchedule, + ...(tags.includes('name') && { train_name: trainrun.name }), + ...(tags.includes('labelIds') && { labels }), + // Reset margins if the path changed because they contain references to path items + ...(tags.includes('nodes') && { path: returnPath, margins: undefined, ...returnSchedule }), + ...(tags.includes('times') && { + schedule: returnSchedule.schedule, + start_time: returnSchedule.start_time, + }), + ...(tags.includes('frequencyId') && { paced: returnPaced }), + ...(tags.includes('categoryId') && { category }), }; if (returnPaced && oldReturnTimetableItem.paced) { @@ -625,7 +656,8 @@ export const handleUpdateTimetableItem = async ({ const returnPacedTrain: TrainSchedule = { ...pacedTrainWithoutTrainScheduleSetId, - ...returnPathAndSchedule, + ...returnSchedule, + path: returnPath, paced: returnPaced, }; @@ -646,9 +678,8 @@ export const handleUpdateTimetableItem = async ({ }; export const handleTrainrunOperation = async ({ - type, netzgrafikDto, - trainrunId, + trainrunEvent, trainScheduleSetId, infraId, state, @@ -656,9 +687,8 @@ export const handleTrainrunOperation = async ({ addUpsertedTimetableItems, addDeletedTimetableItemIds, }: { - type: NGEEvent['type']; netzgrafikDto: NetzgrafikDto; - trainrunId: number; + trainrunEvent: NGETrainrunEvent; trainScheduleSetId: number; infraId: number; state: MacroEditorState; @@ -666,12 +696,13 @@ export const handleTrainrunOperation = async ({ addUpsertedTimetableItems: (timetableItems: TimetableItem[]) => void; addDeletedTimetableItemIds: (timetableItemIds: TimetableItemId[]) => void; }) => { - const trainrun = netzgrafikDto.trainruns.find((tr) => tr.id === trainrunId); - switch (type) { + const trainrun = trainrunEvent.trainrun; + switch (trainrunEvent.type) { case 'create': { await handleCreateTimetableItem( netzgrafikDto, - trainrun!, + trainrun, + trainrunEvent.duplicatedTrainrunId, trainScheduleSetId, infraId, state, @@ -683,7 +714,9 @@ export const handleTrainrunOperation = async ({ case 'update': { await handleUpdateTimetableItem({ netzgrafikDto, - trainrun: trainrun!, + trainrun, + tags: trainrunEvent.tags, + oneWayDirection: trainrunEvent.oneWayDirection, trainScheduleSetId, infraId, dispatch, @@ -694,7 +727,7 @@ export const handleTrainrunOperation = async ({ break; } case 'delete': { - await handleDeleteTimetableItem(trainrunId, state, dispatch, addDeletedTimetableItemIds); + await handleDeleteTimetableItem(trainrun.id, state, dispatch, addDeletedTimetableItemIds); break; } default: @@ -738,6 +771,7 @@ export const updateTrainrunsByNode = async ({ await handleUpdateTimetableItem({ netzgrafikDto, trainrun, + tags: ['nodes'], trainScheduleSetId, infraId, dispatch, diff --git a/front/src/applications/operationalStudies/views/Scenario/components/NGE/types.ts b/front/src/applications/operationalStudies/views/Scenario/components/NGE/types.ts index 27afa945204..6acb10fc66e 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/NGE/types.ts +++ b/front/src/applications/operationalStudies/views/Scenario/components/NGE/types.ts @@ -220,17 +220,46 @@ export type NetzgrafikDto = { }; }; -export type NGEEvent = { - type: 'create' | 'delete' | 'update'; -} & ( +export type TrainrunUpdateTag = + | 'nodes' + | 'times' + | 'numberOfStops' + | 'name' + | 'categoryId' + | 'frequencyId' + | 'timeCategoryId' + | 'labelIds' + | 'direction'; + +export type NGETrainrunEvent = + | { + type: 'create'; + objectType: 'trainrun'; + trainrun: TrainrunDto; + duplicatedTrainrunId?: number; + } | { + type: 'update'; objectType: 'trainrun'; trainrun: TrainrunDto; + tags: TrainrunUpdateTag[]; + oneWayDirection?: 'forward' | 'backward'; } - | { objectType: 'node'; node: NodeDto } - | { objectType: 'label'; label: LabelDto } - | { objectType: 'note'; note: FreeFloatingTextDto } -); + | { + type: 'delete'; + objectType: 'trainrun'; + trainrun: TrainrunDto; + }; + +export type NGEEvent = + | NGETrainrunEvent + | ({ + type: 'create' | 'delete' | 'update'; + } & ( + | { objectType: 'node'; node: NodeDto } + | { objectType: 'label'; label: LabelDto } + | { objectType: 'note'; note: FreeFloatingTextDto } + )); export type LabelDto = { id: number | string;