diff --git a/front/public/locales/en/operationalStudies/scenario.json b/front/public/locales/en/operationalStudies/scenario.json
index 4dbfd2b65fa..6f8c399706c 100644
--- a/front/public/locales/en/operationalStudies/scenario.json
+++ b/front/public/locales/en/operationalStudies/scenario.json
@@ -29,6 +29,13 @@
"pacedTrain": {
"pacedTrain": "Paced train"
},
+ "pacedTrain_one": "1 service",
+ "pacedTrain_other": "{{count}} services",
+ "pacedTrain_zero": "0 service",
+ "pacedTrainCount_one": "1/$t(pacedTrain, { 'count': {{totalCount}} }) selected",
+ "pacedTrainCount_other": "{{count}}/$t(pacedTrain, { 'count': {{totalCount}} }) selected",
+ "pacedTrainCount_zero": "$t(pacedTrain, { 'count': {{totalCount}} })",
+ "pacedTrainAndTrainCount": "{{pacedTrainCount}}/$t(pacedTrain, { 'count': {{totalPacedTrainCount}} }) and {{trainCount}}/$t(train, { 'count': {{totalTrainScheduleCount}} }) selected",
"scenarioCancel": "Cancel",
"scenarioCreateButton": "Create a scenario",
"scenarioCreationTitle": "Create a scenario",
@@ -72,6 +79,7 @@
"simulation_failed": "Simulation failed"
},
"invalidTrains": "Some trains are invalid",
+ "noItem": "No item",
"noSpeedLimitTags": "Without code",
"noSpeedLimitTagsShort": "None",
"noTrain": "No train",
@@ -95,8 +103,8 @@
"validityFilter": "Trains validity"
},
"toggleTimetable": "Toggle the timetable",
- "trainCount_one": "1 out of $t(train, { 'count': {{totalCount}} }) selected",
- "trainCount_other": "{{count}} out of $t(train, { 'count': {{totalCount}} }) selected",
+ "trainCount_one": "1/$t(train, { 'count': {{totalCount}} }) selected",
+ "trainCount_other": "{{count}}/$t(train, { 'count': {{totalCount}} }) selected",
"trainCount_zero": "$t(train, { 'count': {{totalCount}} })",
"train_one": "1 train",
"train_other": "{{count}} trains",
diff --git a/front/public/locales/fr/operationalStudies/scenario.json b/front/public/locales/fr/operationalStudies/scenario.json
index 3027d5b56d3..5491cb316b4 100644
--- a/front/public/locales/fr/operationalStudies/scenario.json
+++ b/front/public/locales/fr/operationalStudies/scenario.json
@@ -28,6 +28,13 @@
"pacedTrain": {
"pacedTrain": "Mission"
},
+ "pacedTrain_one": "1 mission",
+ "pacedTrain_other": "{{count}} missions",
+ "pacedTrain_zero": "0 mission",
+ "pacedTrainCount_one": "1/$t(pacedTrain, { 'count': {{totalCount}} }) sélectionnée",
+ "pacedTrainCount_other": "{{count}}/$t(pacedTrain, { 'count': {{totalCount}} }) sélectionnées",
+ "pacedTrainCount_zero": "$t(pacedTrain, { 'count': {{totalCount}} })",
+ "pacedTrainAndTrainCount": "{{pacedTrainCount}}/$t(pacedTrain, { 'count': {{totalPacedTrainCount}} }) et {{trainCount}}/$t(train, { 'count': {{totalTrainScheduleCount}} }) sélectionnés",
"scenarioCancel": "Annuler",
"scenarioCreateButton": "Créer le scénario",
"scenarioCreationTitle": "Créer un scénario",
@@ -71,6 +78,7 @@
"simulation_failed": "Simulation impossible"
},
"invalidTrains": "Certains trains sont invalides",
+ "noItem": "Aucun item",
"noSpeedLimitTags": "Sans code",
"noSpeedLimitTagsShort": "Aucun",
"noTrain": "Aucun train",
@@ -94,8 +102,8 @@
"validityFilter": "Validité des trains"
},
"toggleTimetable": "Basculer l'affichage de la grille horaire",
- "trainCount_one": "1 sélectionné sur $t(train, { 'count': {{totalCount}} })",
- "trainCount_other": "{{count}} sélectionnés sur $t(train, { 'count': {{totalCount}} })",
+ "trainCount_one": "1/$t(train, { 'count': {{totalCount}} }) sélectionné",
+ "trainCount_other": "{{count}}/$t(train, { 'count': {{totalCount}} }) sélectionnés",
"trainCount_zero": "$t(train, { 'count': {{totalCount}} })",
"train_one": "1 train",
"train_other": "{{count}} trains",
diff --git a/front/src/modules/trainschedule/components/Timetable/PacedTrain/PacedTrainItem.tsx b/front/src/modules/trainschedule/components/Timetable/PacedTrain/PacedTrainItem.tsx
index e9c58aac0c6..91a93dd9b9b 100644
--- a/front/src/modules/trainschedule/components/Timetable/PacedTrain/PacedTrainItem.tsx
+++ b/front/src/modules/trainschedule/components/Timetable/PacedTrain/PacedTrainItem.tsx
@@ -90,9 +90,9 @@ const PacedTrainItem = ({
- {!pacedTrain.invalidReason && (
+ {!pacedTrain.invalidReason ? (
- {pacedTrain.isValid &&
—{` ${ms2min(pacedTrainCadence.ms)}min`}
}
+ {pacedTrain.isValid &&
— {`${ms2min(pacedTrainCadence.ms)}min`}
}
+ ) : (
+
+
+ {t(`timetable.invalid.${pacedTrain.invalidReason}`)}
+
+
)}
void;
@@ -68,10 +73,11 @@ const Timetable = ({
const [displayedTimetableItems, setDisplayedTimetableItems] = useState([]);
const [conflictsListExpanded, setConflictsListExpanded] = useState(false);
- const [selectedTimetableItemIds, setSelectedTimetableItemIds] = useState<{
- trainScheduleIds: TrainScheduleId[];
- pacedTrainIds: PacedTrainId[];
- }>({ trainScheduleIds: [], pacedTrainIds: [] });
+ const [selectedTimetableItemIdsByType, setSelectedTimetableItemIdsByType] =
+ useState({
+ selectedTrainScheduleIds: [],
+ selectedPacedTrainIds: [],
+ });
const [showTrainDetails, setShowTrainDetails] = useState(false);
const [timetableItems, setTimetableItems] = useState([]);
const selectedTrainId = useSelector(getSelectedTrainId);
@@ -84,7 +90,7 @@ const Timetable = ({
const removeAndUnselectTrains = useCallback((trainIds: TimetableItemId[]) => {
removeTrains(trainIds);
- setSelectedTimetableItemIds({ trainScheduleIds: [], pacedTrainIds: [] });
+ setSelectedTimetableItemIdsByType({ selectedTrainScheduleIds: [], selectedPacedTrainIds: [] });
dtoImport();
}, []);
@@ -94,8 +100,8 @@ const Timetable = ({
const handleSelectTimetableItem = useCallback(
(id: TimetableItemId) => {
- const itemType = isTrainSchedule(id) ? 'trainScheduleIds' : 'pacedTrainIds';
- const currentSelectedTrainIds: TimetableItemId[] = selectedTimetableItemIds[itemType];
+ const itemType = isTrainSchedule(id) ? 'selectedTrainScheduleIds' : 'selectedPacedTrainIds';
+ const currentSelectedTrainIds: TimetableItemId[] = selectedTimetableItemIdsByType[itemType];
const index = currentSelectedTrainIds.indexOf(id as TrainScheduleId);
if (index === -1) {
@@ -104,12 +110,12 @@ const Timetable = ({
currentSelectedTrainIds.splice(index, 1);
}
- setSelectedTimetableItemIds({
- ...selectedTimetableItemIds,
+ setSelectedTimetableItemIdsByType({
+ ...selectedTimetableItemIdsByType,
[itemType]: currentSelectedTrainIds,
});
},
- [selectedTimetableItemIds]
+ [selectedTimetableItemIdsByType]
);
const handleConflictClick = (conflict: Conflict) => {
@@ -136,7 +142,7 @@ const Timetable = ({
});
}, [currentDepartureDates]);
- const selectTimeTableItemToEdit = (itemToEdit: TimetableItemResult) => {
+ const selectTimetableItemToEdit = (itemToEdit: TimetableItemResult) => {
dispatch(selectTrainToEdit(itemToEdit));
// TODO Paced train : Adapt this to handle paced trains in issue https://github.com/OpenRailAssociation/osrd/issues/10615
setItemIdToEdit(itemToEdit.id);
@@ -194,14 +200,14 @@ const Timetable = ({
timetableItems={timetableItems}
displayedTimetableItems={displayedTimetableItems}
setDisplayedTimetableItems={setDisplayedTimetableItems}
- selectedTimetableItemIds={selectedTimetableItemIds}
- setSelectedTimetableItemIds={setSelectedTimetableItemIds}
+ selectedTimetableItemIdsByType={selectedTimetableItemIdsByType}
+ setSelectedTimetableItemIdsByType={setSelectedTimetableItemIdsByType}
removeTrains={removeAndUnselectTrains}
trainSchedules={trainSchedules}
isInSelection={
[
- ...selectedTimetableItemIds.pacedTrainIds,
- ...selectedTimetableItemIds.trainScheduleIds,
+ ...selectedTimetableItemIdsByType.selectedPacedTrainIds,
+ ...selectedTimetableItemIdsByType.selectedTrainScheduleIds,
].length > 0
}
/>
@@ -217,7 +223,7 @@ const Timetable = ({
https://github.com/OpenRailAssociation/osrd/issues/10615 */}
{isTrainSchedule(timetableItem.id) ? (
void;
- selectedTimetableItemIds: {
- trainScheduleIds: TrainScheduleId[];
- pacedTrainIds: PacedTrainId[];
- };
- setSelectedTimetableItemIds: (selectedTimetableIds: {
- trainScheduleIds: TrainScheduleId[];
- pacedTrainIds: PacedTrainId[];
- }) => void;
+ selectedTimetableItemIdsByType: SelectedTimetableIdsByType;
+ setSelectedTimetableItemIdsByType: (selectedTimetableIds: SelectedTimetableIdsByType) => void;
removeTrains: (trainIds: TimetableItemId[]) => void;
trainSchedules: TrainScheduleResultWithTrainId[];
isInSelection: boolean;
@@ -53,13 +52,13 @@ const TimetableToolbar = ({
timetableItems,
displayedTimetableItems,
setDisplayedTimetableItems,
- selectedTimetableItemIds,
- setSelectedTimetableItemIds,
+ selectedTimetableItemIdsByType,
+ setSelectedTimetableItemIdsByType,
removeTrains,
trainSchedules,
isInSelection,
}: TimetableToolbarProps) => {
- const { t } = useTranslation(['operationalStudies/scenario', 'common/itemTypes']);
+ const { t } = useTranslation(['operationalStudies/scenario', 'common/itemTypes', 'translation']);
const dispatch = useAppDispatch();
const { openModal } = useContext(ModalContext);
@@ -74,14 +73,30 @@ const TimetableToolbar = ({
useState('both');
const [selectedTags, setSelectedTags] = useState>(new Set());
+ const { totalPacedTrainCount, totalTrainScheduleCount } = useMemo(
+ () =>
+ timetableItems.reduce(
+ (acc, { id }) => {
+ if (isTrainSchedule(id)) {
+ acc.totalTrainScheduleCount += 1;
+ } else {
+ acc.totalPacedTrainCount += 1;
+ }
+ return acc;
+ },
+ { totalPacedTrainCount: 0, totalTrainScheduleCount: 0 }
+ ),
+ [timetableItems]
+ );
+
const debouncedFilter = useDebounce(filter, 500);
const debouncedRollingstockFilter = useDebounce(rollingStockFilter, 500);
const [deleteTrainSchedules] = osrdEditoastApi.endpoints.deleteTrainSchedule.useMutation();
- const { trainScheduleIds, pacedTrainIds } = selectedTimetableItemIds;
- const timetableItemIds = [...trainScheduleIds, ...pacedTrainIds];
+ const { selectedTrainScheduleIds, selectedPacedTrainIds } = selectedTimetableItemIdsByType;
+ const selectedTimetableItemIds = [...selectedTrainScheduleIds, ...selectedPacedTrainIds];
// TODO: move this hook in Timetable
const { uniqueTags } = useFilterTrainSchedules(
@@ -99,43 +114,49 @@ const TimetableToolbar = ({
};
const toggleAllTrainsSelecton = () => {
- if (displayedTimetableItems.length === [...trainScheduleIds, ...pacedTrainIds].length) {
- setSelectedTimetableItemIds({ trainScheduleIds: [], pacedTrainIds: [] });
+ if (displayedTimetableItems.length === selectedTimetableItemIds.length) {
+ setSelectedTimetableItemIdsByType({
+ selectedTrainScheduleIds: [],
+ selectedPacedTrainIds: [],
+ });
} else {
const timetableItemsDisplayed = displayedTimetableItems.reduce(
(acc, { id }) => {
if (isTrainSchedule(id)) {
- acc.trainScheduleIds.push(id);
+ acc.selectedTrainScheduleIds.push(id);
} else {
- acc.pacedTrainIds.push(id);
+ acc.selectedPacedTrainIds.push(id);
}
return acc;
},
- { trainScheduleIds: [] as TrainScheduleId[], pacedTrainIds: [] as PacedTrainId[] }
+ {
+ selectedTrainScheduleIds: [] as TrainScheduleId[],
+ selectedPacedTrainIds: [] as PacedTrainId[],
+ }
);
- setSelectedTimetableItemIds(timetableItemsDisplayed);
+ setSelectedTimetableItemIdsByType(timetableItemsDisplayed);
}
};
const handleTrainsDelete = async () => {
- const itemsCount = timetableItemIds.length;
+ const itemsCount = selectedTimetableItemIds.length;
// TODO Paced train : Adapt this to handle delete paced trains in issue https://github.com/OpenRailAssociation/osrd/issues/10615
- if (selectedTrainId && timetableItemIds.includes(selectedTrainId as TrainScheduleId)) {
+ if (selectedTrainId && selectedTimetableItemIds.includes(selectedTrainId as TrainScheduleId)) {
// we need to set selectedTrainId to undefined, otherwise just after the delete,
// some unvalid rtk calls are dispatched (see rollingstock request in SimulationResults)
dispatch(updateSelectedTrainId(undefined));
}
// TODO Paced train : Adapt this to handle delete paced trains in issue https://github.com/OpenRailAssociation/osrd/issues/10615
- const editoastSelectedTrainScheduleIds = timetableItemIds
+ const editoastSelectedTrainScheduleIds = selectedTimetableItemIds
.filter(isTrainSchedule)
.map((id) => formatTrainScheduleIdToEditoastTrainId(id));
await deleteTrainSchedules({ body: { ids: editoastSelectedTrainScheduleIds } })
.unwrap()
.then(() => {
- removeTrains(trainScheduleIds);
+ removeTrains(selectedTrainScheduleIds);
dispatch(
setSuccess({
title: t('timetable.trainsSelectionDeletedCount', { count: itemsCount }),
@@ -145,7 +166,10 @@ const TimetableToolbar = ({
})
.catch((e) => {
// TODO Paced train : Adapt this to handle delete paced trains in issue https://github.com/OpenRailAssociation/osrd/issues/10615
- if (selectedTrainId && timetableItemIds.includes(selectedTrainId as TrainScheduleId)) {
+ if (
+ selectedTrainId &&
+ selectedTimetableItemIds.includes(selectedTrainId as TrainScheduleId)
+ ) {
dispatch(updateSelectedTrainId(selectedTrainId));
} else {
dispatch(setFailure(castErrorToFailure(e)));
@@ -171,6 +195,45 @@ const TimetableToolbar = ({
a.click();
};
+ const computedItemLabel = (trainSchedulesCount: number, pacedTrainCount: number) => {
+ if (trainSchedulesCount === 0 && pacedTrainCount === 0) return t('timetable.noItem');
+
+ const pacedTrainLabel = t('pacedTrainCount', {
+ count: selectedPacedTrainIds.length,
+ totalCount: totalPacedTrainCount,
+ });
+
+ const trainScheduleLabel = t('trainCount', {
+ count: selectedTrainScheduleIds.length,
+ totalCount: totalTrainScheduleCount,
+ });
+
+ if (
+ trainSchedulesCount === 0 ||
+ (selectedPacedTrainIds.length > 0 && selectedTrainScheduleIds.length === 0)
+ ) {
+ return pacedTrainLabel;
+ }
+
+ if (
+ pacedTrainCount === 0 ||
+ (selectedTrainScheduleIds.length > 0 && selectedPacedTrainIds.length === 0)
+ ) {
+ return trainScheduleLabel;
+ }
+
+ if (selectedTrainScheduleIds.length > 0 && selectedPacedTrainIds.length > 0) {
+ return t('pacedTrainAndTrainCount', {
+ pacedTrainCount: selectedPacedTrainIds.length,
+ totalPacedTrainCount,
+ trainCount: selectedTrainScheduleIds.length,
+ totalTrainScheduleCount,
+ });
+ }
+
+ return `${pacedTrainLabel}\u00A0${t('translation:common.and')}\u00A0${trainScheduleLabel}`;
+ };
+
return (
<>
0
+ selectedTimetableItemIds.length === timetableItems.length &&
+ selectedTimetableItemIds.length > 0
}
isIndeterminate={
- timetableItemIds.length !== timetableItems.length && timetableItemIds.length > 0
+ selectedTimetableItemIds.length !== timetableItems.length &&
+ selectedTimetableItemIds.length > 0
}
onChange={() => toggleAllTrainsSelecton()}
/>
@@ -218,7 +280,7 @@ const TimetableToolbar = ({
)}
- {timetableItemIds.length > 0 && (
+ {selectedTimetableItemIds.length > 0 && (
,
'sm'
)
@@ -241,7 +303,7 @@ const TimetableToolbar = ({
title={t('timetable.exportSelection')}
type="button"
// TODO PACED TRAIN: https://github.com/OpenRailAssociation/osrd/issues/10614
- onClick={() => exportTrainSchedules(trainScheduleIds)}
+ onClick={() => exportTrainSchedules(selectedTrainScheduleIds)}
/>
)}
diff --git a/front/src/modules/trainschedule/components/Timetable/types.ts b/front/src/modules/trainschedule/components/Timetable/types.ts
index 98facd0c2f9..c62d6c3f70b 100644
--- a/front/src/modules/trainschedule/components/Timetable/types.ts
+++ b/front/src/modules/trainschedule/components/Timetable/types.ts
@@ -14,7 +14,7 @@ export type ScheduledPointsHonoredFilter = 'both' | 'honored' | 'notHonored';
type SimulationSummaryResultSuccess = Extract;
-export type TimetableItemWithDetails = Omit<
+type TimetableItemWithDetails = Omit<
TrainScheduleResult,
'id' | 'train_name' | 'rolling_stock_name' | 'timetable_id' | 'start_time'
> & {
@@ -57,3 +57,8 @@ export type PacedTrainWithResult = TimetableItemWithDetails & {
};
export type TimetableItemResult = TrainScheduleWithDetails | PacedTrainWithResult;
+
+export type SelectedTimetableIdsByType = {
+ selectedTrainScheduleIds: TrainScheduleId[];
+ selectedPacedTrainIds: PacedTrainId[];
+};
diff --git a/front/src/styles/scss/applications/operationalStudies/_pacedTrain.scss b/front/src/styles/scss/applications/operationalStudies/_pacedTrain.scss
index a4e0c597084..410450998bb 100644
--- a/front/src/styles/scss/applications/operationalStudies/_pacedTrain.scss
+++ b/front/src/styles/scss/applications/operationalStudies/_pacedTrain.scss
@@ -20,7 +20,7 @@
}
}
- &:has(.paced-train-right-zone:hover, .action-buttons:hover):not(.modified) {
+ &:hover:not(.modified) {
.action-buttons {
display: flex !important;
z-index: 1;
@@ -35,12 +35,6 @@
z-index: 3;
}
- &.invalid {
- .checkbox-title {
- min-width: auto !important;
- }
- }
-
.paced-train-main-info {
display: flex;
margin-inline: 9px 24px;
@@ -144,4 +138,16 @@
height: 0;
}
}
+
+ .invalid-reason {
+ margin-right: 18px;
+ font-size: 16px;
+ color: var(--error80);
+ font-weight: 600;
+ min-width: 155px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ line-height: 24px;
+ }
}
diff --git a/front/src/styles/scss/applications/operationalStudies/_scenario.scss b/front/src/styles/scss/applications/operationalStudies/_scenario.scss
index 3ba87465a1e..1340dd2a521 100644
--- a/front/src/styles/scss/applications/operationalStudies/_scenario.scss
+++ b/front/src/styles/scss/applications/operationalStudies/_scenario.scss
@@ -655,15 +655,11 @@
background-image: url('data:image/svg+xml,%3Csvg version="1.1" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"%3E%3Ctitle%3EAssets/TrainList/InvalidTrain%3C/title%3E%3Cg fill-rule="evenodd"%3E%3Crect width="48" height="48" fill="%23FFEEED"/%3E%3Cpath d="m48 26v22h-22l22-22zm0-26-48 48v-22l26-26h22z" fill="%23FF6868" opacity=".1"/%3E%3C/g%3E%3C/svg%3E');
background-repeat: repeat;
box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, 0.25);
- .checkbox-title {
- min-width: 0;
- }
+
.checkbox-title::after {
display: none;
}
.rolling-stock {
- min-width: max-content;
- white-space: nowrap;
font-weight: 600;
color: var(--error80);
}