Skip to content

Commit

Permalink
front: use new spreadsheet component
Browse files Browse the repository at this point in the history
  • Loading branch information
Akctarus committed Apr 3, 2024
1 parent 43ed182 commit fc0cd2e
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 169 deletions.
2 changes: 1 addition & 1 deletion front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-countdown": "^2.3.5",
"react-datasheet-grid": "^4.11.4",
"react-dom": "^18.2.0",
"react-flatpickr": "^3.10.13",
"react-hook-form": "^7.50.0",
Expand All @@ -100,7 +101,6 @@
"react-rnd": "^10.4.1",
"react-router-dom": "^6.22.0",
"react-select": "^5.8.0",
"react-spreadsheet": "^0.9.4",
"react-tether": "^3.0.3",
"react-transition-group": "^4.4.5",
"redux": "^5.0.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import React, { type Dispatch, type SetStateAction, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import type { Dispatch, SetStateAction } from 'react';

import { DataSheetGrid, keyColumn, intColumn, floatColumn } from 'react-datasheet-grid';
import 'react-datasheet-grid/dist/style.css';
import { useTranslation } from 'react-i18next';
import Spreadsheet, { createEmptyMatrix } from 'react-spreadsheet';
import type { CellBase, Matrix } from 'react-spreadsheet';

import type { ConditionalEffortCurveForm, EffortCurveForms } from 'modules/rollingStock/types';
import type {
ConditionalEffortCurveForm,
DataSheetCurve,
EffortCurveForms,
} from 'modules/rollingStock/types';
import { replaceElementAtIndex } from 'utils/array';
import { msToKmh } from 'utils/physics';

Expand All @@ -14,9 +19,9 @@ type CurveSpreadsheetProps = {
selectedCurve: ConditionalEffortCurveForm;
selectedCurveIndex: number;
selectedTractionModeCurves: ConditionalEffortCurveForm[];
effortCurves: EffortCurveForms | null;
effortCurves: EffortCurveForms;
setEffortCurves: Dispatch<SetStateAction<EffortCurveForms | null>>;
selectedTractionMode: string | null;
selectedTractionMode: string;
isDefaultCurve: boolean;
};

Expand All @@ -30,45 +35,38 @@ const CurveSpreadsheet = ({
isDefaultCurve,
}: CurveSpreadsheetProps) => {
const { t } = useTranslation('rollingstock');
const columns = useMemo(
() => [
{ ...keyColumn('speed', intColumn), title: t('speed') },
{ ...keyColumn('effort', floatColumn), title: t('effort') },
],
[t]
);

const spreadsheetCurve = useMemo(() => {
const { speeds, max_efforts } = selectedCurve.curve;
const filledMatrix: (
| {
value: string;
}
| undefined
)[][] =
speeds && max_efforts
? max_efforts.map((effort, index) => [
{
value:
speeds[index] !== undefined ? Math.round(msToKmh(speeds[index]!)).toString() : '',
},
// Effort needs to be displayed in kN
{ value: effort !== undefined ? Math.round(effort / 1000).toString() : '' },
])
: [];
const numberOfRows = filledMatrix.length < 8 ? 8 - filledMatrix.length : 1;
return filledMatrix.concat(createEmptyMatrix<CellBase<string>>(numberOfRows, 2));
}, [selectedCurve]);
const [needsSort, setNeedsSort] = useState<boolean>(false);

const updateRollingStockCurve = (e: Matrix<{ value: string }>) => {
if (!selectedTractionMode || !effortCurves) return;
const formattedCurve = formatCurve(e);
const handleBlur = useCallback(() => {
setNeedsSort(true);
}, []);

const updateRollingStockCurve = (newCurve: DataSheetCurve[]) => {
// Format the new curve
const formattedCurve = formatCurve(newCurve);

// Create the updated selected curve
const updatedSelectedCurve = {
...selectedCurve,
curve: formattedCurve,
};

// replace the updated curve
// Replace the updated curve in the selected traction mode curves
const updatedCurves = replaceElementAtIndex(
selectedTractionModeCurves,
selectedCurveIndex,
updatedSelectedCurve
);

// Update the effort curves
const updatedEffortCurve = {
...effortCurves,
[selectedTractionMode]: {
Expand All @@ -77,30 +75,56 @@ const CurveSpreadsheet = ({
...(isDefaultCurve ? { default_curve: formattedCurve } : {}),
},
};

setEffortCurves(updatedEffortCurve);
};

const orderSpreadsheetValues = () => {
const orderedValuesByVelocity = spreadsheetCurve.sort((a, b) => {
// if a row has a max_effort, but no speed, it should appear at the top of the table
if (b[0] && b[0].value === '') return 1;
return Number(a[0]?.value) - Number(b[0]?.value);
});
updateRollingStockCurve(orderedValuesByVelocity);
};
const spreadsheetCurve = useMemo(() => {
const { speeds, max_efforts } = selectedCurve.curve;

const filledDataSheet: DataSheetCurve[] = max_efforts.map((effort, index) => ({
speed: speeds[index] !== null ? Math.round(msToKmh(speeds[index]!)) : null,
// Effort needs to be displayed in kN
effort: effort && effort !== null ? effort / 1000 : null,
}));

// Add an empty line for input only if last line is not already empty
if (
filledDataSheet.length === 0 ||
filledDataSheet[filledDataSheet.length - 1].speed !== null ||
filledDataSheet[filledDataSheet.length - 1].effort !== null
) {
filledDataSheet.push({ speed: null, effort: null });
}

return filledDataSheet;
}, [selectedCurve]);

useEffect(() => {
if (needsSort) {
const sortedSpreadsheetValues = spreadsheetCurve
.filter((item) => item.speed !== null || item.effort !== null)
.sort((a, b) => {
if (a.speed === null) return -1;
if (b.speed === null) return 1;
return Number(a.speed) - Number(b.speed);
});

updateRollingStockCurve(sortedSpreadsheetValues);
setNeedsSort(false);
}
}, [needsSort]);

return (
<div className="rollingstock-editor-spreadsheet">
<Spreadsheet
data={spreadsheetCurve}
onChange={(e) => {
updateRollingStockCurve(e);
}}
onBlur={orderSpreadsheetValues}
onKeyDown={(e) => {
if (e.key === 'Enter') orderSpreadsheetValues();
}}
columnLabels={[t('speed'), t('effort')]}
<DataSheetGrid
value={spreadsheetCurve}
columns={columns}
onChange={(e) => updateRollingStockCurve(e as DataSheetCurve[])}
rowHeight={30}
addRowsComponent={false}
onBlur={handleBlur}
onSelectionChange={handleBlur}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import CurveSpreadsheet from 'modules/rollingStock/components/RollingStockEditor
import { STANDARD_COMFORT_LEVEL, THERMAL_TRACTION_IDENTIFIER } from 'modules/rollingStock/consts';
import { getElectricalProfilesAndPowerRestrictions } from 'modules/rollingStock/helpers/rollingStockEditor';
import {
filterUndefinedValueInCurve,
filterNullValueInCurve,
orderElectricalProfils,
orderSelectorList,
} from 'modules/rollingStock/helpers/utils';
Expand Down Expand Up @@ -176,7 +176,7 @@ const RollingStockEditorCurves = ({
...selectedModeCurves,
curves: matchingCurves.map((condCurve) => ({
...condCurve,
curve: filterUndefinedValueInCurve(condCurve.curve),
curve: filterNullValueInCurve(condCurve.curve),
})),
},
} as EffortCurves['modes'];
Expand Down Expand Up @@ -211,7 +211,8 @@ const RollingStockEditorCurves = ({
{selectedTractionMode &&
selectedCurve &&
selectedCurveIndex !== null &&
selectedTractionModeCurves && (
selectedTractionModeCurves &&
effortCurves && (
<div className="rollingstock-editor-curves d-flex p-3">
<CurveSpreadsheet
selectedCurve={selectedCurve}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,16 @@
import type { Matrix } from 'react-spreadsheet';

import type { EffortCurveForm } from 'modules/rollingStock/types';
import type { DataSheetCurve, EffortCurveForm } from 'modules/rollingStock/types';
import { kmhToMs } from 'utils/physics';
import { emptyStringRegex, onlyDigit } from 'utils/strings';

/** Remove rows which have at least 1 empty cell */
const filterUnvalidRows = (sheetValues: Matrix<{ value: string }>) =>
sheetValues.filter(
([a, b]) =>
(a?.value && !emptyStringRegex.test(a.value)) || (b?.value && !emptyStringRegex.test(b.value))
);

/** For each cell, filter non digit characters */
const removeNonDigitCharacters = (rows: Matrix<{ value: string }>) =>
rows.map((row) => row.map((cell) => onlyDigit((cell?.value || '').replaceAll(',', '.'))));

const formatToEffortCurve = (rows: Matrix<string>) =>
const formatCurve = (rows: DataSheetCurve[]) =>
rows.reduce<EffortCurveForm>(
(result, row) => {
result.speeds.push(row[0] !== '' ? kmhToMs(Number(row[0])) : undefined);
result.speeds.push(row.speed !== null ? kmhToMs(Number(row.speed)) : null);
// Back-end needs effort in newton
result.max_efforts.push(row[1] !== '' ? Number(row[1]) * 1000 : undefined);
result.max_efforts.push(row.effort !== null ? Number(row.effort) * 1000 : null);

return result;
},
{ max_efforts: [], speeds: [] }
);

/**
* Given a spreadsheet, return an EffortCurve
* - remove rows which have at least 1 empty cell
* - remove non digit characters in each cell
* - convert rows data to EffortCurve
*/
export default function formatCurve(sheetValues: Matrix<{ value: string }>) {
const validRows = filterUnvalidRows(sheetValues);
const numericRows = removeNonDigitCharacters(validRows);
return formatToEffortCurve(numericRows);
}
export default formatCurve;
10 changes: 5 additions & 5 deletions front/src/modules/rollingStock/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ import { kmhToMs, msToKmh } from 'utils/physics';
import { getTranslationKey } from 'utils/strings';
import type { ValueOf } from 'utils/types';

export const filterUndefinedValueInCurve = (curve: EffortCurveForm) =>
export const filterNullValueInCurve = (curve: EffortCurveForm) =>
curve.speeds.reduce<EffortCurve>(
(result, speed, index) => {
const maxEffort = curve.max_efforts[index];
if (speed !== undefined && maxEffort !== undefined) {
if (speed !== null && maxEffort !== null) {
result.speeds.push(speed);
result.max_efforts.push(maxEffort);
}
Expand Down Expand Up @@ -132,11 +132,11 @@ export const rollingStockEditorQueryArg = (
...acc,
[mode]: {
...currentRsEffortCurve[mode],
default_curve: filterUndefinedValueInCurve(currentRsEffortCurve[mode].default_curve),
default_curve: filterNullValueInCurve(currentRsEffortCurve[mode].default_curve),
curves: [
...currentRsEffortCurve[mode].curves.map((curve) => ({
...curve,
curve: filterUndefinedValueInCurve(curve.curve),
curve: filterNullValueInCurve(curve.curve),
})),
],
},
Expand Down Expand Up @@ -259,7 +259,7 @@ export const checkRollingStockFormValidity = (
Object.entries(effortCurves || {}).forEach(([mode, { curves }]) => {
curves.forEach(
({ curve, cond: { comfort, electrical_profile_level, power_restriction_code } }) => {
const filteredCurve = filterUndefinedValueInCurve(curve);
const filteredCurve = filterNullValueInCurve(curve);

if (isInvalidCurve(filteredCurve)) {
const formattedComfort = formatCurveCondition(comfort, t, 'comfortTypes');
Expand Down
9 changes: 7 additions & 2 deletions front/src/modules/rollingStock/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,15 @@ export type ElectricalProfileByMode = {
thermal: null[];
};

export type DataSheetCurve = {
speed: number | null;
effort: number | null;
};

// Effort curve with values number or undefined
export type EffortCurveForm = {
max_efforts: Array<number | undefined>;
speeds: Array<number | undefined>;
max_efforts: Array<number | null>;
speeds: Array<number | null>;
};

export type ConditionalEffortCurveForm = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,19 +223,12 @@

.rollingstock-editor-spreadsheet {
width: 40%;
height: 20.75rem;
overflow: auto;
tr:not(:first-child) .Spreadsheet__header,
tr:first-child .Spreadsheet__header:first-child {
width: 2rem;
border: solid 1px var(--coolgray3);
}
tr:not(:first-child) .Spreadsheet__header {
background-color: var(--white);
}
tr:first-child .Spreadsheet__header {
height: auto;
border-radius: 0.3rem;
.dsg-cell-header {
color: var(--blue);
white-space: normal !important;
background-color: var(--coolgray1);
}
}

Expand Down
2 changes: 0 additions & 2 deletions front/src/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ export function snakeToCamel(str: string) {
return str.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr: string) => chr.toUpperCase());
}

export const emptyStringRegex = /^\s+$/;

export function getTranslationKey(translationList: string | undefined, item: string): string {
return `${translationList ? `${translationList}.` : ''}${item}`;
}
Expand Down
16 changes: 8 additions & 8 deletions front/tests/009-rollingstock-editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,16 @@ test.describe('Rollingstock editor page', () => {

// Complete the speed effort curves Not specified

const velocityCellRow0 = playwrightRollingstockEditorPage.getVelocityCellByRow('0');
const velocityCellRow0 = playwrightRollingstockEditorPage.getVelocityCellByRow(1);
await playwrightRollingstockEditorPage.setSpreedsheetCell('0', velocityCellRow0);

const effortCellRow0 = playwrightRollingstockEditorPage.getEffortCellByRow('0');
const effortCellRow0 = playwrightRollingstockEditorPage.getEffortCellByRow(1);
await playwrightRollingstockEditorPage.setSpreedsheetCell('900', effortCellRow0);

const velocityCellRow1 = playwrightRollingstockEditorPage.getVelocityCellByRow('1');
const velocityCellRow1 = playwrightRollingstockEditorPage.getVelocityCellByRow(2);
await playwrightRollingstockEditorPage.setSpreedsheetCell('5', velocityCellRow1);

const effortCellRow1 = playwrightRollingstockEditorPage.getEffortCellByRow('1');
const effortCellRow1 = playwrightRollingstockEditorPage.getEffortCellByRow(2);
await playwrightRollingstockEditorPage.setSpreedsheetCell('800', effortCellRow1);

// Select and complete the speed effort curves C0
Expand All @@ -113,16 +113,16 @@ test.describe('Rollingstock editor page', () => {

await playwrightRollingstockEditorPage.setSpreedsheetCell('800', effortCellRow1);

const velocityCellRow2 = playwrightRollingstockEditorPage.getVelocityCellByRow('2');
const velocityCellRow2 = playwrightRollingstockEditorPage.getVelocityCellByRow(3);
await playwrightRollingstockEditorPage.setSpreedsheetCell('10', velocityCellRow2);

const effortCellRow2 = playwrightRollingstockEditorPage.getEffortCellByRow('2');
const effortCellRow2 = playwrightRollingstockEditorPage.getEffortCellByRow(3);
await playwrightRollingstockEditorPage.setSpreedsheetCell('900', effortCellRow2);

const velocityCellRow3 = playwrightRollingstockEditorPage.getVelocityCellByRow('3');
const velocityCellRow3 = playwrightRollingstockEditorPage.getVelocityCellByRow(4);
await playwrightRollingstockEditorPage.setSpreedsheetCell('20', velocityCellRow3);

const effortCellRow3 = playwrightRollingstockEditorPage.getEffortCellByRow('3');
const effortCellRow3 = playwrightRollingstockEditorPage.getEffortCellByRow(4);
await playwrightRollingstockEditorPage.setSpreedsheetCell('800', effortCellRow3);

await playwrightRollingstockEditorPage.clickOnRollingstockDetailsButton();
Expand Down
Loading

0 comments on commit fc0cd2e

Please sign in to comment.