From 20721f3e8483694eea4501d8e7fc53abf52f8710 Mon Sep 17 00:00:00 2001 From: Alice Khoudli Date: Fri, 18 Oct 2024 17:11:18 +0200 Subject: [PATCH] front: update column styles in time stops table Signed-off-by: Alice Khoudli --- front/public/locales/en/timesStops.json | 2 +- front/public/locales/fr/timesStops.json | 2 +- .../modules/timesStops/TimesStopsOutput.tsx | 3 +- .../timesStops/hooks/useTimeStopsColumns.ts | 0 .../timesStops/hooks/useTimeStopsColumns.tsx | 186 ++++++++++++++++++ .../timesStops/styles/_readOnlyTime.scss | 1 - .../tests/011-op-times-and-stops-tab.spec.ts | 2 +- .../expectedInputsCellsData.json | 6 +- .../timesAndStops/updatedInputsCellsData.json | 4 +- .../tests/pages/op-output-table-page-model.ts | 9 +- 10 files changed, 202 insertions(+), 13 deletions(-) delete mode 100644 front/src/modules/timesStops/hooks/useTimeStopsColumns.ts create mode 100644 front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx diff --git a/front/public/locales/en/timesStops.json b/front/public/locales/en/timesStops.json index 3817aadb93f..93727fbccfc 100644 --- a/front/public/locales/en/timesStops.json +++ b/front/public/locales/en/timesStops.json @@ -9,7 +9,7 @@ "diffMargins": "margins diff. (s)", "diffMarginsFull": "margins difference (s)", "name": "CI", - "ch": "ch", + "ch": "CH", "noPathLoaded": "No path loaded", "realMargin": "real margin (s)", "receptionOnClosedSignal": "r. closed signal", diff --git a/front/public/locales/fr/timesStops.json b/front/public/locales/fr/timesStops.json index 61e120e2e1c..fa8b83ea6c4 100644 --- a/front/public/locales/fr/timesStops.json +++ b/front/public/locales/fr/timesStops.json @@ -9,7 +9,7 @@ "diffMargins": "diff. marges (s)", "diffMarginsFull": "différence entre les marges (s)", "name": "CI", - "ch": "ch", + "ch": "CH", "noPathLoaded": "Aucun chemin chargé", "realMargin": "marge réelle (s)", "receptionOnClosedSignal": "r. s. signal fermé", diff --git a/front/src/modules/timesStops/TimesStopsOutput.tsx b/front/src/modules/timesStops/TimesStopsOutput.tsx index a33031616a5..5a535ae2195 100644 --- a/front/src/modules/timesStops/TimesStopsOutput.tsx +++ b/front/src/modules/timesStops/TimesStopsOutput.tsx @@ -48,7 +48,7 @@ const TimesStopsOutput = ({ { + cellClassName={({ rowData: rowData_, columnId }) => { const rowData = rowData_ as TimeStopsRow; const arrivalScheduleNotRespected = rowData.arrival?.time ? rowData.calculatedArrival !== rowData.arrival.time @@ -57,6 +57,7 @@ const TimesStopsOutput = ({ return cx({ 'warning-schedule': arrivalScheduleNotRespected, 'warning-margin': negativeDiffMargins, + 'secondary-code-column': columnId === 'ch', }); }} headerRowHeight={40} diff --git a/front/src/modules/timesStops/hooks/useTimeStopsColumns.ts b/front/src/modules/timesStops/hooks/useTimeStopsColumns.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx b/front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx new file mode 100644 index 00000000000..975824bbd67 --- /dev/null +++ b/front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx @@ -0,0 +1,186 @@ +import { useMemo } from 'react'; + +import cx from 'classnames'; +import { keyColumn, type Column, checkboxColumn, createTextColumn } from 'react-datasheet-grid'; +import type { CellComponent } from 'react-datasheet-grid/dist/types'; +import { useTranslation } from 'react-i18next'; + +import { marginRegExValidation } from '../consts'; +import { disabledTextColumn } from '../helpers/utils'; +import ReadOnlyTime from '../ReadOnlyTime'; +import TimeInput from '../TimeInput'; +import { TableType, type TimeExtraDays, type TimeStopsRow } from '../types'; + +const timeColumn = (isOutputTable: boolean) => + ({ + component: (isOutputTable ? ReadOnlyTime : TimeInput) as CellComponent< + TimeExtraDays | undefined, + string + >, + deleteValue: () => undefined, + copyValue: ({ rowData }) => rowData?.time ?? null, + pasteValue: ({ value }) => ({ time: value }), + minWidth: isOutputTable ? 110 : 170, + isCellEmpty: ({ rowData }) => !rowData, + }) as Partial>; + +const fixedWidth = (width: number) => ({ minWidth: width, maxWidth: width }); + +function headerWithTitleTagIfShortened(shortenedHeader: string, fullHeader: string) { + if (shortenedHeader === fullHeader) return fullHeader; + return {shortenedHeader} ; +} + +export const useTimeStopsColumns = ( + tableType: TableType, + allWaypoints: T[] = [] +) => { + const { t } = useTranslation('timesStops'); + + const columns = useMemo[]>(() => { + const isOutputTable = tableType === TableType.Output; + const extraOutputColumns = ( + isOutputTable + ? [ + { + ...disabledTextColumn('theoreticalMarginSeconds', t('theoreticalMarginSeconds'), { + alignRight: true, + }), + headerClassName: 'padded-header', + ...fixedWidth(90), + }, + { + ...disabledTextColumn('calculatedMargin', t('realMargin'), { alignRight: true }), + headerClassName: 'padded-header', + ...fixedWidth(90), + }, + { + ...disabledTextColumn('diffMargins', t('diffMargins'), { alignRight: true }), + title: headerWithTitleTagIfShortened(t('diffMargins'), t('diffMarginsFull')), + headerClassName: 'padded-header', + ...fixedWidth(90), + }, + { + ...disabledTextColumn('calculatedArrival', t('calculatedArrivalTime')), + headerClassName: 'padded-header', + ...fixedWidth(105), + }, + { + ...disabledTextColumn('calculatedDeparture', t('calculatedDepartureTime')), + title: headerWithTitleTagIfShortened( + t('calculatedDepartureTime'), + t('calculatedDepartureTimeFull') + ), + headerClassName: 'padded-header', + ...fixedWidth(105), + }, + ] + : [] + ) as Column[]; + + return [ + { + ...keyColumn('name', createTextColumn()), + title: t('name'), + ...(isOutputTable && { + component: ({ rowData }) => ( + + {rowData.name} + + ), + }), + disabled: true, + minWidth: isOutputTable ? undefined : 300, + maxWidth: isOutputTable ? 250 : undefined, + }, + { + ...keyColumn('ch', createTextColumn()), + title: t('ch'), + disabled: true, + ...fixedWidth(45), + }, + { + ...keyColumn('arrival', timeColumn(isOutputTable)), + title: t('arrivalTime'), + headerClassName: 'padded-header', + ...fixedWidth(isOutputTable ? 105 : 125), + + // We should not be able to edit the arrival time of the origin + disabled: ({ rowIndex }) => isOutputTable || rowIndex === 0, + }, + { + ...keyColumn( + 'stopFor', + createTextColumn({ + continuousUpdates: false, + alignRight: true, + }) + ), + title: t('stopTime'), + headerClassName: 'padded-header', + disabled: isOutputTable, + ...fixedWidth(80), + }, + { + ...keyColumn('departure', timeColumn(isOutputTable)), + title: headerWithTitleTagIfShortened(t('departureTime'), t('departureTimeFull')), + headerClassName: 'padded-header', + ...fixedWidth(isOutputTable ? 105 : 125), + + // We should not be able to edit the departure time of the origin + disabled: ({ rowIndex }) => isOutputTable || rowIndex === 0, + }, + { + ...keyColumn('onStopSignal', checkboxColumn as Partial>), + title: headerWithTitleTagIfShortened( + t('receptionOnClosedSignal'), + t('receptionOnClosedSignalFull') + ), + headerClassName: 'padded-header', + ...fixedWidth(81), + + // We should not be able to edit the reception on close signal if stopFor is not filled + // except for the destination + disabled: ({ rowData, rowIndex }) => + isOutputTable || (rowIndex !== allWaypoints.length - 1 && !rowData.stopFor), + }, + { + ...keyColumn('shortSlipDistance', checkboxColumn as Partial>), + title: t('shortSlipDistance'), + headerClassName: 'padded-header', + ...fixedWidth(81), + disabled: ({ rowData, rowIndex }) => + isOutputTable || (rowIndex !== allWaypoints.length - 1 && !rowData.onStopSignal), + }, + { + ...keyColumn( + 'theoreticalMargin', + createTextColumn({ + continuousUpdates: false, + placeholder: !isOutputTable ? t('theoreticalMarginPlaceholder') : '', + formatBlurredInput: (value) => { + if (!value || value === '0%') return ''; + if (!isOutputTable && !marginRegExValidation.test(value)) { + return `${value}${t('theoreticalMarginPlaceholder')}`; + } + return value; + }, + alignRight: true, + }) + ), + cellClassName: ({ rowData }) => + cx({ invalidCell: !isOutputTable && !rowData.isMarginValid }), + title: t('theoreticalMargin'), + headerClassName: 'padded-header', + minWidth: 100, + maxWidth: 130, + disabled: ({ rowIndex }) => isOutputTable || rowIndex === allWaypoints.length - 1, + }, + ...extraOutputColumns, + ] as Column[]; + }, [tableType, t, allWaypoints.length]); + + return columns; +}; + +export default timeColumn; diff --git a/front/src/modules/timesStops/styles/_readOnlyTime.scss b/front/src/modules/timesStops/styles/_readOnlyTime.scss index 7b5948f085f..0b64d80215a 100644 --- a/front/src/modules/timesStops/styles/_readOnlyTime.scss +++ b/front/src/modules/timesStops/styles/_readOnlyTime.scss @@ -1,6 +1,5 @@ .read-only-time { // using px to conform with the padding in the react-datasheet-grid library class .dsg-input padding: 0 10px; - text-align: right; width: 100%; } diff --git a/front/tests/011-op-times-and-stops-tab.spec.ts b/front/tests/011-op-times-and-stops-tab.spec.ts index c6ff37db020..4c3b80576db 100644 --- a/front/tests/011-op-times-and-stops-tab.spec.ts +++ b/front/tests/011-op-times-and-stops-tab.spec.ts @@ -110,8 +110,8 @@ test.describe('Times and Stops Tab Verification', () => { translations.name, translations.ch, translations.arrivalTime, - translations.departureTime, translations.stopTime, + translations.departureTime, translations.receptionOnClosedSignal, translations.shortSlipDistance, translations.theoreticalMargin, diff --git a/front/tests/assets/operationStudies/timesAndStops/expectedInputsCellsData.json b/front/tests/assets/operationStudies/timesAndStops/expectedInputsCellsData.json index 380f3be50df..e11dc07070c 100644 --- a/front/tests/assets/operationStudies/timesAndStops/expectedInputsCellsData.json +++ b/front/tests/assets/operationStudies/timesAndStops/expectedInputsCellsData.json @@ -5,14 +5,14 @@ }, { "row": 2, - "values": ["Mid_West_station", "BV", "11:47:40", "11:52:40", "300", "1min/100km"] + "values": ["Mid_West_station", "BV", "11:47:40", "300", "11:52:40", "1min/100km"] }, { "row": 3, - "values": ["Mid_East_station", "BV", "12:05:21", "12:07:25", "124", ""] + "values": ["Mid_East_station", "BV", "12:05:21", "124", "12:07:25", ""] }, { "row": 4, - "values": ["North_East_station", "BV", "", "", "0", ""] + "values": ["North_East_station", "BV", "", "0", "", ""] } ] diff --git a/front/tests/assets/operationStudies/timesAndStops/updatedInputsCellsData.json b/front/tests/assets/operationStudies/timesAndStops/updatedInputsCellsData.json index bb5884bd203..51c6db076b2 100644 --- a/front/tests/assets/operationStudies/timesAndStops/updatedInputsCellsData.json +++ b/front/tests/assets/operationStudies/timesAndStops/updatedInputsCellsData.json @@ -9,10 +9,10 @@ }, { "row": 3, - "values": ["Mid_East_station", "BV", "13:58:19", "13:58:40", "21", ""] + "values": ["Mid_East_station", "BV", "13:58:19", "21", "13:58:40", ""] }, { "row": 4, - "values": ["North_East_station", "BV", "", "", "0", ""] + "values": ["North_East_station", "BV", "", "0", "", ""] } ] diff --git a/front/tests/pages/op-output-table-page-model.ts b/front/tests/pages/op-output-table-page-model.ts index 7b37727329e..bfca3219166 100644 --- a/front/tests/pages/op-output-table-page-model.ts +++ b/front/tests/pages/op-output-table-page-model.ts @@ -14,7 +14,7 @@ class OperationalStudiesOutputTablePage { constructor(page: Page) { this.page = page; this.columnHeaders = page.locator( - '[class="dsg-cell dsg-cell-header"] .dsg-cell-header-container' + '.dsg-cell.dsg-cell-header:not(.dsg-cell-gutter) .dsg-cell-header-container' ); this.tableRows = page.locator('.osrd-simulation-container .time-stops-datasheet .dsg-row'); } @@ -69,9 +69,12 @@ class OperationalStudiesOutputTablePage { calculatedDeparture, ] = await Promise.all([ await OperationalStudiesOutputTablePage.getCellValue( - cells.nth(headerIndexMap[translations.name]) + cells.nth(headerIndexMap[translations.name]), + false + ), + await OperationalStudiesOutputTablePage.getCellValue( + cells.nth(headerIndexMap[translations.ch]) ), - await OperationalStudiesOutputTablePage.getCellValue(cells.nth(headerIndexMap.Ch)), await OperationalStudiesOutputTablePage.getCellValue( cells.nth(headerIndexMap[translations.arrivalTime]), false