diff --git a/front/public/locales/en/operationalStudies/scenario.json b/front/public/locales/en/operationalStudies/scenario.json index 838428701fb..6856daa7f3e 100644 --- a/front/public/locales/en/operationalStudies/scenario.json +++ b/front/public/locales/en/operationalStudies/scenario.json @@ -35,32 +35,35 @@ "scenarioUpdatedDetails": "Scenario {{ name }} has been updated.", "timetable": { "addTrainSchedule": "Add", + "advancedFilterLabel": "Rollingstock", "choosePath": "Use this path", + "compositionCodes": "Composition codes", "copy": "copy", "delete": "Delete", "deleteSelection": "Delete selected trains", "downloadSelection": "Download selected trains", "duplicate": "Clone", + "filterLabel": "Name, label", "importTrainSchedule": "Import", "invalidTrains": "The timetable contains invalid trains.", + "noSpeedLimitTags": "Without code", + "rollingStockFilterPlaceholder": "Family, detail, serie", + "scheduledPointsHonoredFilter": "Scheduled points honored", + "showAllTrains": "All", + "showHonoredTrains": "Honored", + "showInvalidTrains": "Invalid", + "showNotHonoredTrains": "Not honored", + "showValidTrains": "Valid", "stopsCount_one": "1 stop", "stopsCount_other": "{{ count }} stops", + "toggleFilters": "Toggle filters display", + "toggleMultiSelection": "Toggle multiselection", "trainAdded": "Train added", "trainDeleted": "{{ name }} train has been deleted.", - "update": "Edit", - "toggleMultiSelection": "Toggle multiselection", "trainsSelectionDeletedCount_one": "The train has been deleted.", "trainsSelectionDeletedCount_other": "The {{count}} trains have been deleted.", - "noSpeedLimitTags": "Without code", - "showValidTrains": "Valid", - "showInvalidTrains": "Invalid", - "showAllTrains": "All", - "compositionCodes": "Composition codes", - "filterLabel": "Name, label", - "advancedFilterLabel": "Rollingstock", - "rollingStockFilterPlaceholder": "Family, detail, serie", - "validityFilter": "Trains validity", - "toggleFilters": "Toggle filters display" + "update": "Edit", + "validityFilter": "Trains validity" }, "toggleTimetable": "Toggle the timetable", "trainCount_one": "1 train", diff --git a/front/public/locales/fr/operationalStudies/scenario.json b/front/public/locales/fr/operationalStudies/scenario.json index 9c2ef3c6d49..ab6d4ab57d5 100644 --- a/front/public/locales/fr/operationalStudies/scenario.json +++ b/front/public/locales/fr/operationalStudies/scenario.json @@ -34,32 +34,35 @@ "scenarioUpdatedDetails": "Le scénario {{ name }} a bien été mis à jour.", "timetable": { "addTrainSchedule": "Ajouter", + "advancedFilterLabel": "Engin moteur", "choosePath": "Utiliser ce chemin", + "compositionCodes": "Codes de composition", "copy": "copie", "delete": "Supprimer", "deleteSelection": "Supprimer les trains sélectionnés", "downloadSelection": "Télécharger les trains sélectionnés", "duplicate": "Dupliquer", + "filterLabel": "Nom, étiquette", "importTrainSchedule": "Importer", "invalidTrains": "La grille horaires comporte des trains invalides.", + "noSpeedLimitTags": "Sans code", + "rollingStockFilterPlaceholder": "Famille, détail, série", + "scheduledPointsHonoredFilter": "Points horaires honorés", + "showAllTrains": "Tous", + "showHonoredTrains": "Honorés", + "showInvalidTrains": "Invalides", + "showNotHonoredTrains": "Non honorés", + "showValidTrains": "Valides", "stopsCount_one": "1 arrêt", "stopsCount_other": "{{count}} arrêts", + "toggleFilters": "Afficher ou masquer les filtres", + "toggleMultiSelection": "Basculer le mode multisélection", "trainAdded": "Train ajouté", "trainDeleted": "Le train {{name}} a bien été supprimé.", - "update": "Modifier", - "toggleMultiSelection": "Basculer le mode multisélection", "trainsSelectionDeletedCount_one": "Le train a bien été supprimé.", "trainsSelectionDeletedCount_other": "Les {{count}} trains ont bien été supprimés.", - "noSpeedLimitTags": "Sans code", - "showValidTrains": "Valides", - "showInvalidTrains": "Invalides", - "showAllTrains": "Tous", - "compositionCodes": "Codes de composition", - "filterLabel": "Nom, étiquette", - "advancedFilterLabel": "Engin moteur", - "rollingStockFilterPlaceholder": "Famille, détail, série", - "validityFilter": "Validité des trains", - "toggleFilters": "Afficher ou masquer les filtres" + "update": "Modifier", + "validityFilter": "Validité des trains" }, "toggleTimetable": "Basculer l'affichage de la grille horaires", "trainCount_one": "1 train", diff --git a/front/src/modules/trainschedule/components/TimetableV2/FilterPanel.tsx b/front/src/modules/trainschedule/components/TimetableV2/FilterPanel.tsx index 90df04e27bc..444ba3045d2 100644 --- a/front/src/modules/trainschedule/components/TimetableV2/FilterPanel.tsx +++ b/front/src/modules/trainschedule/components/TimetableV2/FilterPanel.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import InputSNCF from 'common/BootstrapSNCF/InputSNCF'; import OptionsSNCF from 'common/BootstrapSNCF/OptionsSNCF'; -import type { ValidityFilter } from './types'; +import type { ValidityFilter, ScheduledPointsHonoredFilter } from './types'; type FilterPanelProps = { filter: string; @@ -15,6 +15,10 @@ type FilterPanelProps = { setRollingStockFilter: (rollingStockFilter: string) => void; validityFilter: ValidityFilter; setValidityFilter: (validityFilter: ValidityFilter) => void; + scheduledPointsHonoredFilter: ScheduledPointsHonoredFilter; + setScheduledPointsHonoredFilter: ( + scheduledPointsHonoredFilter: ScheduledPointsHonoredFilter + ) => void; uniqueTags: string[]; selectedTags: Set; setSelectedTags: React.Dispatch>>; @@ -27,6 +31,8 @@ const FilterPanel = ({ setRollingStockFilter, validityFilter, setValidityFilter, + scheduledPointsHonoredFilter, + setScheduledPointsHonoredFilter, uniqueTags, selectedTags, setSelectedTags, @@ -39,6 +45,12 @@ const FilterPanel = ({ { value: 'invalid', label: t('timetable.showInvalidTrains') }, ]; + const scheduledPointsHonoredOptions: { value: ScheduledPointsHonoredFilter; label: string }[] = [ + { value: 'both', label: t('timetable.showAllTrains') }, + { value: 'honored', label: t('timetable.showHonoredTrains') }, + { value: 'notHonored', label: t('timetable.showNotHonoredTrains') }, + ]; + const toggleTagSelection = (tag: string | null) => { setSelectedTags((prevSelectedTags) => { const newSelectedTags = new Set(prevSelectedTags); @@ -97,6 +109,21 @@ const FilterPanel = ({ /> + +
+ + setScheduledPointsHonoredFilter(event.target.value as ScheduledPointsHonoredFilter) + } + options={scheduledPointsHonoredOptions} + name="schedule-point-honored" + selectedValue={scheduledPointsHonoredFilter} + /> +
+ +
{uniqueTags.map((tag) => { diff --git a/front/src/modules/trainschedule/components/TimetableV2/TimetableToolbar.tsx b/front/src/modules/trainschedule/components/TimetableV2/TimetableToolbar.tsx index 24e50d8f1a1..0e210098f65 100644 --- a/front/src/modules/trainschedule/components/TimetableV2/TimetableToolbar.tsx +++ b/front/src/modules/trainschedule/components/TimetableV2/TimetableToolbar.tsx @@ -20,7 +20,11 @@ import { useDebounce } from 'utils/helpers'; import FilterPanel from './FilterPanel'; import useTrainSchedulesDetails from './hooks'; -import type { TrainScheduleWithDetails, ValidityFilter } from './types'; +import type { + ScheduledPointsHonoredFilter, + TrainScheduleWithDetails, + ValidityFilter, +} from './types'; type TimetableToolbarProps = { trainIds: number[]; @@ -56,9 +60,12 @@ const TimetableToolbar = ({ const [filter, setFilter] = useState(''); const [rollingStockFilter, setRollingStockFilter] = useState(''); const [validityFilter, setValidityFilter] = useState('both'); + const [scheduledPointsHonoredFilter, setScheduledPointsHonoredFilter] = + useState('both'); const [selectedTags, setSelectedTags] = useState>(new Set()); const debouncedFilter = useDebounce(filter, 500); + const debouncedRollingstockFilter = useDebounce(rollingStockFilter, 500); const [deleteTrainSchedules] = osrdEditoastApi.endpoints.deleteV2TrainSchedule.useMutation(); @@ -69,6 +76,7 @@ const TimetableToolbar = ({ debouncedFilter, debouncedRollingstockFilter, validityFilter, + scheduledPointsHonoredFilter, selectedTags ); @@ -210,6 +218,8 @@ const TimetableToolbar = ({ setRollingStockFilter={setRollingStockFilter} validityFilter={validityFilter} setValidityFilter={setValidityFilter} + scheduledPointsHonoredFilter={scheduledPointsHonoredFilter} + setScheduledPointsHonoredFilter={setScheduledPointsHonoredFilter} uniqueTags={uniqueTags} selectedTags={selectedTags} setSelectedTags={setSelectedTags} diff --git a/front/src/modules/trainschedule/components/TimetableV2/TimetableTrainCardV2.tsx b/front/src/modules/trainschedule/components/TimetableV2/TimetableTrainCardV2.tsx index 9c52d226c4c..46a305c0687 100644 --- a/front/src/modules/trainschedule/components/TimetableV2/TimetableTrainCardV2.tsx +++ b/front/src/modules/trainschedule/components/TimetableV2/TimetableTrainCardV2.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Alert, Pencil, Trash } from '@osrd-project/ui-icons'; +import { Alert, Pencil, Trash, Clock } from '@osrd-project/ui-icons'; import cx from 'classnames'; import { omit } from 'lodash'; import { useTranslation } from 'react-i18next'; @@ -216,9 +216,20 @@ const TimetableTrainCardV2 = ({ )}
-
-
{train.startTime}
-
{train.arrivalTime}
+
+ {train.scheduledPointsNotHonored && ( +
+ +
+ )} +
+
{train.startTime}
+
{train.arrivalTime}
+
diff --git a/front/src/modules/trainschedule/components/TimetableV2/TimetableV2.tsx b/front/src/modules/trainschedule/components/TimetableV2/TimetableV2.tsx index 3cf377209ce..ad485a64303 100644 --- a/front/src/modules/trainschedule/components/TimetableV2/TimetableV2.tsx +++ b/front/src/modules/trainschedule/components/TimetableV2/TimetableV2.tsx @@ -134,7 +134,6 @@ const TimetableV2 = ({ {t('timetable.addTrainSchedule')}
- ) => { const infraId = useInfraID(); @@ -80,6 +82,21 @@ const useTrainSchedulesDetails = ( if (validityFilter === 'invalid' && trainSummary.status === 'success') return false; } + // Apply scheduled points honored filter + if (scheduledPointsHonoredFilter !== 'both') { + if (trainSummary.status === 'success') { + const isHonored = trainSummary.scheduled_points_honored; + if ( + (scheduledPointsHonoredFilter === 'honored' && !isHonored) || + (scheduledPointsHonoredFilter === 'notHonored' && isHonored) + ) { + return false; + } + } else { + return false; + } + } + // Apply tag filter if ( selectedTags.size > 0 && @@ -121,6 +138,7 @@ const useTrainSchedulesDetails = ( // We want to trigger this only if a filter is applied const filtereredTrainSchedules = validityFilter !== 'both' || + scheduledPointsHonoredFilter !== 'both' || selectedTags.size > 0 || debouncedRollingstockFilter || debouncedFilter @@ -146,6 +164,7 @@ const useTrainSchedulesDetails = ( duration: trainSummary.time, pathLength: formatKmValue(trainSummary.length, 'millimeters', 1), mechanicalEnergyConsumed: jouleToKwh(trainSummary.energy_consumption, true), + scheduledPointsNotHonored: !trainSummary.scheduled_points_honored, } : { arrivalTime: '', @@ -166,6 +185,7 @@ const useTrainSchedulesDetails = ( speedLimitTag: trainSchedule.speed_limit_tag ?? null, labels: trainSchedule.labels ?? [], rollingStock, + ...otherProps, }; }); @@ -180,6 +200,7 @@ const useTrainSchedulesDetails = ( debouncedFilter, debouncedRollingstockFilter, validityFilter, + scheduledPointsHonoredFilter, selectedTags, ]); diff --git a/front/src/modules/trainschedule/components/TimetableV2/types.ts b/front/src/modules/trainschedule/components/TimetableV2/types.ts index 87c853fe5d6..fb6e664d577 100644 --- a/front/src/modules/trainschedule/components/TimetableV2/types.ts +++ b/front/src/modules/trainschedule/components/TimetableV2/types.ts @@ -5,6 +5,8 @@ import type { export type ValidityFilter = 'both' | 'valid' | 'invalid'; +export type ScheduledPointsHonoredFilter = 'both' | 'honored' | 'notHonored'; + export type TrainScheduleWithDetails = { id: number; trainName: string; @@ -21,6 +23,7 @@ export type TrainScheduleWithDetails = { speedLimitTag: string | null; labels: string[]; invalidReason?: InvalidReason; + scheduledPointsNotHonored?: boolean; }; export type InvalidReason = Exclude; diff --git a/front/src/styles/scss/applications/operationalStudies/_scenario.scss b/front/src/styles/scss/applications/operationalStudies/_scenario.scss index 3e4b3105228..d565a78f505 100644 --- a/front/src/styles/scss/applications/operationalStudies/_scenario.scss +++ b/front/src/styles/scss/applications/operationalStudies/_scenario.scss @@ -270,14 +270,15 @@ .scenario-timetable-train-header { display: flex; align-items: center; - padding: 0 0.25rem; background-color: var(--coolgray3); transition: background-color 0.2s; .scenario-timetable-train-name { + padding-right: 20px; position: relative; display: flex; align-items: center; overflow: hidden; + padding-left: 0.25rem; .scenario-timetable-train-idx { display: flex; align-items: center; @@ -318,10 +319,12 @@ } } .scenario-timetable-train-times { + display: flex; + justify-content: space-between; min-width: 20%; text-align: right; - height: 1.5rem; line-height: 0.8rem; + padding-right: 0.25rem; .scenario-timetable-train-departure { font-weight: 600; font-size: 0.9rem; @@ -334,6 +337,11 @@ .V2 { min-width: 40%; } + .not-honored{ + background-color: var(--warning); + border-top: solid 2px var(--warning); + border-bottom: solid 2px var(--warning); + } } .scenario-timetable-train-body { display: flex;