Skip to content
Open
16 changes: 16 additions & 0 deletions front/public/locales/en/operational-studies.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"errorMessages": {
"error": "An error has occurred",
"errorEmptyFile": "Empty file",
"errorImportClipboardContent": "Clipboard does not contain valid timetables.",
"errorInvalidFile": "Invalid file",
"errorNoDate": "You must enter a date.",
"errorNoFrom": "You must fill in an origin.",
Expand Down Expand Up @@ -66,6 +67,16 @@
"otherTrains": "+{{count}} others",
"trainsInConflictTitle": "TRAIN(S) IN CONFLICT"
},
"copyTimetable": {
"text_one": "Train copied",
"text_other": "Trains copied",
"title": "Timetables export"
},
"cutTimetable": {
"text_one": "Train cut",
"text_other": "Trains cut",
"title": "Timetables deletion"
},
"description": {
"electricalProfileWithId": "Electrical profiles : ID{{id}}",
"electricalProfileWithName": "Electrical profiles : {{name}} | ID{{id}}"
Expand Down Expand Up @@ -152,6 +163,11 @@
"pacedTrain_one": "1 service",
"pacedTrain_other": "{{count}} services",
"pacedTrain_zero": "0 service",
"pasteTimetable": {
"text_one": "Train pasted",
"text_other": "Trains pasted",
"title": "Timetables import"
},
"requestedDestination": "requested destination",
"requestedOrigin": "requested origin",
"requestedPoint": "requested point ({{ count }})",
Expand Down
16 changes: 16 additions & 0 deletions front/public/locales/fr/operational-studies.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"errorMessages": {
"error": "Une erreur est survenue",
"errorEmptyFile": "Fichier vide",
"errorImportClipboardContent": "Le presse-papiers ne contient pas des circulations valides.",
"errorInvalidFile": "Fichier invalide",
"errorNoDate": "Vous devez renseigner une date.",
"errorNoFrom": "Vous devez renseigner une origine.",
Expand Down Expand Up @@ -66,6 +67,16 @@
"otherTrains": "+{{count}} autres",
"trainsInConflictTitle": "TRAIN(S) EN CONFLIT"
},
"copyTimetable": {
"text_one": "Circulation copiée",
"text_other": "Circulations copiées",
"title": "Export de circulations"
},
"cutTimetable": {
"text_one": "Circulation coupée",
"text_other": "Circulations coupées",
"title": "Supression de circulations"
},
"description": {
"electricalProfileWithId": "Profils électriques : ID{{id}}",
"electricalProfileWithName": "Profils électriques : {{name}} | ID{{id}}"
Expand Down Expand Up @@ -152,6 +163,11 @@
"pacedTrain_one": "1 mission",
"pacedTrain_other": "{{count}} missions",
"pacedTrain_zero": "0 mission",
"pasteTimetable": {
"text_one": "Circulation collée",
"text_other": "Circulations collées",
"title": "Import de circulations"
},
"requestedDestination": "destination demandée",
"requestedOrigin": "origine demandée",
"requestedPoint": "point demandé ({{ count }})",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useContext, useMemo, useState } from 'react';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';

import {
ArrowSwitch,
Expand Down Expand Up @@ -41,7 +41,8 @@ import { isTrainScheduleId } from 'utils/trainId';

import Timetable from './Timetable';
import useFilterTimetableItems from './useFilterTimetableItems';
import { exportTimetableItems } from './utils';
import { copyTimetableItemsToClipboard, exportTimetableItems } from './utils';
import postTimetableItems from '../ImportTimetableItem/helpers/postTimetableItems';

type TimetableBoardWrapperProps = {
setDisplayTimetableItemManagement: (mode: string) => void;
Expand Down Expand Up @@ -186,7 +187,10 @@ const TimetableBoardWrapper = ({
[removeTimetableItems, setSelectedTimetableItemIds]
);

const handleTrainsDelete = async (currentSelectedTrainId?: TrainId) => {
const handleTrainsDelete = async (
currentSelectedTrainId?: TrainId,
hideToast: boolean = false
) => {
const itemsCount = selectedTimetableItemIds.length;

const isSelectedTimetableItemInSelection =
Expand Down Expand Up @@ -215,12 +219,15 @@ const TimetableBoardWrapper = ({
await Promise.all([deletingTrainSchedulesPromise, deletingPacedTrainsPromise]);

removeAndUnselectTrains(selectedTimetableItemIds);
dispatch(
setSuccess({
title: t('main.timetable.itemsSelectionDeletedCount', { count: itemsCount }),
text: '',
})
);

if (!hideToast) {
dispatch(
setSuccess({
title: t('main.timetable.itemsSelectionDeletedCount', { count: itemsCount }),
text: '',
})
);
}
} catch (e) {
if (isSelectedTimetableItemInSelection) {
dispatch(updateSelectedTrainId(currentSelectedTrainId));
Expand Down Expand Up @@ -306,6 +313,86 @@ const TimetableBoardWrapper = ({
};
// --- END BOARD WRAPPER MENU ITEMS CONFIGURATION ---

const ctrlC = useCallback(() => {
const selectedText = document.getSelection()?.toString();
if (selectedText !== undefined && selectedText.length > 0) return;

if (selectedTimetableItemIds.length === 0) {
return;
}

copyTimetableItemsToClipboard(selectedTimetableItemIds, timetableItems);
dispatch(
setSuccess({
title: t('main.copyTimetable.title'),
text: t('main.copyTimetable.text', { count: selectedTimetableItemIds.length }),
})
);
}, [selectedTimetableItemIds, timetableItems]);

const ctrlV = useCallback(async () => {
const clipboardContent = await navigator.clipboard.readText();
try {
const data = JSON.parse(clipboardContent);
const { trainSchedules, pacedTrains } = await postTimetableItems(
timetableId,
data.train_schedules,
data.paced_trains,
dispatch
);
const newTimetableItems = [...trainSchedules, ...pacedTrains];
upsertTimetableItems(newTimetableItems);
setSelectedTimetableItemIds(newTimetableItems.map((item) => item.id));
dispatch(
setSuccess({
title: t('main.pasteTimetable.title'),
text: t('main.pasteTimetable.text', { count: newTimetableItems.length }),
})
);
} catch {
dispatch(
setFailure({
name: t('importTrains.errorMessages.error'),
message: t('importTrains.errorMessages.errorImportClipboardContent'),
})
);
}
}, [navigator.clipboard, timetableId]);

const ctrlX = useCallback(
(event: ClipboardEvent) => {
if (selectedTimetableItemIds.length === 0) {
return;
}

event.preventDefault();
copyTimetableItemsToClipboard(selectedTimetableItemIds, timetableItems);
handleTrainsDelete(selectedTrainId, true);
dispatch(
setSuccess({
title: t('main.cutTimetable.title'),
text: t('main.cutTimetable.text', { count: selectedTimetableItemIds.length }),
})
);
},
[selectedTimetableItemIds, timetableItems]
);

useEffect(() => {
document.addEventListener('copy', ctrlC);
return () => document.removeEventListener('copy', ctrlC);
}, [ctrlC]);

useEffect(() => {
document.addEventListener('paste', ctrlV);
return () => document.removeEventListener('paste', ctrlV);
}, [ctrlV]);

useEffect(() => {
document.addEventListener('cut', ctrlX);
return () => document.removeEventListener('cut', ctrlX);
}, [ctrlX]);

return (
<>
<BoardWrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,10 @@ export const roundAndFormatToNearestMinute = (d: Date) =>
export const formatTrainDuration = (duration: Duration) =>
dayjs.duration(duration.ms).format('HH[h]mm');

export const exportTimetableItems = (
export const timetableItemsToJson = (
selectedTimeTableIdsFromClick: TimetableItemId[],
timetableItems: TimetableItem[]
) => {
if (!timetableItems) return;

): string => {
const formattedTimetableItems = timetableItems
.filter(({ id }) => selectedTimeTableIdsFromClick.includes(id))
.reduce<{
Expand All @@ -70,7 +68,28 @@ export const exportTimetableItems = (
{ train_schedules: [], paced_trains: [] }
);

const jsonString = JSON.stringify(formattedTimetableItems);
return JSON.stringify(formattedTimetableItems);
};

export const copyTimetableItemsToClipboard = (
selectedTimeTableIdsFromClick: TimetableItemId[],
timetableItems: TimetableItem[]
) => {
if (!timetableItems) return;

const jsonString = timetableItemsToJson(selectedTimeTableIdsFromClick, timetableItems);
const blob = new Blob([jsonString], { type: 'text/plain' });
const clipboardItem = new ClipboardItem({ [blob.type]: blob });
navigator.clipboard.write([clipboardItem]);
};

export const exportTimetableItems = (
selectedTimeTableIdsFromClick: TimetableItemId[],
timetableItems: TimetableItem[]
) => {
if (!timetableItems) return;

const jsonString = timetableItemsToJson(selectedTimeTableIdsFromClick, timetableItems);
const blob = new Blob([jsonString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
Expand Down
Loading