From da8728cc454dbeaf187fcb80fd9755020c95109a Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Thu, 6 Jun 2024 19:49:12 +0200 Subject: [PATCH 01/16] Switch to airbnb eslint Signed-off-by: Ayoub LABIDI --- .eslintrc.json | 17 +- babel.config.json | 1 + demo/data/EquipmentSearchBar.js | 7 +- demo/data/ReportViewer.js | 4 +- demo/data/TreeViewFinder.jsx | 20 +- demo/src/FlatParametersTab.jsx | 10 +- demo/src/InputsTab.jsx | 30 +- demo/src/TableTab.jsx | 40 +- demo/src/TreeViewFinderConfig.jsx | 29 +- demo/src/app.jsx | 122 ++-- demo/src/equipment-search.tsx | 8 +- demo/src/index.jsx | 1 + demo/src/right-resizable-box.jsx | 27 +- package.json | 6 +- .../AuthenticationRouter.tsx | 25 +- src/components/AuthenticationRouter/index.ts | 3 +- .../CardErrorBoundary/card-error-boundary.tsx | 11 +- src/components/CardErrorBoundary/index.ts | 3 +- .../directory-item-selector.tsx | 297 ++++---- .../element-search-dialog.tsx | 17 +- .../ElementSearchDialog/equipment-item.tsx | 20 +- src/components/ElementSearchDialog/index.ts | 4 +- .../ElementSearchDialog/tag-renderer.tsx | 12 +- .../FlatParameters/FlatParameters.tsx | 147 ++-- src/components/FlatParameters/index.ts | 3 +- src/components/Login/Login.tsx | 15 +- src/components/Login/Logout.tsx | 31 +- src/components/Login/index.ts | 3 +- .../MuiVirtualizedTable/ColumnHeader.tsx | 19 +- .../KeyedColumnsRowIndexer.tsx | 529 +++++++------- .../MuiVirtualizedTable.tsx | 663 +++++++++--------- src/components/MuiVirtualizedTable/index.ts | 4 +- .../MultipleSelectionDialog.tsx | 16 +- .../MultipleSelectionDialog/index.ts | 3 +- src/components/OverflowableText/index.ts | 9 +- .../OverflowableText/overflowable-text.tsx | 8 +- src/components/ReportViewer/filter-button.tsx | 6 +- src/components/ReportViewer/index.ts | 3 +- .../ReportViewer/log-report-item.ts | 28 +- src/components/ReportViewer/log-report.ts | 11 +- src/components/ReportViewer/log-table.tsx | 18 +- .../ReportViewer/multi-select-list.tsx | 6 +- src/components/ReportViewer/report-item.tsx | 10 +- src/components/ReportViewer/report-viewer.tsx | 37 +- src/components/ReportViewerDialog/index.ts | 3 +- .../report-viewer-dialog.tsx | 10 +- .../SignInCallbackHandler.tsx | 6 +- src/components/SignInCallbackHandler/index.ts | 3 +- .../SilentRenewCallbackHandler.tsx | 6 +- .../SilentRenewCallbackHandler/index.ts | 3 +- .../SnackbarProvider/SnackbarProvider.tsx | 25 +- src/components/SnackbarProvider/index.ts | 3 +- src/components/TopBar/AboutDialog.tsx | 339 +++++---- src/components/TopBar/GridLogo.tsx | 44 +- src/components/TopBar/TopBar.test.tsx | 10 +- src/components/TopBar/TopBar.tsx | 103 +-- src/components/TopBar/index.ts | 2 +- .../TreeViewFinder/TreeViewFinder.tsx | 153 ++-- src/components/TreeViewFinder/index.ts | 5 +- src/components/dialogs/custom-mui-dialog.tsx | 14 +- .../description-modification-dialog.tsx | 28 +- .../dialogs/modify-element-selection.tsx | 44 +- .../dialogs/popup-confirmation-dialog.tsx | 13 +- .../criteria-based-filter-edition-dialog.tsx | 33 +- .../criteria-based-filter-form.tsx | 11 +- .../criteria-based-filter-utils.ts | 71 +- .../criteria-based/criteria-based-form.tsx | 18 +- .../criteria-based/filter-free-properties.tsx | 28 +- .../criteria-based/filter-properties.tsx | 24 +- .../filter/criteria-based/filter-property.tsx | 36 +- .../filter/expert/expert-filter-constants.ts | 2 +- .../expert/expert-filter-edition-dialog.tsx | 26 +- .../filter/expert/expert-filter-form.tsx | 41 +- .../filter/expert/expert-filter-utils.ts | 108 +-- .../explicit-naming-filter-edition-dialog.tsx | 28 +- .../explicit-naming-filter-form.tsx | 77 +- .../filter/filter-creation-dialog.tsx | 24 +- src/components/filter/filter-form.tsx | 49 +- src/components/filter/utils/filter-api.ts | 16 +- .../filter/utils/filter-form-utils.ts | 4 +- .../react-hook-form/ExpandingTextField.tsx | 16 +- .../ag-grid-table/bottom-right-buttons.tsx | 23 +- .../cell-editors/numericEditor.ts | 34 +- .../csv-uploader/csv-uploader.tsx | 85 ++- .../ag-grid-table/custom-ag-grid-table.tsx | 62 +- .../autocomplete-input.tsx | 37 +- .../multiple-autocomplete-input.tsx | 6 +- .../booleans/boolean-input.tsx | 9 +- .../booleans/checkbox-input.tsx | 10 +- .../react-hook-form/booleans/switch-input.tsx | 6 +- .../react-hook-form/directory-items-input.tsx | 28 +- .../error-management/error-input.tsx | 44 +- .../error-management/field-error-alert.tsx | 8 +- .../error-management/mid-form-error.tsx | 4 +- .../react-hook-form/numbers/float-input.tsx | 16 +- .../react-hook-form/numbers/integer-input.tsx | 11 +- .../provider/custom-form-provider.tsx | 20 +- .../provider/use-custom-form-context.ts | 4 +- .../inputs/react-hook-form/radio-input.tsx | 6 +- .../inputs/react-hook-form/range-input.tsx | 16 +- .../react-hook-form/raw-read-only-input.ts | 4 +- .../select-inputs/countries-input.tsx | 16 +- .../input-with-popup-confirmation.tsx | 10 +- .../select-inputs/mui-select-input.tsx | 22 +- .../select-inputs/select-input.tsx | 33 +- .../inputs/react-hook-form/slider-input.tsx | 16 +- .../inputs/react-hook-form/text-input.tsx | 22 +- .../react-hook-form/unique-name-input.tsx | 62 +- .../react-hook-form/utils/cancel-button.tsx | 9 +- .../react-hook-form/utils/field-label.tsx | 9 +- .../react-hook-form/utils/functions.tsx | 8 +- .../react-hook-form/utils/submit-button.tsx | 9 +- .../utils/text-field-with-adornment.tsx | 22 +- .../inputs/react-query-builder/add-button.tsx | 31 +- .../combinator-selector.tsx | 19 +- .../country-value-editor.tsx | 36 +- .../custom-react-query-builder.tsx | 41 +- .../element-value-editor.tsx | 82 +-- .../property-value-editor.tsx | 16 +- .../react-query-builder/remove-button.tsx | 24 +- .../react-query-builder/text-value-editor.tsx | 35 +- .../translated-value-editor.tsx | 53 +- .../react-query-builder/use-convert-value.ts | 2 +- .../react-query-builder/value-editor.tsx | 117 ++-- .../react-query-builder/value-selector.tsx | 4 +- src/components/inputs/select-clearable.tsx | 18 +- .../translations/card-error-boundary-en.ts | 4 +- .../translations/card-error-boundary-fr.ts | 4 +- .../translations/common-button-en.ts | 4 +- .../translations/common-button-fr.ts | 4 +- .../translations/directory-items-input-en.ts | 4 +- .../translations/directory-items-input-fr.ts | 4 +- .../translations/element-search-en.ts | 4 +- .../translations/element-search-fr.ts | 4 +- .../translations/equipment-search-en.ts | 4 +- .../translations/equipment-search-fr.ts | 4 +- src/components/translations/filter-en.ts | 4 +- .../translations/filter-expert-en.ts | 4 +- .../translations/filter-expert-fr.ts | 4 +- src/components/translations/filter-fr.ts | 4 +- .../translations/flat-parameters-en.ts | 4 +- .../translations/flat-parameters-fr.ts | 4 +- src/components/translations/inputs-en.ts | 4 +- src/components/translations/inputs-fr.ts | 4 +- src/components/translations/login-en.ts | 4 +- src/components/translations/login-fr.ts | 4 +- .../multiple-selection-dialog-en.ts | 4 +- .../multiple-selection-dialog-fr.ts | 4 +- .../translations/report-viewer-en.ts | 4 +- .../translations/report-viewer-fr.ts | 4 +- src/components/translations/table-en.ts | 4 +- src/components/translations/table-fr.ts | 4 +- src/components/translations/top-bar-en.ts | 4 +- src/components/translations/top-bar-fr.ts | 4 +- .../translations/treeview-finder-en.ts | 4 +- .../translations/treeview-finder-fr.ts | 4 +- src/hooks/localized-countries-hook.ts | 6 +- src/hooks/predefined-properties-hook.ts | 6 +- src/hooks/useDebounce.ts | 4 +- src/hooks/useIntlRef.ts | 12 +- src/hooks/useSnackMessage.ts | 119 ++-- src/index.ts | 43 +- src/redux/actions.ts | 18 +- src/services/apps-metadata.ts | 8 +- src/services/directory.ts | 19 +- src/services/explore.ts | 36 +- src/services/index.ts | 7 +- src/services/study.ts | 17 +- src/services/utils.ts | 72 +- src/utils/AuthService.ts | 493 +++++++------ src/utils/ElementIcon.tsx | 11 +- src/utils/ElementType.ts | 8 + src/utils/EquipmentType.ts | 12 +- src/utils/Events.ts | 18 + src/utils/FetchStatus.ts | 4 +- src/utils/MetaData.ts | 16 + src/utils/UserManagerMock.ts | 22 +- src/utils/algos.ts | 6 +- ...-types-for-predefined-properties-mapper.ts | 6 +- src/utils/field-constants.ts | 4 +- src/utils/functions.ts | 7 +- src/utils/styles.ts | 3 +- src/utils/types.ts | 3 +- src/utils/yup-config.ts | 3 +- 184 files changed, 3057 insertions(+), 2904 deletions(-) create mode 100644 src/utils/Events.ts create mode 100644 src/utils/MetaData.ts diff --git a/.eslintrc.json b/.eslintrc.json index 3737826c..fe70018e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,15 +1,26 @@ { "root": true, "extends": [ - "react-app", + "airbnb", + "airbnb-typescript", "plugin:prettier/recommended" ], + "parserOptions": { + "project": "./tsconfig.json" + }, "ignorePatterns": [ // node_modules is implicitly always ignored - "dist" + "dist", + "jest.setup.ts", + "jest.config.ts" ], "rules": { "prettier/prettier": "warn", - "curly": "error" + "curly": "error", + "no-console": "off", + "react/jsx-props-no-spreading": "off", + "react/react-in-jsx-scope": "off", + "no-shadow": "off", + "@typescript-eslint/no-shadow": "error" } } diff --git a/babel.config.json b/babel.config.json index 4b470eb4..988e8d4b 100644 --- a/babel.config.json +++ b/babel.config.json @@ -1,5 +1,6 @@ { "presets": [ + "airbnb", "@babel/preset-env", ["@babel/preset-react", { "runtime": "automatic" }], "@babel/preset-typescript", diff --git a/demo/data/EquipmentSearchBar.js b/demo/data/EquipmentSearchBar.js index b158e4b4..4e13fabd 100644 --- a/demo/data/EquipmentSearchBar.js +++ b/demo/data/EquipmentSearchBar.js @@ -148,7 +148,7 @@ const EQUIPMENTS = [ }, ]; -export const searchEquipments = (searchTerm, equipmentLabelling) => { +const searchEquipments = (searchTerm, equipmentLabelling) => { if (searchTerm) { return getEquipmentsInfosForSearchBar( equipmentLabelling @@ -160,7 +160,8 @@ export const searchEquipments = (searchTerm, equipmentLabelling) => { ), equipmentLabelling ? (e) => e.name || e.id : (e) => e.id ); - } else { - return []; } + return []; }; + +export default searchEquipments; diff --git a/demo/data/ReportViewer.js b/demo/data/ReportViewer.js index 28d6ddbe..54c6fb8e 100644 --- a/demo/data/ReportViewer.js +++ b/demo/data/ReportViewer.js @@ -6,7 +6,7 @@ */ /* eslint-disable no-template-curly-in-string */ -export const LOGS_JSON = { +const LOGS_JSON = { taskKey: 'Test', defaultName: 'Test', taskValues: {}, @@ -230,3 +230,5 @@ export const LOGS_JSON = { ], reports: [], }; + +export default LOGS_JSON; diff --git a/demo/data/TreeViewFinder.jsx b/demo/data/TreeViewFinder.jsx index f54accc3..ee22453e 100644 --- a/demo/data/TreeViewFinder.jsx +++ b/demo/data/TreeViewFinder.jsx @@ -15,7 +15,8 @@ import { Whatshot as WhatshotIcon, } from '@mui/icons-material'; -var PokemonTree = [ +// eslint-disable-next-line import/no-mutable-exports +let PokemonTree = [ { id: 'D1', name: 'Team', @@ -116,7 +117,8 @@ var PokemonTree = [ }, ]; -var PokemonList = [ +// eslint-disable-next-line import/no-mutable-exports +let PokemonList = [ { id: '1', name: 'Pikachu', @@ -137,14 +139,14 @@ var PokemonList = [ { id: '6', name: 'Machoc', type: 'Combat', power: '64' }, ]; -var IDCounter = 100; // Start at 100 to avoid conflicts for demo +let IDCounter = 100; // Start at 100 to avoid conflicts for demo function fetchInfinitePokemonList() { - IDCounter++; + IDCounter += 1; PokemonList = [ ...PokemonList, { id: IDCounter.toString(), - name: 'Métamorph_' + new Date().getTime(), + name: `Métamorph_${new Date().getTime()}`, type: 'Normal', power: '1', icon: , @@ -154,20 +156,20 @@ function fetchInfinitePokemonList() { } function fetchInfinitePokemonTree(nodeId) { - IDCounter++; + IDCounter += 1; - let PokemonTreeCopy = [...PokemonTree]; + const PokemonTreeCopy = [...PokemonTree]; const dirFound = PokemonTreeCopy.find((element) => element.id === nodeId); if (dirFound) { dirFound.children.push({ id: IDCounter.toString(), - name: 'Métamorph_' + new Date().getTime(), + name: `Métamorph_${new Date().getTime()}`, type: 'Normal', power: '1', icon: , }); - dirFound.childrenCount++; + dirFound.childrenCount += 1; } PokemonTree = PokemonTreeCopy; diff --git a/demo/src/FlatParametersTab.jsx b/demo/src/FlatParametersTab.jsx index 330da9a2..405380e1 100644 --- a/demo/src/FlatParametersTab.jsx +++ b/demo/src/FlatParametersTab.jsx @@ -7,7 +7,7 @@ import { useCallback, useState } from 'react'; import RightResizableBox from './right-resizable-box'; -import FlatParameters from '../../src/components/FlatParameters/FlatParameters'; +import { FlatParameters } from '../../src/components/FlatParameters/FlatParameters'; const EXAMPLE_PARAMETERS = [ { @@ -198,7 +198,7 @@ const EXAMPLE_PARAMETERS = [ }, ]; -export const FlatParametersTab = () => { +function FlatParametersTab() { const [currentParameters, setCurrentParameters] = useState({}); const onChange = useCallback((paramName, value, isEdit) => { if (!isEdit) { @@ -217,7 +217,7 @@ export const FlatParametersTab = () => { paramsAsArray={EXAMPLE_PARAMETERS} initValues={currentParameters} onChange={onChange} - variant={'standard'} + variant="standard" showSeparator selectionWithDialog={(param) => param?.possibleValues?.length > 10 @@ -226,4 +226,6 @@ export const FlatParametersTab = () => { ); -}; +} + +export default FlatParametersTab; diff --git a/demo/src/InputsTab.jsx b/demo/src/InputsTab.jsx index 490d80f4..180c835a 100644 --- a/demo/src/InputsTab.jsx +++ b/demo/src/InputsTab.jsx @@ -9,6 +9,7 @@ import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; import { Box, Grid } from '@mui/material'; +import { useState } from 'react'; import AutocompleteInput from '../../src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input'; import TextInput from '../../src/components/inputs/react-hook-form/text-input'; import RadioInput from '../../src/components/inputs/react-hook-form/radio-input'; @@ -22,7 +23,6 @@ import SubmitButton from '../../src/components/inputs/react-hook-form/utils/subm import ExpandingTextField from '../../src/components/inputs/react-hook-form/ExpandingTextField'; import CustomFormProvider from '../../src/components/inputs/react-hook-form/provider/custom-form-provider'; import SelectClearable from '../../src/components/inputs/select-clearable'; -import { useState } from 'react'; const AUTOCOMPLETE_INPUT = 'autocomplete'; const TEXT_INPUT = 'text'; @@ -84,7 +84,7 @@ const areIdsEqual = (val1, val2) => { const logWhenValuesChange = false; const logWhenValidate = true; -export function InputsTab() { +function InputsTab() { const formMethods = useForm({ defaultValues: emptyFormData, resolver: yupResolver(formSchema), @@ -124,26 +124,26 @@ export function InputsTab() { - + + /> @@ -160,36 +160,36 @@ export function InputsTab() { - + @@ -206,3 +206,5 @@ export function InputsTab() { ); } + +export default InputsTab; diff --git a/demo/src/TableTab.jsx b/demo/src/TableTab.jsx index c38f00b6..518c3c04 100644 --- a/demo/src/TableTab.jsx +++ b/demo/src/TableTab.jsx @@ -6,7 +6,7 @@ */ import { useCallback, useMemo, useState } from 'react'; -import { DEFAULT_CELL_PADDING } from '../../src'; +// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { Box, @@ -16,8 +16,9 @@ import { Switch, TextField, } from '@mui/material'; +import { DEFAULT_CELL_PADDING } from '../../src'; import MuiVirtualizedTable, { - CHANGE_WAYS, + ChangeWays, generateMuiVirtualizedTableClass, KeyedColumnsRowIndexer, } from '../../src/components/MuiVirtualizedTable'; @@ -29,16 +30,17 @@ const evenThenOddOrderingKey = (n) => { if (n <= 0 && remainder < 1) { // first negative even and zero ]...-3,-2,-1] return n / 2 - 1; - } else if (n > 0 && remainder < 1) { + } + if (n > 0 && remainder < 1) { // then positive even [-1/2, -1/3 ..., 0[ return -1 / (n / 2 + 1); - } else if (n < 0 && remainder >= 1) { + } + if (n < 0 && remainder >= 1) { // then negative odds ]0, 1/3, 1/2... return -1 / ((n - 1) / 2 - 1); - } else { - //positive odd [1,2,3,4...[ - return (n + 1) / 2; } + // positive odd [1,2,3,4...[ + return (n + 1) / 2; }; /** @@ -64,7 +66,7 @@ const stylesVirtualizedTable = (theme) => ({ }, '& .tableCell': { flex: 1, - padding: DEFAULT_CELL_PADDING + 'px', + padding: `${DEFAULT_CELL_PADDING}px`, }, '& .noClick': { cursor: 'initial', @@ -92,7 +94,7 @@ const stylesEmotion = ({ theme }) => ); const StyledVirtualizedTable = styled(MuiVirtualizedTable)(stylesEmotion); -export const TableTab = () => { +export function TableTab() { const [usesCustomStyles, setUsesCustomStyles] = useState(true); const VirtualizedTable = usesCustomStyles @@ -141,11 +143,11 @@ export const TableTab = () => { function makeIndexer(prevIndexer) { const prevCol = prevIndexer?.highestCodedColumn(columns); - let colKey = !prevCol ? 'key2' : 'key' + ((Math.abs(prevCol) % 4) + 1); + const colKey = !prevCol ? 'key2' : `key${(Math.abs(prevCol) % 4) + 1}`; const ret = new KeyedColumnsRowIndexer(true, false); ret.setColFilterOuterParams(colKey, ['val9']); - const changeWay = CHANGE_WAYS.SIMPLE; + const changeWay = ChangeWays.SIMPLE; // fake user click twice, to set descending order ret.updateSortingFromUser(colKey, changeWay); ret.updateSortingFromUser(colKey, changeWay); @@ -190,7 +192,7 @@ export const TableTab = () => { filtered = filtered.reverse(); } } - return filtered.map(([d, j]) => j); + return filtered.map(([j]) => j); }, [rows, filter] ); @@ -238,7 +240,7 @@ export const TableTab = () => { @@ -249,7 +251,7 @@ export const TableTab = () => { )} { updateKeyIfNeeded(); setFilterValue(event.target.value); @@ -262,13 +264,13 @@ export const TableTab = () => { )} { // still update the key to cause unmount/remount even if we don't get a new different number // from the field to give more occasions to test unmount/remounts updateKeyIfNeeded(); const newHeaderHeight = Number(event.target.value); - if (!isNaN(newHeaderHeight)) { + if (!Number.isNaN(newHeaderHeight)) { setHeaderHeight(event.target.value); } }} @@ -288,7 +290,7 @@ export const TableTab = () => { sortable={sortable} defersFilterChanges={defersFilterChanges} columns={columns} - enableExportCSV={true} + enableExportCSV exportCSVDataKeys={['key2', 'key4']} headerHeight={ !headerHeight ? undefined : Number(headerHeight) @@ -303,4 +305,6 @@ export const TableTab = () => { ); -}; +} + +export default TableTab; diff --git a/demo/src/TreeViewFinderConfig.jsx b/demo/src/TreeViewFinderConfig.jsx index 113a29d6..9dcf5432 100644 --- a/demo/src/TreeViewFinderConfig.jsx +++ b/demo/src/TreeViewFinderConfig.jsx @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import PropTypes from 'prop-types'; import { Checkbox, FormControl, @@ -29,7 +30,7 @@ import { * @param {EventListener} onSelectionTypeChange - onChange type EventListener on the multiselect value change. * @param {EventListener} onOnlyLeavesChange - onChange type EventListener on the onlyLeaves value change. */ -const TreeViewFinderConfig = (props) => { +function TreeViewFinderConfig(props) { const { dynamicData, dataFormat, @@ -159,6 +160,32 @@ const TreeViewFinderConfig = (props) => { ); +} + +TreeViewFinderConfig.propTypes = { + dynamicData: PropTypes.bool, + dataFormat: PropTypes.string, + multiSelect: PropTypes.bool, + onlyLeaves: PropTypes.bool, + sortedAlphabetically: PropTypes.bool, + onDynamicDataChange: PropTypes.func, + onDataFormatChange: PropTypes.func, + onSelectionTypeChange: PropTypes.func, + onOnlyLeavesChange: PropTypes.func, + onSortedAlphabeticallyChange: PropTypes.func, +}; + +TreeViewFinderConfig.defaultProps = { + dynamicData: false, + dataFormat: 'Tree', + multiSelect: false, + onlyLeaves: false, + sortedAlphabetically: false, + onDynamicDataChange: () => {}, + onDataFormatChange: () => {}, + onSelectionTypeChange: () => {}, + onOnlyLeavesChange: () => {}, + onSortedAlphabeticallyChange: () => {}, }; export default TreeViewFinderConfig; diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 20a3ea56..070f9750 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -1,3 +1,14 @@ +/* eslint-disable func-names */ +/* eslint-disable no-nested-ternary */ +/* eslint-disable no-return-assign */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable no-promise-executor-return */ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/* eslint-disable no-alert */ +/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-shadow */ +/* eslint-disable react/jsx-no-bind */ +/* eslint-disable react/prop-types */ /** * Copyright (c) 2020, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public @@ -7,24 +18,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import TopBar from '../../src/components/TopBar'; -import SnackbarProvider from '../../src/components/SnackbarProvider'; -import AuthenticationRouter from '../../src/components/AuthenticationRouter'; -import CardErrorBoundary from '../../src/components/CardErrorBoundary'; -import { - ElementType, - EQUIPMENT_TYPE, - equipmentStyles, - getFileIcon, - initializeAuthenticationDev, - LANG_ENGLISH, - LANG_FRENCH, - LANG_SYSTEM, - LIGHT_THEME, - logout, -} from '../../src'; -import { useSnackMessage } from '../../src/hooks/useSnackMessage'; - import { Box, Button, @@ -41,13 +34,27 @@ import { ThemeProvider, Typography, } from '@mui/material'; +// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; - +// eslint-disable-next-line import/no-extraneous-dependencies import { useMatch } from 'react-router'; import { IntlProvider, useIntl } from 'react-intl'; import { BrowserRouter, useLocation, useNavigate } from 'react-router-dom'; - +import TopBar from '../../src/components/TopBar'; +import SnackbarProvider from '../../src/components/SnackbarProvider'; +import AuthenticationRouter from '../../src/components/AuthenticationRouter'; +import CardErrorBoundary from '../../src/components/CardErrorBoundary'; import { + ElementType, + EQUIPMENT_TYPE, + equipmentStyles, + getFileIcon, + initializeAuthenticationDev, + LANG_ENGLISH, + LANG_FRENCH, + LANG_SYSTEM, + LIGHT_THEME, + logout, card_error_boundary_en, card_error_boundary_fr, element_search_en, @@ -72,9 +79,12 @@ import { top_bar_fr, treeview_finder_en, treeview_finder_fr, -} from '../../src/index'; +} from '../../src'; +import { useSnackMessage } from '../../src/hooks/useSnackMessage'; + import translations from './demo_intl'; +// eslint-disable-next-line import/no-unresolved import PowsyblLogo from '../images/powsybl_logo.svg?react'; import AppPackage from '../../package.json'; @@ -91,21 +101,21 @@ import { testDataTree, } from '../data/TreeViewFinder'; -import { LOGS_JSON } from '../data/ReportViewer'; +import LOGS_JSON from '../data/ReportViewer'; -import { searchEquipments } from '../data/EquipmentSearchBar'; +import searchEquipments from '../data/EquipmentSearchBar'; import { EquipmentItem } from '../../src/components/ElementSearchDialog/equipment-item'; import OverflowableText from '../../src/components/OverflowableText'; import { setShowAuthenticationRouterLogin } from '../../src/redux/actions'; import { TableTab } from './TableTab'; -import { FlatParametersTab } from './FlatParametersTab'; +import FlatParametersTab from './FlatParametersTab'; import { toNestedGlobalSelectors } from '../../src/utils/styles'; -import { InputsTab } from './InputsTab'; +import InputsTab from './InputsTab'; import inputs_en from '../../src/components/translations/inputs-en'; import inputs_fr from '../../src/components/translations/inputs-fr'; -import { EquipmentSearchDialog } from './equipment-search'; +import EquipmentSearchDialog from './equipment-search'; const messages = { en: { @@ -157,9 +167,8 @@ const darkTheme = createTheme({ const getMuiTheme = (theme) => { if (theme === LIGHT_THEME) { return lightTheme; - } else { - return darkTheme; } + return darkTheme; }; const style = { @@ -192,13 +201,14 @@ const CustomTreeViewFinder = styled(TreeViewFinder)( TreeViewFinderCustomStylesEmotion ); -const Crasher = () => { +function Crasher() { const [crash, setCrash] = useState(false); if (crash) { + // eslint-disable-next-line no-undef window.foonotexists.bar(); } return ; -}; +} function SnackErrorButton() { const { snackError } = useSnackMessage(); @@ -292,14 +302,15 @@ function PermanentSnackButton() { ); } -const validateUser = (user) => { +const validateUser = () => { // change to false to simulate user unauthorized access - return new Promise((resolve) => - window.setTimeout(() => resolve(true), 500) - ); + return new Promise((resolve) => { + // eslint-disable-next-line no-undef + window.setTimeout(() => resolve(true), 500); + }); }; -const AppContent = ({ language, onLanguageClick }) => { +function AppContent({ language, onLanguageClick }) { const navigate = useNavigate(); const location = useLocation(); const intl = useIntl(); @@ -344,9 +355,8 @@ const AppContent = ({ language, onLanguageClick }) => { .map((node) => { if (node.children && node.children.length > 0) { return 1 + countNodes(node.children); - } else { - return 1; } + return 1; }) .reduce((a, b) => { return a + b; @@ -457,7 +467,7 @@ const AppContent = ({ language, onLanguageClick }) => { function testIcons() { return ( - + {Object.keys(ElementType).map((type) => ( {getFileIcon(type)} @@ -602,7 +612,7 @@ const AppContent = ({ language, onLanguageClick }) => { Logs setOpenReportViewer(false)} jsonReport={LOGS_JSON} @@ -676,12 +686,9 @@ const AppContent = ({ language, onLanguageClick }) => { sortedAlphabetically ? sortAlphabetically : undefined } // Customisation props to pass the counter in the title - title={ - 'Number of nodes : ' + - countNodes( - dataFormat === 'Tree' ? nodesTree : nodesList - ) - } + title={`Number of nodes : ${countNodes( + dataFormat === 'Tree' ? nodesTree : nodesList + )}`} /> @@ -81,12 +79,11 @@ const Login = ({ onLoginClick, disabled }: LoginProps) => { GridSuite {' '} - {new Date().getFullYear()} - {'.'} + {new Date().getFullYear()}. ); -}; +} export default Login; diff --git a/src/components/Login/Logout.tsx b/src/components/Login/Logout.tsx index 0968d7f6..90cf1478 100644 --- a/src/components/Login/Logout.tsx +++ b/src/components/Login/Logout.tsx @@ -39,20 +39,19 @@ export interface LogoutProps { disabled: boolean; } -const Logout = ({ onLogoutClick, disabled }: LogoutProps) => { - function Copyright() { - return ( - - {'Copyright © '} - - GridSuite - {' '} - {new Date().getFullYear()} - {'.'} - - ); - } +function Copyright() { + return ( + + {'Copyright © '} + + GridSuite + {' '} + {new Date().getFullYear()}. + + ); +} +function Logout({ onLogoutClick, disabled }: Readonly) { return ( @@ -62,7 +61,7 @@ const Logout = ({ onLogoutClick, disabled }: LogoutProps) => { {' '} ? @@ -76,7 +75,7 @@ const Logout = ({ onLogoutClick, disabled }: LogoutProps) => { > @@ -85,6 +84,6 @@ const Logout = ({ onLogoutClick, disabled }: LogoutProps) => { ); -}; +} export default Logout; diff --git a/src/components/Login/index.ts b/src/components/Login/index.ts index 64dfbe98..ee8cad59 100644 --- a/src/components/Login/index.ts +++ b/src/components/Login/index.ts @@ -4,4 +4,5 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default } from './Login'; +// eslint-disable-next-line import/prefer-default-export +export { default as Login } from './Login'; diff --git a/src/components/MuiVirtualizedTable/ColumnHeader.tsx b/src/components/MuiVirtualizedTable/ColumnHeader.tsx index 488648cc..dccaf056 100644 --- a/src/components/MuiVirtualizedTable/ColumnHeader.tsx +++ b/src/components/MuiVirtualizedTable/ColumnHeader.tsx @@ -22,6 +22,7 @@ import { FilterAltOutlined as FilterAltOutlinedIcon, } from '@mui/icons-material'; +// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { Box, BoxProps, Theme } from '@mui/material'; import { mergeSx } from '../../utils/styles'; @@ -75,7 +76,7 @@ interface SortButtonProps { // in the same direction as it will get if clicked (once). // signedRank > 0 means sorted by ascending value from lower indices to higher indices // so lesser values are at top, so the upward arrow -const SortButton = ({ signedRank = 0, ...props }: SortButtonProps) => { +function SortButton({ signedRank = 0, ...props }: Readonly) { const sortRank = Math.abs(signedRank); const visibilityStyle = (!signedRank || undefined) && @@ -90,7 +91,7 @@ const SortButton = ({ signedRank = 0, ...props }: SortButtonProps) => { {sortRank > 1 && !props.hovered && {sortRank}} ); -}; +} interface FilterButtonProps { filterLevel: number; @@ -98,22 +99,22 @@ interface FilterButtonProps { onClick: ComponentProps['onClick']; } -const FilterButton = (props: FilterButtonProps) => { +function FilterButton(props: Readonly) { + const { filterLevel, headerHovered, onClick } = props; const visibilityStyle = - !props.filterLevel && - (props.headerHovered ? styles.hovered : styles.transparent); + !filterLevel && (headerHovered ? styles.hovered : styles.transparent); return ( 1 && styles.filterTooLossy, + filterLevel > 1 && styles.filterTooLossy, visibilityStyle )} /> ); -}; +} export interface ColumnHeaderProps extends BoxProps { label: ReactNode; @@ -155,7 +156,7 @@ export const ColumnHeader = forwardRef( }, [onFilterClick]); return ( - //@ts-ignore it does not let us define Box with onMouseEnter/onMouseLeave attributes with 'div' I think, not sure though + // @ts-ignore it does not let us define Box with onMouseEnter/onMouseLeave attributes with 'div' I think, not sure though { if (column?.numeric) { return numericHelper; - } else if (!column?.nostat) { + } + if (!column?.nostat) { return collectibleHelper; - } else { - return noOpHelper; } + return noOpHelper; }; export interface Preferences { @@ -128,6 +147,212 @@ export interface FilteredRows { rowsCount: number; } +const giveDirSignFor = (fuzzySign: number | string | undefined) => { + // @ts-ignore we should check whether it is a string or a number first + if (fuzzySign < 0) { + return -1; + } + // @ts-ignore we should check whether it is a string or a number first + if (fuzzySign > 0) { + return 1; + } + if (fuzzySign === 'asc') { + return 1; + } + if (fuzzySign === 'desc') { + return -1; + } + return 0; +}; + +const codedColumnsFromKeyAndDirection = ( + keyAndDirections: [string, string | undefined][] | null, + columns: CustomColumnProps[] +) => { + if (!keyAndDirections) { + return null; + } + + let ret = []; + const columIndexByKey: Record = {}; + for (let colIdx = 0; colIdx < columns.length; colIdx + 1) { + const col = columns[colIdx]; + const colKey = col.dataKey; + columIndexByKey[colKey] = colIdx; + } + + ret = keyAndDirections.map((knd) => { + const colKey = knd[0]; + const dir = knd[1]; + + const colIdx = columIndexByKey[colKey]; + if (colIdx === undefined) { + return 0; + } + + const sign = giveDirSignFor(dir); + return (colIdx + 1) * sign; + }); + return ret; +}; + +const compareValue = ( + a: number | string | undefined, + b: number | string | undefined, + isNumeric: boolean, + undefSign: number = -1 +) => { + if (a === undefined && b === undefined) { + return 0; + } + if (a === undefined) { + return undefSign; + } + if (b === undefined) { + return -undefSign; + } + + if (!isNumeric) { + return `${a}`.localeCompare(b as string); + } + if (Number.isNaN(a as number)) { + return Number.isNaN(b as number) ? 0 : 1; + } + if (Number.isNaN(b as number)) { + return -1; + } + return Math.sign(Number(a) - Number(b)); +}; + +const makeCompositeComparatorFromCodedColumns = ( + codedColumns: number[] | null, + columns: CustomColumnProps[], + rowExtractor: ( + row: [Record, number][] + ) => Record +) => { + return ( + row_a_i: [Record, number][], + row_b_i: [Record, number][] + ) => { + const rowA = rowExtractor(row_a_i); + const rowB = rowExtractor(row_b_i); + // @ts-ignore codedColumns could be null we should add a check + codedColumns.map((cc) => { + const i = Math.abs(cc) - 1; + const mul = Math.sign(cc); + const col = columns[i]; + const key = col.dataKey; + // @ts-ignore numeric could be undefined, how to handle this case ? + const sgn = compareValue(rowA[key], rowB[key], col.numeric); + if (sgn) { + return mul * sgn; + } + return undefined; + }); + return 0; + }; +}; + +const canonicalForSign = (dirSign: number): string | undefined => { + if (dirSign > 0) { + return 'asc'; + } + if (dirSign < 0) { + return 'desc'; + } + return undefined; +}; + +const groupRows = ( + groupingColumnsCount: number, + columns: CustomColumnProps[], + indexedArray: [Record, number][] +) => { + const groupingComparator = makeCompositeComparatorFromCodedColumns( + Array(groupingColumnsCount).map((x, i) => i + 1), + columns, + // @ts-ignore does not match other pattern + (ar) => ar[0] + ); + // @ts-ignore does not match other pattern + indexedArray.sort(groupingComparator); + + const groups: any = []; + let prevSlice: any[] | null = null; + let inBuildGroup: any = []; + groups.push(inBuildGroup); + indexedArray.forEach((p) => { + // @ts-ignore could be undefined how to handle this case ? + const nextSlice = p[0].slice(0, groupingColumnsCount); + if (prevSlice === null || !equalsArray(prevSlice, nextSlice)) { + inBuildGroup = []; + groups.push(inBuildGroup); + } + inBuildGroup.push(p); + prevSlice = nextSlice; + }); + return groups; +}; + +const groupAndSort = ( + preFilteredRowPairs: FilteredRows, + codedColumns: number[] | null, + groupingColumnsCount: number, + columns: CustomColumnProps[] +) => { + const nothingToDo = !codedColumns && !groupingColumnsCount; + + let indexedArray = preFilteredRowPairs.rowAndOrigIndex; + const noOutFiltered = preFilteredRowPairs.rowsCount === indexedArray.length; + if (nothingToDo && noOutFiltered) { + return null; + } + if (!nothingToDo) { + // make a copy for not losing base order + indexedArray = [...indexedArray]; + } + + if (nothingToDo) { + // just nothing + } else if (!groupingColumnsCount) { + const sortingComparator = makeCompositeComparatorFromCodedColumns( + codedColumns, + columns, + // @ts-ignore I don't know how to fix this one + (ar) => ar[0] + ); + // @ts-ignore I don't know how to fix this one + indexedArray.sort(sortingComparator); + } else { + // @ts-ignore I don't know how to fix this one + const groups = groupRows(groupingColumnsCount, columns, indexedArray); + + const interGroupSortingComparator = + makeCompositeComparatorFromCodedColumns( + codedColumns, + columns, + (ar) => ar[0][0] + ); + groups.sort(interGroupSortingComparator); + + const intraGroupSortingComparator = + makeCompositeComparatorFromCodedColumns( + codedColumns, + columns, + // @ts-ignore I don't know how to fix this one + (ar) => ar[0] + ); + + indexedArray = []; + groups.forEach((group: any) => { + group.sort(intraGroupSortingComparator); + indexedArray.push(...group); + }); + } + return indexedArray; +}; + /** * A rows indexer for MuiVirtualizedTable to delegate to an instance of it * for filtering, grouping and multi-column sorting via @@ -135,28 +360,39 @@ export interface FilteredRows { */ export class KeyedColumnsRowIndexer { static get CHANGE_WAYS() { - return CHANGE_WAYS; + return ChangeWays; } - _versionSetter: ((version: number) => void) | null; + versionSetter: ((version: number) => void) | null; + byColFilter: Record< string, { userParams?: any[]; outerParams?: any[] } > | null; + byRowFilter: ((row: RowProps) => boolean) | null; + delegatorCallback: | (( instance: KeyedColumnsRowIndexer, callback: (input: any) => void ) => void) | null; + filterVersion: number; + groupingCount: number; + indirectionStatus: string | null; + isThreeState: boolean; + lastUsedRank: number; + singleColumnByDefault: boolean; + sortingState: [string, string | undefined][] | null; + version: number; constructor( @@ -165,7 +401,7 @@ export class KeyedColumnsRowIndexer { delegatorCallback = null, versionSetter: ((version: number) => void) | null = null ) { - this._versionSetter = versionSetter; + this.versionSetter = versionSetter; this.version = 0; this.filterVersion = 0; @@ -184,14 +420,14 @@ export class KeyedColumnsRowIndexer { } hasVersionSetter = () => { - return !!this._versionSetter; + return !!this.versionSetter; }; getVersion = () => { return this.version; }; - _bumpVersion = (isFilter = false) => { + bumpVersion = (isFilter = false) => { this.version += 1; if (isFilter) { this.filterVersion = this.version; @@ -202,8 +438,8 @@ export class KeyedColumnsRowIndexer { this.indirectionStatus = updated_ok ? 'done' : 'no_luck'; }); } - if (this._versionSetter) { - this._versionSetter(this.version); + if (this.versionSetter) { + this.versionSetter(this.version); } }; @@ -222,7 +458,8 @@ export class KeyedColumnsRowIndexer { this.singleColumnByDefault = preferences.singleColumnByDefault; } - this._bumpVersion(); + this.bumpVersion(); + return true; }; // Does not mutate any internal @@ -251,19 +488,19 @@ export class KeyedColumnsRowIndexer { const ri: [RowProps, number][] = []; const cs: Record = {}; - for (const col of columns) { + columns.forEach((col) => { const helper = getHelper(col); const colStat = helper.initStat(); if (colStat) { cs[col.dataKey] = colStat; } - } + }); - for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) { + for (let rowIdx = 0; rowIdx < rows.length; rowIdx + 1) { const row = rows[rowIdx]; let acceptsRow = true; - let acceptedOnRow: Record = {}; - for (let colIdx = 0; colIdx < columns.length; colIdx++) { + const acceptedOnRow: Record = {}; + for (let colIdx = 0; colIdx < columns.length; colIdx + 1) { const col = columns[colIdx]; const helper = getHelper(col); const colKey = col.dataKey; @@ -295,11 +532,11 @@ export class KeyedColumnsRowIndexer { } if (acceptsRow) { - for (let [idx, value] of Object.entries(acceptedOnRow)) { - const col = columns[idx as unknown as number]; + Object.entries(acceptedOnRow).forEach(([idx, value]) => { + const col = columns[Number(idx)]; const helper = getHelper(col); helper.updateStat(cs[col.dataKey], value, true); - } + }); ri.push([row, rowIdx]); } } @@ -321,7 +558,7 @@ export class KeyedColumnsRowIndexer { ? null : codedColumnsFromKeyAndDirection(this.sortingState, columns); const groupingColumnsCount = this.groupingCount; - let indexedArray = groupAndSort( + const indexedArray = groupAndSort( preFilteredRowPairs, codedColumns, groupingColumnsCount, @@ -344,10 +581,10 @@ export class KeyedColumnsRowIndexer { }; // returns true if really changed (and calls versionSetter if needed) - updateSortingFromUser = (colKey: string, change_way: CHANGE_WAYS) => { + updateSortingFromUser = (colKey: string, change_way: ChangeWays) => { const keyAndDirections = this.sortingState; - if (change_way === CHANGE_WAYS.REMOVE) { + if (change_way === ChangeWays.REMOVE) { if (!keyAndDirections) { return false; } @@ -362,12 +599,12 @@ export class KeyedColumnsRowIndexer { this.sortingState = [[colKey, canonicalForSign(1)]]; this.lastUsedRank = 1; } else { - let wasAtIdx = keyAndDirections.findIndex((p) => p[0] === colKey); + const wasAtIdx = keyAndDirections.findIndex((p) => p[0] === colKey); const wasFuzzyDir = wasAtIdx < 0 ? 0 : keyAndDirections[wasAtIdx][1]; const wasSignDir = giveDirSignFor(wasFuzzyDir); - if (change_way === CHANGE_WAYS.SIMPLE) { + if (change_way === ChangeWays.SIMPLE) { if (wasSignDir < 0 && this.isThreeState) { if (this.sortingState?.length === 1) { this.sortingState = null; @@ -387,7 +624,7 @@ export class KeyedColumnsRowIndexer { ]; this.sortingState?.unshift(nextKD); } - } else if (change_way === CHANGE_WAYS.TAIL) { + } else if (change_way === ChangeWays.TAIL) { if (wasAtIdx < 0) { this.sortingState?.push([colKey, canonicalForSign(1)]); } else if (wasAtIdx !== keyAndDirections.length - 1) { @@ -402,19 +639,19 @@ export class KeyedColumnsRowIndexer { } } else { // AMEND + // eslint-disable-next-line no-lonely-if if (wasAtIdx < 0) { if ( this.lastUsedRank - 1 > - //@ts-ignore could be undefined, how to handle this case ? + // @ts-ignore could be undefined, how to handle this case ? this.sortingState.length ) { return false; - } else { - this.sortingState?.splice(this.lastUsedRank - 1, 0, [ - colKey, - canonicalForSign(1), - ]); } + this.sortingState?.splice(this.lastUsedRank - 1, 0, [ + colKey, + canonicalForSign(1), + ]); } else if (!(this.isThreeState && wasSignDir === -1)) { // @ts-ignore could be null but hard to handle with such accesses this.sortingState[wasAtIdx][1] = canonicalForSign( @@ -426,7 +663,7 @@ export class KeyedColumnsRowIndexer { } } } - this._bumpVersion(); + this.bumpVersion(); return true; }; @@ -459,7 +696,7 @@ export class KeyedColumnsRowIndexer { return giveDirSignFor(colSorting[1]) * (idx + 1); }; - _getColFilterParams = (colKey: string | null, isForUser: boolean) => { + getColFilterParams = (colKey: string | null, isForUser: boolean) => { if (!colKey || !this.byColFilter) { return undefined; } @@ -471,7 +708,7 @@ export class KeyedColumnsRowIndexer { return colFilter[isForUser ? 'userParams' : 'outerParams']; }; - _setColFilterParams = ( + setColFilterParams = ( colKey: string | null, params: any[] | null, isForUser: boolean @@ -512,25 +749,25 @@ export class KeyedColumnsRowIndexer { } } if (isForUser) { - this._bumpVersion(true); + this.bumpVersion(true); } return true; }; getColFilterOuterParams = (colKey: string | null) => { - return this._getColFilterParams(colKey, false); + return this.getColFilterParams(colKey, false); }; setColFilterOuterParams = (colKey: string, outerParams: any[]) => { - return this._setColFilterParams(colKey, outerParams, false); + return this.setColFilterParams(colKey, outerParams, false); }; getColFilterUserParams = (colKey: string | null) => { - return this._getColFilterParams(colKey, true); + return this.getColFilterParams(colKey, true); }; setColFilterUserParams = (colKey: string | null, params: any[] | null) => { - return this._setColFilterParams(colKey, params, true); + return this.setColFilterParams(colKey, params, true); }; getUserFiltering = () => { @@ -552,216 +789,10 @@ export class KeyedColumnsRowIndexer { throw new Error('row filter should be a function'); } this.byRowFilter = rowFilterFunc; - this._bumpVersion(); + this.bumpVersion(); }; } -const giveDirSignFor = (fuzzySign: number | string | undefined) => { - //@ts-ignore we should check whether it is a string or a number first - if (fuzzySign < 0) { - return -1; - } - //@ts-ignore we should check whether it is a string or a number first - if (fuzzySign > 0) { - return 1; - } - if (fuzzySign === 'asc') { - return 1; - } - if (fuzzySign === 'desc') { - return -1; - } - return 0; -}; - -const canonicalForSign = (dirSign: number): string | undefined => { - if (dirSign > 0) { - return 'asc'; - } - if (dirSign < 0) { - return 'desc'; - } - return undefined; -}; - -const codedColumnsFromKeyAndDirection = ( - keyAndDirections: [string, string | undefined][] | null, - columns: CustomColumnProps[] -) => { - if (!keyAndDirections) { - return null; - } - - const ret = []; - const columIndexByKey: Record = {}; - for (let colIdx = 0; colIdx < columns.length; colIdx++) { - const col = columns[colIdx]; - const colKey = col.dataKey; - columIndexByKey[colKey] = colIdx; - } - - for (const knd of keyAndDirections) { - const colKey = knd[0]; - const dir = knd[1]; - - const colIdx = columIndexByKey[colKey]; - if (colIdx === undefined) { - continue; - } - - const sign = giveDirSignFor(dir); - ret.push((colIdx + 1) * sign); - } - return ret; -}; - -const compareValue = ( - a: number | string | undefined, - b: number | string | undefined, - isNumeric: boolean, - undefSign: number = -1 -) => { - if (a === undefined && b === undefined) { - return 0; - } else { - if (a === undefined) { - return undefSign; - } else { - if (b === undefined) { - return -undefSign; - } - } - } - if (!isNumeric) { - return ('' + a).localeCompare(b as string); - } else { - if (isNaN(a as number)) { - return isNaN(b as number) ? 0 : 1; - } - if (isNaN(b as number)) { - return -1; - } - return Math.sign(Number(a) - Number(b)); - } -}; - -const makeCompositeComparatorFromCodedColumns = ( - codedColumns: number[] | null, - columns: CustomColumnProps[], - rowExtractor: ( - row: [Record, number][] - ) => Record -) => { - return ( - row_a_i: [Record, number][], - row_b_i: [Record, number][] - ) => { - const row_a = rowExtractor(row_a_i); - const row_b = rowExtractor(row_b_i); - //@ts-ignore codedColumns could be null we should add a check - for (const cc of codedColumns) { - const i = Math.abs(cc) - 1; - const mul = Math.sign(cc); - const col = columns[i]; - const key = col.dataKey; - //@ts-ignore numeric could be undefined, how to handle this case ? - const sgn = compareValue(row_a[key], row_b[key], col.numeric); - if (sgn) { - return mul * sgn; - } - } - return 0; - }; -}; - -const groupRows = ( - groupingColumnsCount: number, - columns: CustomColumnProps[], - indexedArray: [Record, number][] -) => { - const groupingComparator = makeCompositeComparatorFromCodedColumns( - Array(groupingColumnsCount).map((x, i) => i + 1), - columns, - //@ts-ignore does not match other pattern - (ar) => ar[0] - ); - //@ts-ignore does not match other pattern - indexedArray.sort(groupingComparator); - - const groups: any = []; - let prevSlice = null; - let inBuildGroup: any = []; - groups.push(inBuildGroup); - for (const p of indexedArray) { - // @ts-ignore could be undefined how to handle this case ? - const nextSlice = p[0].slice(0, groupingColumnsCount); - if (prevSlice === null || !equalsArray(prevSlice, nextSlice)) { - inBuildGroup = []; - groups.push(inBuildGroup); - } - inBuildGroup.push(p); - prevSlice = nextSlice; - } - return groups; -}; - -const groupAndSort = ( - preFilteredRowPairs: FilteredRows, - codedColumns: number[] | null, - groupingColumnsCount: number, - columns: CustomColumnProps[] -) => { - const nothingToDo = !codedColumns && !groupingColumnsCount; - - let indexedArray = preFilteredRowPairs.rowAndOrigIndex; - const noOutFiltered = preFilteredRowPairs.rowsCount === indexedArray.length; - if (nothingToDo && noOutFiltered) { - return null; - } else if (!nothingToDo) { - // make a copy for not losing base order - indexedArray = [...indexedArray]; - } - - if (nothingToDo) { - // just nothing - } else if (!groupingColumnsCount) { - const sortingComparator = makeCompositeComparatorFromCodedColumns( - codedColumns, - columns, - //@ts-ignore I don't know how to fix this one - (ar) => ar[0] - ); - //@ts-ignore I don't know how to fix this one - indexedArray.sort(sortingComparator); - } else { - //@ts-ignore I don't know how to fix this one - const groups = groupRows(groupingColumnsCount, columns, indexedArray); - - const interGroupSortingComparator = - makeCompositeComparatorFromCodedColumns( - codedColumns, - columns, - (ar) => ar[0][0] - ); - groups.sort(interGroupSortingComparator); - - const intraGroupSortingComparator = - makeCompositeComparatorFromCodedColumns( - codedColumns, - columns, - //@ts-ignore I don't know how to fix this one - (ar) => ar[0] - ); - - indexedArray = []; - for (const group of groups) { - group.sort(intraGroupSortingComparator); - indexedArray.push(...group); - } - } - return indexedArray; -}; - export const forTesting = { codedColumnsFromKeyAndDirection, makeCompositeComparatorFromCodedColumns, diff --git a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx index 4466c9d0..a310553a 100644 --- a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx +++ b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx @@ -11,7 +11,6 @@ import { createRef, PureComponent, - ReactElement, ReactNode, MouseEvent, KeyboardEvent, @@ -29,6 +28,7 @@ import { TableCell, TextField, } from '@mui/material'; +// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { GetApp as GetAppIcon } from '@mui/icons-material'; import { @@ -40,30 +40,33 @@ import { TableCellProps, } from 'react-virtualized'; import CsvDownloader from 'react-csv-downloader'; -import OverflowableText from '../OverflowableText/overflowable-text'; +import { OverflowableText } from '../OverflowableText/overflowable-text'; import { makeComposeClasses, toNestedGlobalSelectors, } from '../../utils/styles'; import { - CHANGE_WAYS, + ChangeWays, collectibleHelper, + CustomColumnProps, getHelper, KeyedColumnsRowIndexer, + RowProps, } from './KeyedColumnsRowIndexer'; -import ColumnHeader from './ColumnHeader'; +import { ColumnHeader } from './ColumnHeader'; +import { on } from 'events'; function getTextWidth(text: any): number { // re-use canvas object for better performance - let canvas = - //@ts-ignore this is questioning + const canvas = + // @ts-ignore this is questioning getTextWidth.canvas || - //@ts-ignore this is questioning + // @ts-ignore this is questioning (getTextWidth.canvas = document.createElement('canvas')); - let context = canvas.getContext('2d'); + const context = canvas.getContext('2d'); // TODO find a better way to find Material UI style context.font = '14px "Roboto", "Helvetica", "Arial", sans-serif'; - let metrics = context.measureText(text); + const metrics = context.measureText(text); return metrics.width; } @@ -104,7 +107,7 @@ const defaultStyles = { [cssTableRowHover]: {}, [cssTableCell]: { flex: 1, - padding: DEFAULT_CELL_PADDING + 'px', + padding: `${DEFAULT_CELL_PADDING}px`, }, [cssTableCellColor]: {}, [cssNoClick]: { @@ -125,7 +128,7 @@ const defaultTooltipSx = { fontSize: '0.9rem', }; -//TODO do we need to export this to clients (index.js) ? +// TODO do we need to export this to clients (index.js) ? export const generateMuiVirtualizedTableClass = (className: string) => `MuiVirtualizedTable-${className}`; const composeClasses = makeComposeClasses(generateMuiVirtualizedTableClass); @@ -138,7 +141,9 @@ interface AmongChooserProps { id: string; } -const AmongChooser = (props: AmongChooserProps) => { +function AmongChooser( + props: Readonly> +) { const { options, value, setValue, id, onDropDownVisibility } = props; return ( @@ -146,20 +151,22 @@ const AmongChooser = (props: AmongChooserProps) => { { setValue(newVal); }} onClose={() => onDropDownVisibility(false)} onOpen={() => onDropDownVisibility(true)} options={options} - renderInput={(props) => } + renderInput={(inputProps) => ( + + )} renderTags={(val, getTagsProps) => { return val.map((code, index) => { return ( @@ -169,7 +176,7 @@ const AmongChooser = (props: AmongChooserProps) => { /> ); -}; +} function makeIndexRecord( viewIndexToModel: number[] | null, @@ -192,22 +199,6 @@ function makeIndexRecord( }; } -export interface CustomColumnProps extends ColumnProps { - sortable?: boolean; - numeric?: boolean; - indexer?: KeyedColumnsRowIndexer; - label: string; - clickable?: boolean; - fractionDigits?: number; - unit?: number; - extra?: ReactElement; - nostat?: boolean; -} - -export interface RowProps { - notClickable?: boolean; -} - const initIndexer = ( props: CustomColumnProps, versionSetter: (version: number) => void @@ -229,6 +220,7 @@ const preFilterData = memoize( rows, filterFromProps, indexer, + // eslint-disable-next-line @typescript-eslint/no-unused-vars filterVersion // filterVersion is unused directly, used only as a workaround just to reset the memoization ) => { return indexer.preFilterRowMapping(columns, rows, filterFromProps); @@ -259,7 +251,7 @@ const reorderIndex = memoize( : indexer.highestCodedColumn(columns); if (sortFromProps && highestCodedColumn) { const colIdx = Math.abs(highestCodedColumn) - 1; - let reorderedIndex = sortFromProps( + const reorderedIndex = sortFromProps( columns[colIdx].dataKey, highestCodedColumn > 0, !!columns[colIdx].numeric @@ -271,8 +263,8 @@ const reorderIndex = memoize( const viewIndexToModel = sortFromProps(null, false, false); return makeIndexRecord(viewIndexToModel, rows); } catch (e) { - //some external sort functions may expect to only be called - //when the user has select a column. Catch their errors and ignore + // some external sort functions may expect to only be called + // when the user has select a column. Catch their errors and ignore console.warn( 'error in external sort. consider adding support for datakey=null in your external sort function' ); @@ -295,8 +287,8 @@ const reorderIndex = memoize( if (filterFromProps) { const viewIndexToModel = rows .map((r: unknown, i: number) => [r, i]) - .filter(([r, _]: [unknown, number]) => filterFromProps(r)) - .map(([_, j]: [unknown, number]) => j); + .filter(([r]: [unknown, number]) => filterFromProps(r)) + .map(([j]: [unknown, number]) => j); return makeIndexRecord(viewIndexToModel, rows); } @@ -305,20 +297,20 @@ const reorderIndex = memoize( ); export interface MuiVirtualizedTableProps extends CustomColumnProps { - headerHeight: number; + headerHeight?: number; columns: CustomColumnProps[]; defersFilterChanges: (() => void) | null; rows: RowProps[]; filter: unknown; sort: unknown; - classes: Record; + classes?: Record; onRowClick?: (event: RowMouseEventHandlerParams) => void; - rowHeight: number; + rowHeight?: number; onCellClick: (row: RowProps, column: ColumnProps) => void; tooltipSx: SxProps; name: string; exportCSVDataKeys: unknown[]; - enableExportCSV: boolean; + enableExportCSV?: boolean; } export interface MuiVirtualizedTableState { @@ -337,39 +329,98 @@ class MuiVirtualizedTable extends PureComponent< MuiVirtualizedTableProps, MuiVirtualizedTableState > { + // eslint-disable-next-line react/static-property-placement static defaultProps = { headerHeight: DEFAULT_HEADER_HEIGHT, rowHeight: DEFAULT_ROW_HEIGHT, enableExportCSV: false, classes: {}, + onRowClick: undefined, + }; + + static readonly computeDataWidth = (text: string) => { + return getTextWidth(text || '') + 2 * DEFAULT_CELL_PADDING; }; headers: MutableRefObject; + observer: IntersectionObserver; + dropDownVisible: boolean; + sizes = memoize((columns, rows, rowGetter) => { + const sizes: Record = {}; + columns.forEach((col: CustomColumnProps) => { + if (col.width) { + sizes[col.dataKey] = col.width; + } else { + /* calculate the header (and min size if exists) + * NB : ignores the possible icons + */ + let size = Math.max( + col.minWidth ?? 0, + MuiVirtualizedTable.computeDataWidth(col.label) + ); + /* calculate for each row the width, and keep the max */ + for (let i = 0; i < rows.length; i += 1) { + const gotRow = rowGetter(i); + const text = MuiVirtualizedTable.getDisplayValue( + col, + gotRow[col.dataKey] + ); + size = Math.max( + size, + MuiVirtualizedTable.computeDataWidth(text) + ); + } + if (col.maxWidth) { + size = Math.min(col.maxWidth, size); + } + sizes[col.dataKey] = Math.ceil(size); + } + }); + return sizes; + }); + + csvHeaders = memoize((columns, exportCSVDataKeys) => { + const tempHeaders: { displayName: string; id: string }[] = []; + columns.forEach((col: CustomColumnProps) => { + if ( + exportCSVDataKeys !== undefined && + exportCSVDataKeys.find((el: string) => el === col.dataKey) + ) { + tempHeaders.push({ + displayName: col.label, + id: col.dataKey, + }); + } + }); + return tempHeaders; + }); + constructor(props: MuiVirtualizedTableProps, context: any) { super(props, context); - this._computeHeaderSize = this._computeHeaderSize.bind(this); - this._registerHeader = this._registerHeader.bind(this); - this._registerObserver = this._registerObserver.bind(this); + this.computeHeaderSize = this.computeHeaderSize.bind(this); + this.registerHeader = this.registerHeader.bind(this); + this.registerObserver = this.registerObserver.bind(this); // we shouldn't use createRef here, just defining an object would be enough // We have to type RefObject to MutableRefObject to enable mutability, and TS enables that... this.headers = createRef(); this.headers.current = {}; - let options = { + const options = { root: null, rootMargin: '0px', threshold: 0.1, }; this.observer = new IntersectionObserver( - this._computeHeaderSize, + this.computeHeaderSize, options ); this.dropDownVisible = false; + const { headerHeight } = this.props; this.state = { - headerHeight: this.props.headerHeight, + headerHeight, indexer: initIndexer(props, this.setVersion), indirectionVersion: 0, popoverAnchorEl: null, @@ -378,15 +429,13 @@ class MuiVirtualizedTable extends PureComponent< }; } - setVersion = (v: number) => { - this.setState({ indirectionVersion: v }); - }; + componentDidMount() { + window.addEventListener('resize', this.computeHeaderSize); + } componentDidUpdate(oldProps: MuiVirtualizedTableProps) { - if ( - oldProps.indexer !== this.props.indexer || - oldProps.sortable !== this.props.sortable - ) { + const { indexer, sortable, headerHeight } = this.props; + if (oldProps.indexer !== indexer || oldProps.sortable !== sortable) { this.setState((state) => { return { indexer: initIndexer(this.props, this.setVersion), @@ -394,66 +443,158 @@ class MuiVirtualizedTable extends PureComponent< }; }); } - if (oldProps.headerHeight !== this.props.headerHeight) { - this._computeHeaderSize(); + if (oldProps.headerHeight !== headerHeight) { + this.computeHeaderSize(); } } - componentDidMount() { - window.addEventListener('resize', this._computeHeaderSize); - } - componentWillUnmount() { - window.removeEventListener('resize', this._computeHeaderSize); + window.removeEventListener('resize', this.computeHeaderSize); this.observer.disconnect(); } - _registerHeader(label: string, header: unknown) { - if (this.headers.current) { - this.headers.current[label] = header; + handleKeyDownOnPopover = (evt: KeyboardEvent) => { + if (evt.key === 'Enter' && !this.dropDownVisible) { + this.closePopover(evt, 'enterKeyDown'); } - } + }; - _registerObserver(element: Element) { - if (element !== null) { - this.observer.observe(element); + onFilterParamsChange(newVal: unknown[] | null, colKey: string | null) { + const { indexer, indirectionVersion } = this.state; + const { defersFilterChanges } = this.props; + const nonEmpty = newVal?.length === 0 ? null : newVal; + if (defersFilterChanges) { + this.setState({ + deferredFilterChange: { newVal, colKey }, + }); + } else if (indexer?.setColFilterUserParams(colKey, nonEmpty)) { + this.setState({ + indirectionVersion: indirectionVersion + 1, + }); } } - computeDataWidth = (text: string) => { - return getTextWidth(text || '') + 2 * DEFAULT_CELL_PADDING; + getCSVData = () => { + const { rows, columns, filter, sort, exportCSVDataKeys } = this.props; + const { indexer, indirectionVersion } = this.state; + const reorderedIndex = reorderIndex( + indexer, + indirectionVersion, + rows, + columns, + filter, + sort + ); + const rowsCount = + reorderedIndex.viewIndexToModel?.length ?? rows.length; + + const csvData = []; + for (let index = 0; index < rowsCount; index += 1) { + const myobj: Record = {}; + const sortedRow: any = reorderedIndex.rowGetter(index); + const exportedKeys = exportCSVDataKeys; + columns.forEach((col) => { + if (exportedKeys?.find((el: any) => el === col.dataKey)) { + myobj[col.dataKey] = sortedRow[col.dataKey]; + } + }); + csvData.push(myobj); + } + + return Promise.resolve(csvData); }; - sizes = memoize((columns, rows, rowGetter) => { - let sizes: Record = {}; - columns.forEach((col: CustomColumnProps) => { - if (col.width) { - sizes[col.dataKey] = col.width; - } else { - /* calculate the header (and min size if exists) - * NB : ignores the possible icons - */ - let size = Math.max( - col.minWidth || 0, - this.computeDataWidth(col.label) - ); - /* calculate for each row the width, and keep the max */ - for (let i = 0; i < rows.length; ++i) { - const gotRow = rowGetter(i); - let text = this.getDisplayValue(col, gotRow[col.dataKey]); - size = Math.max(size, this.computeDataWidth(text)); + getCSVFilename = () => { + const { name, columns } = this.props; + if (name?.length > 0) { + return name.replace(/\s/g, '_'); + } + const filename = Object.entries(columns) + .map((p) => p[1].label) + .join('_'); + return filename; + }; + + // type check should be increased here + static getDisplayValue(column: CustomColumnProps, cellData: any) { + let displayedValue: any; + if (!column.numeric) { + displayedValue = cellData; + } else if (Number.isNaN(cellData)) { + displayedValue = ''; + } else if ( + column.fractionDigits === undefined || + column.fractionDigits === 0 + ) { + displayedValue = Math.round(cellData); + } else { + displayedValue = Number(cellData).toFixed(column.fractionDigits); + } + + if (column.unit !== undefined) { + displayedValue += ' '; + displayedValue += column.unit; + } + return displayedValue; + } + + makeSizedTable = ( + height: number, + width: number, + sizes: Record, + reorderedIndex: number[] | null, + rowGetter: ((index: number) => RowProps) | ((index: number) => number) + ) => { + const { sort, onRowClick, ...otherProps } = this.props; + const { headerHeight } = this.state; + return ( + + this.getRowClassName({ index, rowGetter }) } - sizes[col.dataKey] = Math.ceil(size); - } - }); - return sizes; - }); + rowGetter={({ index }) => rowGetter(index)} + > + {otherProps.columns.map(({ dataKey, ...other }, index) => { + return ( + + ); + })} +
+ ); + }; openPopover = (popoverTarget: Element, colKey: string) => { - const col = this.props.columns.find((c) => c.dataKey === colKey); + const { columns } = this.props; + const col = columns.find((c) => c.dataKey === colKey); if (getHelper(col) !== collectibleHelper) { return; } @@ -465,18 +606,12 @@ class MuiVirtualizedTable extends PureComponent< }); }; - handleKeyDownOnPopover = (evt: KeyboardEvent) => { - if (evt.key === 'Enter' && !this.dropDownVisible) { - this.closePopover(evt, 'enterKeyDown'); - } - }; - closePopover = (_: KeyboardEvent, reason: string) => { let bumpsVersion = false; if (reason === 'backdropClick' || reason === 'enterKeyDown') { - bumpsVersion = this._commitFilterChange(); + bumpsVersion = this.commitFilterChange(); } - this.setState((state, _) => { + this.setState((state) => { return { popoverAnchorEl: null, popoverColKey: null, @@ -488,32 +623,39 @@ class MuiVirtualizedTable extends PureComponent< }; makeColumnFilterEditor = () => { - const colKey = this.state.popoverColKey; - const outerParams = this.state.indexer?.getColFilterOuterParams(colKey); + const { columns, rows, filter, defersFilterChanges } = this.props; + const { + popoverColKey, + indexer, + deferredFilterChange, + indirectionVersion, + } = this.state; + const colKey = popoverColKey; + const outerParams = indexer?.getColFilterOuterParams(colKey); const userParams = - !this.props.defersFilterChanges || !this.state.deferredFilterChange - ? this.state.indexer?.getColFilterUserParams(colKey) - : this.state.deferredFilterChange.newVal ?? undefined; + !defersFilterChanges || !deferredFilterChange + ? indexer?.getColFilterUserParams(colKey) + : deferredFilterChange.newVal ?? undefined; const prefiltered = preFilterData( - this.props.columns, - this.props.rows, - this.props.filter, - this.state.indexer, - this.state.indirectionVersion + columns, + rows, + filter, + indexer, + indirectionVersion ); - let options = []; + const options: any[] = []; if (outerParams) { options.push(...outerParams); } // @ts-ignore colKey could be null, how to handle this ? const colStat = prefiltered?.colsStats?.[colKey]; if (colStat?.seen) { - for (const key of Object.getOwnPropertyNames(colStat.seen)) { + Object.keys(colStat.seen).forEach((key) => { if (options.findIndex((o) => o === key) < 0) { options.push(key); } - } + }); } options.sort(); @@ -521,25 +663,26 @@ class MuiVirtualizedTable extends PureComponent< { this.onFilterParamsChange(newVal, colKey); }} - onDropDownVisibility={(visible) => - (this.dropDownVisible = visible) - } + onDropDownVisibility={(visible) => { + this.dropDownVisible = visible; + }} /> ); }; - _commitFilterChange = () => { - if (this.state.deferredFilterChange) { - const colKey = this.state.deferredFilterChange.colKey; - let newVal = this.state.deferredFilterChange.newVal; + commitFilterChange = () => { + const { deferredFilterChange, indexer } = this.state; + if (deferredFilterChange) { + const { colKey } = deferredFilterChange; + let { newVal } = deferredFilterChange; if (newVal?.length === 0) { newVal = null; } - if (this.state.indexer?.setColFilterUserParams(colKey, newVal)) { + if (indexer?.setColFilterUserParams(colKey, newVal)) { return true; } } @@ -547,42 +690,34 @@ class MuiVirtualizedTable extends PureComponent< return false; }; - onFilterParamsChange(newVal: unknown[] | null, colKey: string | null) { - const nonEmpty = newVal?.length === 0 ? null : newVal; - if (this.props.defersFilterChanges) { - this.setState({ - deferredFilterChange: { newVal: newVal, colKey }, - }); - } else if ( - this.state.indexer?.setColFilterUserParams(colKey, nonEmpty) - ) { - this.setState({ - indirectionVersion: this.state.indirectionVersion + 1, - }); - } - } + setVersion = (v: number) => { + this.setState({ indirectionVersion: v }); + }; sortClickHandler = (evt: MouseEvent, _: unknown, columnIndex: number) => { - const colKey = this.props.columns[columnIndex].dataKey; + const { columns } = this.props; + const colKey = columns[columnIndex].dataKey; + + const { indexer, indirectionVersion } = this.state; if (evt.altKey) { - //@ts-ignore should be currentTarget maybe ? + // @ts-ignore should be currentTarget maybe ? this.openPopover(evt.target, colKey); return; } - let way = CHANGE_WAYS.SIMPLE; + let way = ChangeWays.SIMPLE; if (evt.ctrlKey && evt.shiftKey) { - way = CHANGE_WAYS.AMEND; + way = ChangeWays.AMEND; } else if (evt.ctrlKey) { - way = CHANGE_WAYS.REMOVE; + way = ChangeWays.REMOVE; } else if (evt.shiftKey) { - way = CHANGE_WAYS.TAIL; + way = ChangeWays.TAIL; } - if (this.state.indexer?.updateSortingFromUser(colKey, way)) { + if (indexer?.updateSortingFromUser(colKey, way)) { this.setState({ - indirectionVersion: this.state.indirectionVersion + 1, + indirectionVersion: indirectionVersion + 1, }); } }; @@ -592,11 +727,12 @@ class MuiVirtualizedTable extends PureComponent< target: Element | undefined, columnIndex: number ) => { + const { columns } = this.props; // ColumnHeader to (header) TableCell const retargeted = target?.parentNode ?? target; - const colKey = this.props.columns[columnIndex].dataKey; - //@ts-ignore still not the good types + const colKey = columns[columnIndex].dataKey; + // @ts-ignore still not the good types this.openPopover(retargeted, colKey); }; @@ -607,17 +743,17 @@ class MuiVirtualizedTable extends PureComponent< label: string; columnIndex: number; }) => { - const { columns } = this.props; - const indexer = this.state.indexer; + const { columns, rows, filter, sort } = this.props; + const { indexer } = this.state; const colKey = columns[columnIndex].dataKey; const signedRank = indexer?.columnSortingSignedRank(colKey); const userParams = indexer?.getColFilterUserParams(colKey); - const numeric = columns[columnIndex].numeric; + const { numeric } = columns[columnIndex]; const prefiltered = preFilterData( columns, - this.props.rows, - this.props.filter, + rows, + filter, indexer, indexer?.filterVersion ); @@ -639,7 +775,7 @@ class MuiVirtualizedTable extends PureComponent< // - a cellRenderer is defined, as we have no simple way to match for chosen value(s) // - using an external sort, because it would hardly know about the indexer filtering const onFilterClick = - numeric || this.props.sort || columns[columnIndex].cellRenderer + numeric || sort || columns[columnIndex].cellRenderer ? undefined : (ev: MouseEvent, retargeted?: Element) => { this.filterClickHandler(ev, retargeted, columnIndex); @@ -647,7 +783,7 @@ class MuiVirtualizedTable extends PureComponent< return ( this._registerHeader(label, e)} + ref={(e) => this.registerHeader(label, e)} sortSignedRank={signedRank} filterLevel={filterLevel} numeric={numeric ?? false} @@ -666,7 +802,7 @@ class MuiVirtualizedTable extends PureComponent< return (
{ - this._registerHeader(label, element); + this.registerHeader(label, element); }} > {label} @@ -697,13 +833,14 @@ class MuiVirtualizedTable extends PureComponent< }; onClickableRowClick = (event: RowMouseEventHandlerParams) => { + const { onRowClick } = this.props; if ( event.rowData?.notClickable !== true || event.event?.shiftKey || event.event?.ctrlKey ) { - //@ts-ignore onRowClick is possibly undefined - this.props.onRowClick(event); + // @ts-ignore onRowClick is possibly undefined + onRowClick(event); } }; @@ -711,7 +848,7 @@ class MuiVirtualizedTable extends PureComponent< const { columns, classes, rowHeight, onCellClick, rows, tooltipSx } = this.props; - let displayedValue = this.getDisplayValue( + const displayedValue = MuiVirtualizedTable.getDisplayValue( columns[columnIndex], cellData ); @@ -758,37 +895,29 @@ class MuiVirtualizedTable extends PureComponent< > ); }; - // type check should be increased here - getDisplayValue(column: CustomColumnProps, cellData: any) { - let displayedValue: any; - if (!column.numeric) { - displayedValue = cellData; - } else if (isNaN(cellData)) { - displayedValue = ''; - } else if ( - column.fractionDigits === undefined || - column.fractionDigits === 0 - ) { - displayedValue = Math.round(cellData); - } else { - displayedValue = Number(cellData).toFixed(column.fractionDigits); + + registerHeader(label: string, header: unknown) { + if (this.headers.current) { + this.headers.current[label] = header; } + } - if (column.unit !== undefined) { - displayedValue += ' '; - displayedValue += column.unit; + registerObserver(element: Element) { + if (element !== null) { + this.observer.observe(element); } - return displayedValue; } - _computeHeaderSize() { + computeHeaderSize() { + const { headerHeight: stateHeaderHeight } = this.state; + const { headerHeight: propsHeaderHeight } = this.props; const headers = Object.values(this.headers.current); if (headers.length === 0) { return; @@ -797,20 +926,21 @@ class MuiVirtualizedTable extends PureComponent< // though it can not make a difference from clientHeight, // as overflow-y as no scroll value const scrollHeights = headers.map((header: any) => header.scrollHeight); - let headerHeight = Math.max( + const computedHeaderHeight = Math.max( Math.max(...scrollHeights) + DEFAULT_CELL_PADDING, - this.props.headerHeight + propsHeaderHeight // hides (most often) padding override by forcing height ); - if (headerHeight !== this.state.headerHeight) { + if (computedHeaderHeight !== stateHeaderHeight) { this.setState({ - headerHeight: headerHeight, + headerHeight: computedHeaderHeight, }); } } makeHeaderRenderer(dataKey: string, columnIndex: number) { - const { columns, classes } = this.props; + const { columns, classes, sortable } = this.props; + const { indexer, headerHeight } = this.state; return (headerProps: any) => { return ( this._registerObserver(e)} + ref={(e: Element) => this.registerObserver(e)} > - {this.props.sortable && this.state.indexer + {sortable && indexer ? this.sortableHeader({ ...headerProps, columnIndex, @@ -843,134 +973,29 @@ class MuiVirtualizedTable extends PureComponent< }; } - makeSizedTable = ( - height: number, - width: number, - sizes: Record, - reorderedIndex: number[] | null, - rowGetter: ((index: number) => RowProps) | ((index: number) => number) - ) => { - const { sort, ...otherProps } = this.props; - - return ( - - this.getRowClassName({ index, rowGetter }) - } - rowGetter={({ index }) => rowGetter(index)} - > - {otherProps.columns.map(({ dataKey, ...other }, index) => { - return ( - - ); - })} -
- ); - }; - - getCSVFilename = () => { - if (this.props.name?.length > 0) { - return this.props.name.replace(/\s/g, '_'); - } else { - let filename = Object.entries(this.props.columns) - .map((p) => p[1].label) - .join('_'); - return filename; - } - }; - - getCSVData = () => { - let reorderedIndex = reorderIndex( - this.state.indexer, - this.state.indirectionVersion, - this.props.rows, - this.props.columns, - this.props.filter, - this.props.sort - ); - let rowsCount = - reorderedIndex.viewIndexToModel?.length ?? this.props.rows.length; - - const csvData = []; - for (let index = 0; index < rowsCount; ++index) { - const myobj: Record = {}; - const sortedRow: any = reorderedIndex.rowGetter(index); - const exportedKeys = this.props.exportCSVDataKeys; - this.props.columns.forEach((col) => { - if (exportedKeys?.find((el: any) => el === col.dataKey)) { - myobj[col.dataKey] = sortedRow[col.dataKey]; - } - }); - csvData.push(myobj); - } - - return Promise.resolve(csvData); - }; - - csvHeaders = memoize((columns, exportCSVDataKeys) => { - let tempHeaders: { displayName: string; id: string }[] = []; - columns.forEach((col: CustomColumnProps) => { - if ( - exportCSVDataKeys !== undefined && - exportCSVDataKeys.find((el: string) => el === col.dataKey) - ) { - tempHeaders.push({ - displayName: col.label, - id: col.dataKey, - }); - } - }); - return tempHeaders; - }); - render() { + const { + rows, + columns, + filter, + sort, + exportCSVDataKeys, + className, + enableExportCSV, + } = this.props; + + const { indexer, indirectionVersion, popoverAnchorEl } = this.state; const { viewIndexToModel, rowGetter } = reorderIndex( - this.state.indexer, - this.state.indirectionVersion, - this.props.rows, - this.props.columns, - this.props.filter, - this.props.sort + indexer, + indirectionVersion, + rows, + columns, + filter, + sort ); - const sizes = this.sizes( - this.props.columns, - this.props.rows, - rowGetter - ); - const csvHeaders = this.csvHeaders( - this.props.columns, - this.props.exportCSVDataKeys - ); + const sizes = this.sizes(columns, rows, rowGetter); + const csvHeaders = this.csvHeaders(columns, exportCSVDataKeys); return (
- {this.props.enableExportCSV && ( + {enableExportCSV && (
- {this.state.popoverAnchorEl && ( + {popoverAnchorEl && ( {this.makeColumnFilterEditor()} diff --git a/src/components/MuiVirtualizedTable/index.ts b/src/components/MuiVirtualizedTable/index.ts index 63b47574..e9eee2f3 100644 --- a/src/components/MuiVirtualizedTable/index.ts +++ b/src/components/MuiVirtualizedTable/index.ts @@ -6,7 +6,7 @@ */ export { - default, + default as MuiVirtualizedTable, generateMuiVirtualizedTableClass, } from './MuiVirtualizedTable'; -export { KeyedColumnsRowIndexer, CHANGE_WAYS } from './KeyedColumnsRowIndexer'; +export { KeyedColumnsRowIndexer, ChangeWays } from './KeyedColumnsRowIndexer'; diff --git a/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx b/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx index 9a77a178..731111e6 100644 --- a/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx +++ b/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx @@ -29,7 +29,7 @@ export interface MultipleSelectionDialogProps { titleId: string; } -const MultipleSelectionDialog = ({ +function MultipleSelectionDialog({ options, selectedOptions, open, @@ -37,7 +37,7 @@ const MultipleSelectionDialog = ({ handleClose, handleValidate, titleId, -}: MultipleSelectionDialogProps) => { +}: Readonly) { const [selectedIds, setSelectedIds] = useState(selectedOptions ?? []); const handleSelectAll = () => { if (selectedIds.length !== options.length) { @@ -63,9 +63,7 @@ const MultipleSelectionDialog = ({ + } control={ ); -}; +} export default MultipleSelectionDialog; diff --git a/src/components/MultipleSelectionDialog/index.ts b/src/components/MultipleSelectionDialog/index.ts index 38168057..df3b37ab 100644 --- a/src/components/MultipleSelectionDialog/index.ts +++ b/src/components/MultipleSelectionDialog/index.ts @@ -5,4 +5,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default } from './MultipleSelectionDialog'; +// eslint-disable-next-line import/prefer-default-export +export { default as MultipleSelectionDialog } from './MultipleSelectionDialog'; diff --git a/src/components/OverflowableText/index.ts b/src/components/OverflowableText/index.ts index 59dc819e..72518eb0 100644 --- a/src/components/OverflowableText/index.ts +++ b/src/components/OverflowableText/index.ts @@ -1,7 +1,2 @@ -/** - * Copyright (c) 2021, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -export { default } from './overflowable-text'; +// eslint-disable-next-line import/prefer-default-export +export { default as OverflowableText } from './overflowable-text'; diff --git a/src/components/OverflowableText/overflowable-text.tsx b/src/components/OverflowableText/overflowable-text.tsx index 3d57c32d..c5d5caf0 100644 --- a/src/components/OverflowableText/overflowable-text.tsx +++ b/src/components/OverflowableText/overflowable-text.tsx @@ -12,8 +12,7 @@ import { useRef, useState, } from 'react'; -import { Box, BoxProps, SxProps, Tooltip } from '@mui/material'; -import { styled } from '@mui/system'; +import { Box, BoxProps, SxProps, Tooltip, styled } from '@mui/material'; import { Style } from 'node:util'; const overflowStyle = { @@ -114,14 +113,15 @@ export const OverflowableText = styled( + > + {children || text} + ); } diff --git a/src/components/ReportViewer/filter-button.tsx b/src/components/ReportViewer/filter-button.tsx index 84bf72d0..14613766 100644 --- a/src/components/ReportViewer/filter-button.tsx +++ b/src/components/ReportViewer/filter-button.tsx @@ -42,10 +42,10 @@ export interface FilterButtonProps { * @param {Function} setSelectedItems - Setter needed to update the list underlying data */ -export const FilterButton = ({ +export function FilterButton({ selectedItems, setSelectedItems, -}: FilterButtonProps) => { +}: FilterButtonProps) { const [initialState] = useState(selectedItems); const [anchorEl, setAnchorEl] = useState(null); @@ -86,4 +86,4 @@ export const FilterButton = ({ /> ); -}; +} diff --git a/src/components/ReportViewer/index.ts b/src/components/ReportViewer/index.ts index d1c63311..b1ec5e3d 100644 --- a/src/components/ReportViewer/index.ts +++ b/src/components/ReportViewer/index.ts @@ -4,4 +4,5 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default } from './report-viewer'; +// eslint-disable-next-line import/prefer-default-export +export { default as ReportViewer } from './report-viewer'; diff --git a/src/components/ReportViewer/log-report-item.ts b/src/components/ReportViewer/log-report-item.ts index dd93cbd7..37a7e068 100644 --- a/src/components/ReportViewer/log-report-item.ts +++ b/src/components/ReportViewer/log-report-item.ts @@ -9,8 +9,11 @@ import { LogSeverities, LogSeverity } from './log-severity'; export default class LogReportItem { key: string; + reportId: string; + severity: LogSeverity; + log: string; static resolveTemplateMessage( @@ -18,15 +21,18 @@ export default class LogReportItem { templateValues: Record ) { const templateVars: Record = {}; - for (const [key, value] of Object.entries(templateValues)) { + Object.entries(templateValues).forEach(([key, value]) => { templateVars[key] = value.value; - } - return templateMessage.replace(/\${([^{}]*)}/g, function (a, b) { - let r = templateVars[b]; - return typeof r === 'string' || typeof r === 'number' - ? r.toString() - : a; }); + return templateMessage.replace( + /\${([^{}]*)}/g, + function resolveTemplate(a, b) { + const r = templateVars[b]; + return typeof r === 'string' || typeof r === 'number' + ? r.toString() + : a; + } + ); } constructor(jsonReport: SubReport, reportId: string) { @@ -36,8 +42,8 @@ export default class LogReportItem { jsonReport.values ); this.reportId = reportId; - this.severity = this.initSeverity( - jsonReport.values['reportSeverity'] as unknown as ReportValue + this.severity = LogReportItem.initSeverity( + jsonReport.values.reportSeverity as unknown as ReportValue ); } @@ -65,14 +71,14 @@ export default class LogReportItem { return this.severity.colorHexCode; } - initSeverity(jsonSeverity: ReportValue) { + static initSeverity(jsonSeverity: ReportValue) { let severity = LogSeverities.UNKNOWN; if (!jsonSeverity) { return severity; } Object.values(LogSeverities).some((value) => { - let severityFound = (jsonSeverity.value as string).includes( + const severityFound = (jsonSeverity.value as string).includes( value.name ); if (severityFound) { diff --git a/src/components/ReportViewer/log-report.ts b/src/components/ReportViewer/log-report.ts index 87fa6b9e..cbffeb9b 100644 --- a/src/components/ReportViewer/log-report.ts +++ b/src/components/ReportViewer/log-report.ts @@ -5,18 +5,23 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { v4 as uuid4 } from 'uuid'; import LogReportItem from './log-report-item'; -import { v4 as uuid4 } from 'uuid'; import { Report } from './report.type'; import { LogSeverities, LogSeverity } from './log-severity'; export default class LogReport { id: string; + key: string; + title: string; + subReports: LogReport[]; + logs: LogReportItem[]; + parentReportId?: string; constructor(jsonReporter: Report, parentReportId?: string) { @@ -64,10 +69,10 @@ export default class LogReport { } getHighestSeverity(currentSeverity = LogSeverities.UNKNOWN): LogSeverity { - let reduceFct = (p: LogSeverity, c: LogSeverity) => + const reduceFct = (p: LogSeverity, c: LogSeverity) => p.level < c.level ? c : p; - let highestSeverity = this.getLogs() + const highestSeverity = this.getLogs() .map((r) => r.getSeverity()) .reduce(reduceFct, currentSeverity); diff --git a/src/components/ReportViewer/log-table.tsx b/src/components/ReportViewer/log-table.tsx index 06eda0c0..cd4b0c22 100644 --- a/src/components/ReportViewer/log-table.tsx +++ b/src/components/ReportViewer/log-table.tsx @@ -7,11 +7,12 @@ import { memo, useCallback, useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { TableCell, Theme, useTheme } from '@mui/material'; +// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; -import MuiVirtualizedTable from '../MuiVirtualizedTable'; +import { MuiVirtualizedTable } from '../MuiVirtualizedTable'; import { FilterButton } from './filter-button'; import LogReportItem from './log-report-item'; -import { RowProps } from '../MuiVirtualizedTable/MuiVirtualizedTable'; +import { RowProps } from '../MuiVirtualizedTable/KeyedColumnsRowIndexer'; const SEVERITY_COLUMN_FIXED_WIDTH = 115; @@ -44,12 +45,12 @@ export interface LogTableProps { ) => void; } -const LogTable = ({ +function LogTable({ logs, onRowClick, selectedSeverity, setSelectedSeverity, -}: LogTableProps) => { +}: LogTableProps) { const intl = useIntl(); const theme = useTheme(); @@ -125,13 +126,14 @@ const LogTable = ({ const rowStyleFormat = (row: { index: number }) => { if (row.index < 0) { - return; + return undefined; } if (selectedRowIndex === row.index) { return { backgroundColor: theme.palette.action.selected, }; } + return undefined; }; useEffect(() => { @@ -151,18 +153,18 @@ const LogTable = ({ ); return ( - //TODO do we need to useMemo/useCallback these props to avoid rerenders ? + // TODO do we need to useMemo/useCallback these props to avoid rerenders ? ); -}; +} export default memo(LogTable); diff --git a/src/components/ReportViewer/multi-select-list.tsx b/src/components/ReportViewer/multi-select-list.tsx index 32048ce9..2522cc3a 100644 --- a/src/components/ReportViewer/multi-select-list.tsx +++ b/src/components/ReportViewer/multi-select-list.tsx @@ -40,12 +40,12 @@ export interface MultiSelectListProps { * @param {Object} anchor - Determines where the menu will appear on screen */ -export const MultiSelectList = ({ +export function MultiSelectList({ selectedItems, handleChange, handleClose, anchor, -}: MultiSelectListProps) => { +}: MultiSelectListProps) { const open = Boolean(anchor); return ( @@ -68,4 +68,4 @@ export const MultiSelectList = ({ })} ); -}; +} diff --git a/src/components/ReportViewer/report-item.tsx b/src/components/ReportViewer/report-item.tsx index 7d922f3f..a1c5fec7 100644 --- a/src/components/ReportViewer/report-item.tsx +++ b/src/components/ReportViewer/report-item.tsx @@ -7,6 +7,7 @@ import { PropsWithChildren, ReactNode, useContext } from 'react'; import { Box, Theme, Typography } from '@mui/material'; +// eslint-disable-next-line import/no-extraneous-dependencies import { alpha, styled } from '@mui/system'; import { TreeItem, TreeItemProps } from '@mui/lab'; import { Label } from '@mui/icons-material'; @@ -87,11 +88,12 @@ export interface ReportItemProps extends TreeItemProps { className?: any; } -const ReportItem = (props: PropsWithChildren) => { +function ReportItem(props: PropsWithChildren) { + const { nodeId } = props; // using a context because TreeItem uses useMemo on this. See report-viewer.js for the provider const { isHighlighted } = useContext(ReportTreeViewContext); - const highlighted = isHighlighted ? isHighlighted(props.nodeId) : false; + const highlighted = isHighlighted ? isHighlighted(nodeId) : false; const { labelText, labelIconColor, className, ...other } = props; @@ -123,6 +125,10 @@ const ReportItem = (props: PropsWithChildren) => { {...other} /> ); +} + +ReportItem.defaultProps = { + className: undefined, }; export default styled(ReportItem)({}); diff --git a/src/components/ReportViewer/report-viewer.tsx b/src/components/ReportViewer/report-viewer.tsx index a5478c75..76d600b6 100644 --- a/src/components/ReportViewer/report-viewer.tsx +++ b/src/components/ReportViewer/report-viewer.tsx @@ -18,13 +18,14 @@ import { ArrowDropDown as ArrowDropDownIcon, ArrowRight as ArrowRightIcon, } from '@mui/icons-material'; +import { Grid } from '@mui/material'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { TreeView } from '@mui/x-tree-view'; import ReportItem from './report-item'; import LogReport from './log-report'; -import { Grid } from '@mui/material'; import LogTable from './log-table'; import ReportTreeViewContext from './report-tree-view-context'; import LogReportItem from './log-report-item'; -import { TreeView } from '@mui/x-tree-view'; import { Report } from './report.type'; import { LogSeverities } from './log-severity'; @@ -104,7 +105,7 @@ export default function ReportViewer({ useEffect(() => { rootReport.current = new LogReport(jsonReport); - let rootId = rootReport.current.getId().toString(); + const rootId = rootReport.current.getId().toString(); treeView.current = createReporterItem(rootReport.current); setSelectedNode(rootId); setExpandedNodes([rootId]); @@ -113,18 +114,14 @@ export default function ReportViewer({ const handleToggleNode = (event: SyntheticEvent, nodeIds: string[]) => { event.persist(); - //@ts-ignore + // @ts-ignore // With SyntheticEvent target is an EventTarget that does not have the 'closest' method so this shouldn't work - let iconClicked = event.target.closest('.MuiTreeItem-iconContainer'); + const iconClicked = event.target.closest('.MuiTreeItem-iconContainer'); if (iconClicked) { setExpandedNodes(nodeIds); } }; - const handleSelectNode = (event: SyntheticEvent, nodeId: string) => { - selectNode(nodeId); - }; - const selectNode = (nodeId: string) => { if (selectedNode !== nodeId) { setSelectedNode(nodeId); @@ -133,6 +130,10 @@ export default function ReportViewer({ } }; + const handleSelectNode = (event: SyntheticEvent, nodeId: string) => { + selectNode(nodeId); + }; + // The MUI TreeView/TreeItems use useMemo on our items, so it's important to avoid changing the context const isHighlighted = useMemo( () => ({ @@ -144,11 +145,10 @@ export default function ReportViewer({ const onRowClick = (data: LogReportItem) => { setExpandedNodes((previouslyExpandedNodes) => { - let nodesToExpand = []; - let reportId = data.reportId; + const nodesToExpand = []; + let { reportId } = data; while (allReports.current[reportId]?.parentReportId) { - let parentReportId = - allReports.current[reportId].parentReportId; + const { parentReportId } = allReports.current[reportId]; if (parentReportId !== undefined) { if (!previouslyExpandedNodes.includes(parentReportId)) { nodesToExpand.push(parentReportId); @@ -158,9 +158,8 @@ export default function ReportViewer({ } if (nodesToExpand.length > 0) { return nodesToExpand.concat(previouslyExpandedNodes); - } else { - return previouslyExpandedNodes; } + return previouslyExpandedNodes; }); setHighlightedReportId(data.reportId); }; @@ -177,7 +176,7 @@ export default function ReportViewer({ borderRight: '1px solid rgba(81, 81, 81, 1)', }} > - {/*Passing a ref to isHighlighted to all children (here + {/* Passing a ref to isHighlighted to all children (here TreeItems) wouldn't work since TreeView children are memoized and would then be rerendered only when TreeView is rerendered. That's why we pass the isHighlighted callback in @@ -185,7 +184,7 @@ export default function ReportViewer({ as the context is modified, children will be rerendered accordingly */} - {/*TODO do we need to useMemo/useCallback these props to avoid rerenders ?*/} + {/* TODO do we need to useMemo/useCallback these props to avoid rerenders ? */} } @@ -212,3 +211,7 @@ export default function ReportViewer({ ) ); } + +ReportViewer.defaultProps = { + maxSubReports: MAX_SUB_REPORTS, +}; diff --git a/src/components/ReportViewerDialog/index.ts b/src/components/ReportViewerDialog/index.ts index 710e7ccb..c2efc106 100644 --- a/src/components/ReportViewerDialog/index.ts +++ b/src/components/ReportViewerDialog/index.ts @@ -4,4 +4,5 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default } from './report-viewer-dialog'; +// eslint-disable-next-line import/prefer-default-export +export { default as ReportViewerDialog } from './report-viewer-dialog'; diff --git a/src/components/ReportViewerDialog/report-viewer-dialog.tsx b/src/components/ReportViewerDialog/report-viewer-dialog.tsx index a5e3d3c8..7aee2daf 100644 --- a/src/components/ReportViewerDialog/report-viewer-dialog.tsx +++ b/src/components/ReportViewerDialog/report-viewer-dialog.tsx @@ -18,7 +18,7 @@ import { FullscreenExit as FullscreenExitIcon, } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; -import ReportViewer from '../ReportViewer'; +import { ReportViewer } from '../ReportViewer'; import { Report } from '../ReportViewer/report.type'; const styles = { @@ -37,7 +37,9 @@ export interface ReportViewerDialogProps { jsonReport: Report; } -export default function ReportViewerDialog(props: ReportViewerDialogProps) { +export default function ReportViewerDialog( + props: Readonly +) { const { title, open, onClose, jsonReport } = props; const [fullScreen, setFullScreen] = useState(false); @@ -56,14 +58,14 @@ export default function ReportViewerDialog(props: ReportViewerDialogProps) { onClose={onClose} fullScreen={fullScreen} aria-labelledby="dialog-title-report" - fullWidth={true} + fullWidth maxWidth="lg" sx={{ '& .MuiDialog-paperFullWidth': styles.paperFullWidth, }} > {title} - + diff --git a/src/components/SignInCallbackHandler/SignInCallbackHandler.tsx b/src/components/SignInCallbackHandler/SignInCallbackHandler.tsx index 95bd4d1a..9ab178bc 100644 --- a/src/components/SignInCallbackHandler/SignInCallbackHandler.tsx +++ b/src/components/SignInCallbackHandler/SignInCallbackHandler.tsx @@ -13,10 +13,10 @@ export interface SignInCallbackHandlerProps { handleSignInCallback: () => void; } -const SignInCallbackHandler = ({ +function SignInCallbackHandler({ userManager, handleSignInCallback, -}: SignInCallbackHandlerProps) => { +}: Readonly) { useEffect(() => { if (userManager !== null) { handleSignInCallback(); @@ -24,5 +24,5 @@ const SignInCallbackHandler = ({ }, [userManager, handleSignInCallback]); return

; -}; +} export default SignInCallbackHandler; diff --git a/src/components/SignInCallbackHandler/index.ts b/src/components/SignInCallbackHandler/index.ts index be81ccba..c34dd93e 100644 --- a/src/components/SignInCallbackHandler/index.ts +++ b/src/components/SignInCallbackHandler/index.ts @@ -4,4 +4,5 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default } from './SignInCallbackHandler'; +// eslint-disable-next-line import/prefer-default-export +export { default as SignInCallbackHandler } from './SignInCallbackHandler'; diff --git a/src/components/SilentRenewCallbackHandler/SilentRenewCallbackHandler.tsx b/src/components/SilentRenewCallbackHandler/SilentRenewCallbackHandler.tsx index b08aaf0a..7fe93b41 100644 --- a/src/components/SilentRenewCallbackHandler/SilentRenewCallbackHandler.tsx +++ b/src/components/SilentRenewCallbackHandler/SilentRenewCallbackHandler.tsx @@ -13,10 +13,10 @@ export interface SilentRenewCallbackHandlerProps { handleSilentRenewCallback: () => void; } -const SilentRenewCallbackHandler = ({ +function SilentRenewCallbackHandler({ userManager, handleSilentRenewCallback, -}: SilentRenewCallbackHandlerProps) => { +}: Readonly) { useEffect(() => { if (userManager !== null) { handleSilentRenewCallback(); @@ -24,6 +24,6 @@ const SilentRenewCallbackHandler = ({ }, [userManager, handleSilentRenewCallback]); return

Technical token renew window, you should not see this

; -}; +} export default SilentRenewCallbackHandler; diff --git a/src/components/SilentRenewCallbackHandler/index.ts b/src/components/SilentRenewCallbackHandler/index.ts index c734a65a..e6fad105 100644 --- a/src/components/SilentRenewCallbackHandler/index.ts +++ b/src/components/SilentRenewCallbackHandler/index.ts @@ -4,4 +4,5 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default } from './SilentRenewCallbackHandler'; +// eslint-disable-next-line import/prefer-default-export +export { default as SilentRenewCallbackHandler } from './SilentRenewCallbackHandler'; diff --git a/src/components/SnackbarProvider/SnackbarProvider.tsx b/src/components/SnackbarProvider/SnackbarProvider.tsx index b4b27643..ee6b7f2c 100644 --- a/src/components/SnackbarProvider/SnackbarProvider.tsx +++ b/src/components/SnackbarProvider/SnackbarProvider.tsx @@ -10,29 +10,32 @@ import { Button } from '@mui/material'; import { SnackbarProvider as OrigSnackbarProvider, + SnackbarKey, SnackbarProviderProps, } from 'notistack'; /* A wrapper around notistack's SnackbarProvider that provides defaults props */ -const SnackbarProvider = (props: SnackbarProviderProps) => { +function SnackbarProvider(props: Readonly) { const ref = useRef(null); + const action = (key: SnackbarKey) => ( + + ); + return ( ( - - )} + hideIconVariant + action={action} {...props} /> ); -}; +} export default SnackbarProvider; diff --git a/src/components/SnackbarProvider/index.ts b/src/components/SnackbarProvider/index.ts index a9238518..384d936d 100644 --- a/src/components/SnackbarProvider/index.ts +++ b/src/components/SnackbarProvider/index.ts @@ -4,4 +4,5 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default } from './SnackbarProvider'; +// eslint-disable-next-line import/prefer-default-export +export { default as SnackbarProvider } from './SnackbarProvider'; diff --git a/src/components/TopBar/AboutDialog.tsx b/src/components/TopBar/AboutDialog.tsx index 06e08323..ec4fabd8 100644 --- a/src/components/TopBar/AboutDialog.tsx +++ b/src/components/TopBar/AboutDialog.tsx @@ -46,7 +46,7 @@ import { LogoText } from './GridLogo'; const styles = { general: { '.MuiAccordion-root': { - //dunno why the theme has the background as black in dark mode + // dunno why the theme has the background as black in dark mode bgcolor: 'unset', }, }, @@ -84,7 +84,7 @@ function getGlobalVersion( ) { if (fnPromise) { console.debug('Getting', type, 'global version...'); - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (setLoader) { setLoader(true); } @@ -110,10 +110,10 @@ function getGlobalVersion( setLoader(false); } }); - } else { - console.debug('No getter for global version'); - setData(null); } + console.debug('No getter for global version'); + setData(null); + return Promise.resolve(null); } const moduleTypeSort = { @@ -127,7 +127,7 @@ type ModuleType = keyof typeof moduleTypeSort; type ModuleDefinition = { name: string; type: ModuleType }; function compareModules(c1: ModuleDefinition, c2: ModuleDefinition) { - //sort by type then by name + // sort by type then by name return ( (moduleTypeSort[c1.type] || 100) - (moduleTypeSort[c2.type] || 100) || (c1.name || '').localeCompare(c2.name || '') @@ -153,7 +153,155 @@ export interface AboutDialogProps { additionalModulesPromise?: () => Promise; } -const AboutDialog = ({ +const moduleStyles = { + icons: { + flexGrow: 0, + position: 'relative', + top: '4px', + flexShrink: 0, + }, + version: { + flexGrow: 0, + alignSelf: 'flex-end', + flexShrink: 0, + }, + tooltip: (theme: Theme) => ({ + [`& .${tooltipClasses.tooltip}`]: { + border: '1px solid #dadde9', + boxShadow: theme.shadows[1], + }, + }), + tooltipDetails: { + display: 'grid', + gridTemplateColumns: 'max-content auto', + margin: 0, + dt: { + gridColumnStart: 1, + '&:after': { + content: '" :"', + }, + }, + dd: { + gridColumnStart: 2, + paddingLeft: '0.5em', + }, + }, +}; + +const ModuleTypesIcons = { + app: ( + + ), + server: ( + + ), + other: , +}; + +function insensitiveCaseCompare(str: string, obj: string) { + return str.localeCompare(obj, undefined, { + sensitivity: 'base', + }); +} +function tooltipTypeLabel(type: string) { + if (insensitiveCaseCompare('app', type) === 0) { + return 'about-dialog/module-tooltip-app'; + } + if (insensitiveCaseCompare('server', type) === 0) { + return 'about-dialog/module-tooltip-server'; + } + return 'about-dialog/module-tooltip-other'; +} + +function Module({ type, name, version, gitTag }: Readonly) { + return ( + + + {name || ''} + + + + + + + + {version && ( + <> + + + + + {version} + + + )} + {gitTag && ( + <> + + + + + {gitTag} + + + )} + + + } + > + + {ModuleTypesIcons[type] || ModuleTypesIcons.other} + + {name || ''} + + theme.palette.text.secondary} + display="inline" + noWrap + sx={moduleStyles.version} + > + {gitTag ?? version ?? null} + + + + + ); +} + +function AboutDialog({ open, onClose, globalVersionPromise, @@ -162,9 +310,9 @@ const AboutDialog = ({ appGitTag, appLicense, additionalModulesPromise, -}: AboutDialogProps) => { +}: Readonly) { const theme = useTheme(); - const [isRefreshing, setRefreshState] = useState(false); + const [isRefreshing, setIsRefreshing] = useState(false); const [loadingGlobalVersion, setLoadingGlobalVersion] = useState(false); const [showGlobalVersion, setShowGlobalVersion] = useState(false); @@ -176,8 +324,7 @@ const AboutDialog = ({ getGlobalVersion( globalVersionPromise, 'Initial', - setInitialGlobalVersion, - undefined + setInitialGlobalVersion ); } }, [globalVersionPromise, initialGlobalVersion]); @@ -219,7 +366,7 @@ const AboutDialog = ({ ) .then( (values) => (Array.isArray(values) ? values : []), - (reason) => [] + () => [] ) .then((values) => { setModules([currentApp, ...values]); @@ -245,21 +392,21 @@ const AboutDialog = ({ { + onExited: () => { setModules(null); setActualGlobalVersion(null); }, }} > - + {/* we insert content in the title as a trick to have the main content not in the dialog's scrollable section */} {initialGlobalVersion !== undefined && @@ -278,7 +425,7 @@ const AboutDialog = ({ loadingPosition="start" loading={isRefreshing} onClick={() => { - setRefreshState(true); + setIsRefreshing(true); window.location.reload(); }} > @@ -303,7 +450,7 @@ const AboutDialog = ({ in={loadingGlobalVersion} appear unmountOnExit - onExited={(node) => setShowGlobalVersion(true)} + onExited={() => setShowGlobalVersion(true)} > @@ -388,9 +535,9 @@ const AboutDialog = ({ <> {[...modules] .sort(compareModules) - .map((module, idx) => ( + .map((module) => ( )) || ( - theme.palette.error.main + color={(selectedTheme) => + selectedTheme.palette.error.main } > Error @@ -421,154 +568,6 @@ const AboutDialog = ({
); -}; - -export default AboutDialog; - -const moduleStyles = { - icons: { - flexGrow: 0, - position: 'relative', - top: '4px', - flexShrink: 0, - }, - version: { - flexGrow: 0, - alignSelf: 'flex-end', - flexShrink: 0, - }, - tooltip: (theme: Theme) => ({ - [`& .${tooltipClasses.tooltip}`]: { - border: '1px solid #dadde9', - boxShadow: theme.shadows[1], - }, - }), - tooltipDetails: { - display: 'grid', - gridTemplateColumns: 'max-content auto', - margin: 0, - dt: { - gridColumnStart: 1, - '&:after': { - content: '" :"', - }, - }, - dd: { - gridColumnStart: 2, - paddingLeft: '0.5em', - }, - }, -}; - -const ModuleTypesIcons = { - app: ( - - ), - server: ( - - ), - other: , -}; - -function insensitiveCaseCompare(str: string, obj: string) { - return str.localeCompare(obj, undefined, { - sensitivity: 'base', - }); -} -function tooltipTypeLabel(type: string) { - if (insensitiveCaseCompare('app', type) === 0) { - return 'about-dialog/module-tooltip-app'; - } else if (insensitiveCaseCompare('server', type) === 0) { - return 'about-dialog/module-tooltip-server'; - } else { - return 'about-dialog/module-tooltip-other'; - } } -const Module = ({ type, name, version, gitTag }: GridSuiteModule) => { - return ( - - - {name || ''} - - - - - - - - {version && ( - <> - - - - - {version} - - - )} - {gitTag && ( - <> - - - - - {gitTag} - - - )} - - - } - > - - {ModuleTypesIcons[type] || ModuleTypesIcons['other']} - - {name || ''} - - theme.palette.text.secondary} - display="inline" - noWrap - sx={moduleStyles.version} - > - {gitTag || version || null} - - - - - ); -}; +export default AboutDialog; diff --git a/src/components/TopBar/GridLogo.tsx b/src/components/TopBar/GridLogo.tsx index 66cbc931..a4514c74 100644 --- a/src/components/TopBar/GridLogo.tsx +++ b/src/components/TopBar/GridLogo.tsx @@ -7,8 +7,8 @@ import { Box, SxProps, Typography } from '@mui/material'; import { BrokenImage } from '@mui/icons-material'; -import { mergeSx } from '../../utils/styles'; import { ReactNode } from 'react'; +import { mergeSx } from '../../utils/styles'; const styles = { logo: { @@ -30,12 +30,30 @@ export interface GridLogoProps extends Omit { appLogo: ReactNode; } -const GridLogo = ({ +export function LogoText({ + appName, + appColor, + style, + onClick, +}: Readonly>) { + return ( + + Grid + {appName} + + ); +} + +function GridLogo({ appLogo, appName, appColor, onClick, -}: Partial) => { +}: Readonly>) { return ( <> ); -}; +} export default GridLogo; @@ -62,21 +80,3 @@ export interface LogoTextProps { style: SxProps; onClick: () => void; } - -export const LogoText = ({ - appName, - appColor, - style, - onClick, -}: Partial) => { - return ( - - Grid - {appName} - - ); -}; diff --git a/src/components/TopBar/TopBar.test.tsx b/src/components/TopBar/TopBar.test.tsx index 281ab7aa..9596aec2 100644 --- a/src/components/TopBar/TopBar.test.tsx +++ b/src/components/TopBar/TopBar.test.tsx @@ -10,14 +10,14 @@ import { createRoot } from 'react-dom/client'; import { act } from 'react-dom/test-utils'; import { IntlProvider } from 'react-intl'; -import TopBar, { LANG_ENGLISH } from './TopBar'; -import { top_bar_en } from '../../'; - -import PowsyblLogo from '../images/powsybl_logo.svg?react'; - import { red } from '@mui/material/colors'; import { createTheme, ThemeProvider } from '@mui/material'; +// eslint-disable-next-line import/no-extraneous-dependencies import { beforeEach, afterEach, it, expect } from '@jest/globals'; +import TopBar, { LANG_ENGLISH } from './TopBar'; +import { top_bar_en } from '../..'; + +import PowsyblLogo from '../images/powsybl_logo.svg?react'; let container: Element; diff --git a/src/components/TopBar/TopBar.tsx b/src/components/TopBar/TopBar.tsx index 7b31c1ed..8cbe7ee2 100644 --- a/src/components/TopBar/TopBar.tsx +++ b/src/components/TopBar/TopBar.tsx @@ -41,13 +41,13 @@ import { Settings as SettingsIcon, WbSunny as WbSunnyIcon, } from '@mui/icons-material'; +// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; -import PropTypes from 'prop-types'; +import { User } from 'oidc-client'; import GridLogo, { GridLogoProps } from './GridLogo'; import AboutDialog, { AboutDialogProps } from './AboutDialog'; import { LogoutProps } from '../Login/Logout'; -import { User } from 'oidc-client'; import { CommonMetadata } from '../../services'; const styles = { @@ -176,7 +176,7 @@ export type TopBarProps = Omit & language: GsLang; }; -const TopBar = ({ +function TopBar({ appName, appColor, appLogo, @@ -197,7 +197,7 @@ const TopBar = ({ equipmentLabelling, onLanguageClick, language, -}: PropsWithChildren) => { +}: PropsWithChildren) { const [anchorElSettingsMenu, setAnchorElSettingsMenu] = useState(null); const [anchorElAppsMenu, setAnchorElAppsMenu] = useState( @@ -231,9 +231,8 @@ const TopBar = ({ const tab = name.split(' ').map((x) => x.charAt(0)); if (tab.length === 1) { return tab[0]; - } else { - return tab[0] + tab[tab.length - 1]; } + return tab[0] + tab[tab.length - 1]; }; const changeTheme = (_: MouseEvent, value: GsTheme) => { @@ -265,7 +264,7 @@ const TopBar = ({ } }; - const logo_clickable = useMemo( + const logoClickable = useMemo( () => ( - {logo_clickable} + {logoClickable} {children} {user && ( @@ -354,7 +353,7 @@ const TopBar = ({ onClick={handleToggleSettingsMenu} color="inherit" style={ - Boolean(anchorElSettingsMenu) + anchorElSettingsMenu ? { cursor: 'initial' } : { cursor: 'pointer' } } @@ -362,7 +361,7 @@ const TopBar = ({ {user !== null ? abbreviationFromUserName( - //@ts-ignore name could be undefined, how to handle this case ? + // @ts-ignore name could be undefined, how to handle this case ? user.profile.name ) : ''} @@ -388,7 +387,7 @@ const TopBar = ({ {/* user info */} @@ -415,7 +414,7 @@ const TopBar = ({ {/* Display mode */} @@ -459,11 +456,11 @@ const TopBar = ({ - {/*/!* Equipment labelling *!/*/} - {/*If the callback onEquipmentLabellingClick is undefined, equipment labelling component should not be displayed*/} + {/* /!* Equipment labelling *!/ */} + {/* If the callback onEquipmentLabellingClick is undefined, equipment labelling component should not be displayed */} {onEquipmentLabellingClick && ( @@ -501,28 +496,24 @@ const TopBar = ({ > )} - {/*Languages */} + {/* Languages */} @@ -577,7 +566,7 @@ const TopBar = ({ {/* Settings */} - {/*If the callback onParametersClicked is undefined, parameters component should be disabled*/} + {/* If the callback onParametersClicked is undefined, parameters component should be disabled */} {onParametersClick && ( @@ -602,7 +589,7 @@ const TopBar = ({ )} {/* About */} - {/*If the callback onAboutClick is undefined, we open default about dialog*/} + {/* If the callback onAboutClick is undefined, we open default about dialog */} @@ -634,9 +621,7 @@ const TopBar = ({ > @@ -659,29 +644,15 @@ const TopBar = ({ ); -}; - -TopBar.propTypes = { - onParametersClick: PropTypes.func, - onLogoutClick: PropTypes.func, - onLogoClick: PropTypes.func, - appName: PropTypes.string, - appColor: PropTypes.string, - appLogo: PropTypes.object, - appVersion: PropTypes.string, - appLicense: PropTypes.string, - user: PropTypes.object, - children: PropTypes.node, - appsAndUrls: PropTypes.array, - onThemeClick: PropTypes.func, - theme: PropTypes.string, - onAboutClick: PropTypes.func, - globalVersionPromise: PropTypes.func, - additionalModulesPromise: PropTypes.func, - onEquipmentLabellingClick: PropTypes.func, - equipmentLabelling: PropTypes.bool, - onLanguageClick: PropTypes.func.isRequired, - language: PropTypes.string.isRequired, +} + +TopBar.defaultProps = { + onParametersClick: undefined, + onAboutClick: undefined, + onThemeClick: undefined, + theme: undefined, + onEquipmentLabellingClick: undefined, + equipmentLabelling: undefined, }; export default TopBar; diff --git a/src/components/TopBar/index.ts b/src/components/TopBar/index.ts index 5c31ffda..7ecab4ac 100644 --- a/src/components/TopBar/index.ts +++ b/src/components/TopBar/index.ts @@ -4,6 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default } from './TopBar'; +export { default as TopBar } from './TopBar'; export * from './GridLogo'; export { default as AboutDialog } from './AboutDialog'; diff --git a/src/components/TreeViewFinder/TreeViewFinder.tsx b/src/components/TreeViewFinder/TreeViewFinder.tsx index 65229dca..a426aac4 100644 --- a/src/components/TreeViewFinder/TreeViewFinder.tsx +++ b/src/components/TreeViewFinder/TreeViewFinder.tsx @@ -13,11 +13,8 @@ import React, { useState, } from 'react'; import { useIntl } from 'react-intl'; -import { - makeComposeClasses, - toNestedGlobalSelectors, -} from '../../utils/styles'; +// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { @@ -32,12 +29,17 @@ import { ModalProps, } from '@mui/material'; +// eslint-disable-next-line import/no-extraneous-dependencies import { TreeItem, TreeView, TreeViewClasses } from '@mui/x-tree-view'; import { Check as CheckIcon, ChevronRight as ChevronRightIcon, ExpandMore as ExpandMoreIcon, } from '@mui/icons-material'; +import { + makeComposeClasses, + toNestedGlobalSelectors, +} from '../../utils/styles'; import CancelButton from '../inputs/react-hook-form/utils/cancel-button'; // As a bunch of individual variables to try to make it easier @@ -89,7 +91,7 @@ interface TreeViewFinderNodeMapProps { } export interface TreeViewFinderProps { - //TreeView Props + // TreeView Props defaultExpanded?: string[]; defaultSelected?: string[]; selected?: string[]; @@ -144,7 +146,7 @@ export interface TreeViewFinderProps { * @param {Object} [selected] - ids of selected items * @param {Array} [expanded] - ids of the expanded items */ -const TreeViewFinder = (props: TreeViewFinderProps) => { +function TreeViewFinder(props: Readonly) { const intl = useIntl(); const { classes = {}, @@ -182,14 +184,14 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { const [autoScrollAllowed, setAutoScrollAllowed] = useState(true); /* Utilities */ - const isSelectable = (node: TreeViewFinderNodeProps) => { - return onlyLeaves ? isLeaf(node) : true; // otherwise everything is selectable - }; - const isLeaf = (node: TreeViewFinderNodeProps) => { return node && node.children === undefined; }; + const isSelectable = (node: TreeViewFinderNodeProps) => { + return onlyLeaves ? isLeaf(node) : true; // otherwise everything is selectable + }; + const isValidationDisabled = () => { return ( selected?.length === 0 || @@ -200,7 +202,7 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { const computeMapPrintedNodes = useCallback( (nodes: TreeViewFinderNodeProps[] | undefined) => { - let newMapPrintedNodes: TreeViewFinderNodeMapProps = {}; + const newMapPrintedNodes: TreeViewFinderNodeMapProps = {}; nodes?.forEach((node) => { newMapPrintedNodes[node.id] = node; if (!isLeaf(node)) { @@ -219,7 +221,7 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { useEffect(() => { // compute all mapPrintedNodes here from data prop // if data changes in current expanded nodes - let newMapPrintedNodes = computeMapPrintedNodes(data); + const newMapPrintedNodes = computeMapPrintedNodes(data); setMapPrintedNodes(newMapPrintedNodes); }, [data, computeMapPrintedNodes]); @@ -237,7 +239,7 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { nodeIds.every((nodeId) => { if (!expanded?.includes(nodeId)) { // proc onTreeBrowse here - onTreeBrowse && onTreeBrowse(nodeId); + onTreeBrowse?.(nodeId); return false; // break loop to call onTreeBrowse only once } return true; @@ -253,7 +255,7 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { } if (selectedProp.length > 0) { setSelected((oldSelectedNodes) => [ - ...(oldSelectedNodes ? oldSelectedNodes : []), + ...(oldSelectedNodes || []), ...selectedProp, ]); } @@ -265,7 +267,7 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { } if (expandedProp.length > 0) { setExpanded((oldExpandedNodes) => [ - ...(oldExpandedNodes ? oldExpandedNodes : []), + ...(oldExpandedNodes || []), ...expandedProp, ]); } @@ -306,16 +308,14 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { setSelected( values.filter((nodeId) => isSelectable(mapPrintedNodes[nodeId])) ); - } else { - if (!Array.isArray(values)) { - // Toggle selection to allow unselection - if (selected?.includes(values)) { - setSelected([]); - } else { - setSelected( - isSelectable(mapPrintedNodes[values]) ? [values] : [] - ); - } + } else if (!Array.isArray(values)) { + // Toggle selection to allow unselection + if (selected?.includes(values)) { + setSelected([]); + } else { + setSelected( + isSelectable(mapPrintedNodes[values]) ? [values] : [] + ); } } }; @@ -324,26 +324,25 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { const getValidationButtonText = () => { if (validationButtonText) { return validationButtonText; - } else { - let buttonLabelId = ''; - if (Array.isArray(selectedProp)) { - buttonLabelId = - selectedProp?.length > 0 - ? 'treeview_finder/replaceElementsValidation' - : 'treeview_finder/addElementsValidation'; - } else { - buttonLabelId = selectedProp + } + let buttonLabelId = ''; + if (Array.isArray(selectedProp)) { + buttonLabelId = + selectedProp?.length > 0 ? 'treeview_finder/replaceElementsValidation' : 'treeview_finder/addElementsValidation'; - } - - return intl.formatMessage( - { id: buttonLabelId }, - { - nbElements: selected?.length, - } - ); + } else { + buttonLabelId = selectedProp + ? 'treeview_finder/replaceElementsValidation' + : 'treeview_finder/addElementsValidation'; } + + return intl.formatMessage( + { id: buttonLabelId }, + { + nbElements: selected?.length, + } + ); }; const getNodeIcon = (node: TreeViewFinderNodeProps) => { @@ -358,17 +357,15 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { return ( ); - } else { - if (node.icon) { - return ( -
- {node.icon} -
- ); - } else { - return null; - } } + if (node.icon) { + return ( +
+ {node.icon} +
+ ); + } + return null; }; const renderTreeItemLabel = (node: TreeViewFinderNodeProps) => { @@ -391,7 +388,19 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { const renderTree = (node: TreeViewFinderNodeProps) => { if (!node) { - return; + return null; + } + let childrenNodes = null; + + if (Array.isArray(node.children)) { + if (node.children.length) { + const sortedChildren = node.children.sort(sortMethod); + childrenNodes = sortedChildren.map((child) => + renderTree(child) + ); + } else { + childrenNodes = [false]; // Pass non empty Array here to simulate a child then this node isn't considered as a leaf. + } } return ( { } }} > - {Array.isArray(node.children) - ? node.children.length - ? node.children - .sort(sortMethod) - .map((child) => renderTree(child)) - : [false] // Pass non empty Array here to simulate a child then this node isn't considered as a leaf. - : null} + {childrenNodes} ); }; @@ -447,7 +450,7 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { open={open} onClose={(e, r) => { if (r === 'escapeKeyDown' || r === 'backdropClick') { - onClose && onClose([]); + onClose?.([]); setSelected([]); } }} @@ -458,21 +461,19 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { }} > - {title - ? title - : intl.formatMessage( - { id: 'treeview_finder/finderTitle' }, - { multiSelect: multiSelect } - )} + {title || + intl.formatMessage( + { id: 'treeview_finder/finderTitle' }, + { multiSelect } + )} - {contentText - ? contentText - : intl.formatMessage( - { id: 'treeview_finder/contentText' }, - { multiSelect: multiSelect } - )} + {contentText || + intl.formatMessage( + { id: 'treeview_finder/contentText' }, + { multiSelect } + )} { { - onClose && onClose([]); + onClose?.([]); setSelected([]); setAutoScrollAllowed(true); }} @@ -505,7 +506,7 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { variant="outlined" style={{ float: 'left', margin: '5px' }} onClick={() => { - onClose && onClose(computeSelectedNodes()); + onClose?.(computeSelectedNodes()); setSelected([]); setAutoScrollAllowed(true); }} @@ -516,7 +517,7 @@ const TreeViewFinder = (props: TreeViewFinderProps) => { ); -}; +} const nestedGlobalSelectorsStyles = toNestedGlobalSelectors( defaultStyles, diff --git a/src/components/TreeViewFinder/index.ts b/src/components/TreeViewFinder/index.ts index af58aeb7..1d116af5 100644 --- a/src/components/TreeViewFinder/index.ts +++ b/src/components/TreeViewFinder/index.ts @@ -4,7 +4,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default, generateTreeViewFinderClass } from './TreeViewFinder'; +export { + default as TreeViewFinder, + generateTreeViewFinderClass, +} from './TreeViewFinder'; export type { TreeViewFinderProps, TreeViewFinderNodeProps, diff --git a/src/components/dialogs/custom-mui-dialog.tsx b/src/components/dialogs/custom-mui-dialog.tsx index 3a231a40..2363a493 100644 --- a/src/components/dialogs/custom-mui-dialog.tsx +++ b/src/components/dialogs/custom-mui-dialog.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { FunctionComponent } from 'react'; +import React from 'react'; import { FieldErrors, UseFormReturn } from 'react-hook-form'; import { FormattedMessage } from 'react-intl'; import { @@ -16,12 +16,12 @@ import { Grid, LinearProgress, } from '@mui/material'; +import * as yup from 'yup'; import SubmitButton from '../inputs/react-hook-form/utils/submit-button'; import CancelButton from '../inputs/react-hook-form/utils/cancel-button'; import CustomFormProvider, { MergedFormContextProps, } from '../inputs/react-hook-form/provider/custom-form-provider'; -import * as yup from 'yup'; interface ICustomMuiDialog { open: boolean; @@ -49,7 +49,7 @@ const styles = { }, }; -const CustomMuiDialog: FunctionComponent = ({ +function CustomMuiDialog({ open, formSchema, formMethods, @@ -63,11 +63,11 @@ const CustomMuiDialog: FunctionComponent = ({ onCancel, children, language, -}) => { +}: Readonly) { const { handleSubmit } = formMethods; const handleCancel = (event: React.MouseEvent) => { - onCancel && onCancel(); + onCancel?.(); onClose(event); }; @@ -84,7 +84,7 @@ const CustomMuiDialog: FunctionComponent = ({ }; const handleValidationError = (errors: FieldErrors) => { - onValidationError && onValidationError(errors); + onValidationError?.(errors); }; return ( @@ -121,6 +121,6 @@ const CustomMuiDialog: FunctionComponent = ({ ); -}; +} export default CustomMuiDialog; diff --git a/src/components/dialogs/description-modification-dialog.tsx b/src/components/dialogs/description-modification-dialog.tsx index 03916d2b..e709c31d 100644 --- a/src/components/dialogs/description-modification-dialog.tsx +++ b/src/components/dialogs/description-modification-dialog.tsx @@ -5,15 +5,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback } from 'react'; -import yup from '../../utils/yup-config'; -import { FieldConstants } from '../../utils/field-constants'; +import { useCallback } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; +import { Box } from '@mui/material'; +import yup from '../../utils/yup-config'; +import FieldConstants from '../../utils/field-constants'; import { useSnackMessage } from '../../hooks/useSnackMessage'; import CustomMuiDialog from './custom-mui-dialog'; import ExpandingTextField from '../inputs/react-hook-form/ExpandingTextField'; -import { Box } from '@mui/material'; export interface IDescriptionModificationDialog { elementUuid: string; @@ -32,9 +32,13 @@ const schema = yup.object().shape({ .max(500, 'descriptionLimitError'), }); -const DescriptionModificationDialog: FunctionComponent< - IDescriptionModificationDialog -> = ({ elementUuid, description, open, onClose, updateElement }) => { +function DescriptionModificationDialog({ + elementUuid, + description, + open, + onClose, + updateElement, +}: Readonly) { const { snackError } = useSnackMessage(); const emptyFormData = { @@ -77,19 +81,19 @@ const DescriptionModificationDialog: FunctionComponent< onSave={onSubmit} formSchema={schema} formMethods={methods} - titleId={'description'} - removeOptional={true} + titleId="description" + removeOptional > + /> {' '} ); -}; +} export default DescriptionModificationDialog; diff --git a/src/components/dialogs/modify-element-selection.tsx b/src/components/dialogs/modify-element-selection.tsx index 093e1ef0..ddf02b4c 100644 --- a/src/components/dialogs/modify-element-selection.tsx +++ b/src/components/dialogs/modify-element-selection.tsx @@ -11,7 +11,7 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { useController } from 'react-hook-form'; import { UUID } from 'crypto'; import { TreeViewFinderNodeProps } from '../TreeViewFinder'; -import { FieldConstants } from '../../utils/field-constants'; +import FieldConstants from '../../utils/field-constants'; import DirectoryItemSelector from '../DirectoryItemSelector/directory-item-selector'; import { ElementType } from '../../utils/ElementType'; import { fetchDirectoryElementPath } from '../../services'; @@ -25,11 +25,16 @@ export interface ModifyElementSelectionProps { onElementValidated?: (elementId: UUID) => void; } -const ModifyElementSelection: React.FunctionComponent< - ModifyElementSelectionProps -> = (props) => { +function ModifyElementSelection(props: Readonly) { const intl = useIntl(); - + const { + elementType, + dialogTitleLabel, + dialogMessageLabel, + dialogOpeningButtonLabel, + noElementMessageLabel, + onElementValidated, + } = props; const [open, setOpen] = useState(false); const [activeDirectoryName, setActiveDirectoryName] = useState(''); @@ -59,8 +64,8 @@ const ModifyElementSelection: React.FunctionComponent< const handleClose = (nodes: TreeViewFinderNodeProps[]) => { if (nodes.length) { onChange(nodes[0].id); - if (props.onElementValidated) { - props.onElementValidated(nodes[0].id as UUID); + if (onElementValidated) { + onElementValidated(nodes[0].id as UUID); } } setOpen(false); @@ -83,7 +88,7 @@ const ModifyElementSelection: React.FunctionComponent< color="primary" component="label" > - + - {activeDirectoryName - ? activeDirectoryName - : props?.noElementMessageLabel - ? intl.formatMessage({ - id: props.noElementMessageLabel, - }) - : ''} + {activeDirectoryName || + (props?.noElementMessageLabel + ? intl.formatMessage({ + id: noElementMessageLabel, + }) + : '')}
); -}; +} export default ModifyElementSelection; diff --git a/src/components/dialogs/popup-confirmation-dialog.tsx b/src/components/dialogs/popup-confirmation-dialog.tsx index 463c098b..79ccab07 100644 --- a/src/components/dialogs/popup-confirmation-dialog.tsx +++ b/src/components/dialogs/popup-confirmation-dialog.tsx @@ -13,7 +13,6 @@ import DialogActions from '@mui/material/DialogActions'; import Button from '@mui/material/Button'; import { FormattedMessage } from 'react-intl'; import CancelButton from '../inputs/react-hook-form/utils/cancel-button'; -import { FunctionComponent } from 'react'; export interface PopupConfirmationDialogProps { message: string; @@ -23,22 +22,20 @@ export interface PopupConfirmationDialogProps { handlePopupConfirmation: () => void; } -const PopupConfirmationDialog: FunctionComponent< - PopupConfirmationDialogProps -> = ({ +function PopupConfirmationDialog({ message, validateButtonLabel, openConfirmationPopup, setOpenConfirmationPopup, handlePopupConfirmation, -}: PopupConfirmationDialogProps) => { +}: Readonly) { return ( - - {'Confirmation'} + + Confirmation @@ -53,7 +50,7 @@ const PopupConfirmationDialog: FunctionComponent< ); -}; +} PopupConfirmationDialog.defaultProps = { validateButtonLabel: undefined, diff --git a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx index 33ec2393..598d4701 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx +++ b/src/components/filter/criteria-based/criteria-based-filter-edition-dialog.tsx @@ -7,29 +7,28 @@ import { Dispatch, - FunctionComponent, SetStateAction, useCallback, useEffect, useState, } from 'react'; -import { FieldConstants } from '../../../utils/field-constants'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { UUID } from 'crypto'; +import FieldConstants from '../../../utils/field-constants'; import { backToFrontTweak, frontToBackTweak, } from './criteria-based-filter-utils'; import CustomMuiDialog from '../../dialogs/custom-mui-dialog'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; -import { useForm } from 'react-hook-form'; -import { yupResolver } from '@hookform/resolvers/yup'; import { criteriaBasedFilterSchema } from './criteria-based-filter-form'; import yup from '../../../utils/yup-config'; -import { FilterForm } from '../filter-form'; -import { UUID } from 'crypto'; import { FilterType } from '../constants/filter-constants'; -import { FetchStatus } from '../../../utils/FetchStatus'; -import { ElementType } from '../../../utils/ElementType'; +import FetchStatus from '../../../utils/FetchStatus'; import { saveFilter } from '../../../services/explore'; +import { ElementExistsType } from '../../../utils/ElementType'; +import FilterForm from '../filter-form'; export type SelectionCopy = { sourceItemUuid: UUID | null; @@ -38,12 +37,6 @@ export type SelectionCopy = { parentDirectoryUuid: UUID | null; }; -export type elementExistsType = ( - directory: UUID, - value: string, - elementType: ElementType -) => Promise; - export const noSelectionForCopy: SelectionCopy = { sourceItemUuid: null, name: null, @@ -74,13 +67,11 @@ interface CriteriaBasedFilterEditionDialogProps { selection: SelectionCopy ) => Dispatch>; activeDirectory?: UUID; - elementExists?: elementExistsType; + elementExists?: ElementExistsType; language?: string; } -const CriteriaBasedFilterEditionDialog: FunctionComponent< - CriteriaBasedFilterEditionDialogProps -> = ({ +function CriteriaBasedFilterEditionDialog({ id, name, titleId, @@ -93,7 +84,7 @@ const CriteriaBasedFilterEditionDialog: FunctionComponent< activeDirectory, elementExists, language, -}) => { +}: Readonly) { const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); @@ -173,7 +164,7 @@ const CriteriaBasedFilterEditionDialog: FunctionComponent< formSchema={formSchema} formMethods={formMethods} titleId={titleId} - removeOptional={true} + removeOptional disabledSave={!!nameError || !!isValidating} isDataFetching={dataFetchStatus === FetchStatus.FETCHING} language={language} @@ -186,6 +177,6 @@ const CriteriaBasedFilterEditionDialog: FunctionComponent< )} ); -}; +} export default CriteriaBasedFilterEditionDialog; diff --git a/src/components/filter/criteria-based/criteria-based-filter-form.tsx b/src/components/filter/criteria-based/criteria-based-filter-form.tsx index bd15cb7a..ab9ca4fa 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-form.tsx +++ b/src/components/filter/criteria-based/criteria-based-filter-form.tsx @@ -4,20 +4,19 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import Grid from '@mui/material/Grid'; import FilterProperties, { filterPropertiesYupSchema, - FreePropertiesTypes, } from './filter-properties'; -import { FieldConstants } from '../../../utils/field-constants'; +import FieldConstants from '../../../utils/field-constants'; import yup from '../../../utils/yup-config'; import CriteriaBasedForm from './criteria-based-form'; -import Grid from '@mui/material/Grid'; -import { FunctionComponent } from 'react'; import { getCriteriaBasedFormData, getCriteriaBasedSchema, } from './criteria-based-filter-utils'; import { FILTER_EQUIPMENTS } from '../utils/filter-form-utils'; +import { FreePropertiesTypes } from './filter-free-properties'; export const criteriaBasedFilterSchema = getCriteriaBasedSchema({ [FieldConstants.ENERGY_SOURCE]: yup.string().nullable(), @@ -30,7 +29,7 @@ export const criteriaBasedFilterEmptyFormData = getCriteriaBasedFormData(null, { [FreePropertiesTypes.FREE_FILTER_PROPERTIES]: [], }); -const CriteriaBasedFilterForm: FunctionComponent = () => { +function CriteriaBasedFilterForm() { return ( { ); -}; +} export default CriteriaBasedFilterForm; diff --git a/src/components/filter/criteria-based/criteria-based-filter-utils.ts b/src/components/filter/criteria-based/criteria-based-filter-utils.ts index 7b64c0d9..a1e9a1f4 100644 --- a/src/components/filter/criteria-based/criteria-based-filter-utils.ts +++ b/src/components/filter/criteria-based/criteria-based-filter-utils.ts @@ -5,8 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FieldConstants } from '../../../utils/field-constants'; -import { FreePropertiesTypes } from './filter-properties'; +import FieldConstants from '../../../utils/field-constants'; import { PROPERTY_NAME, PROPERTY_VALUES, @@ -21,6 +20,7 @@ import { getRangeInputDataForm, getRangeInputSchema, } from '../../inputs/react-hook-form/range-input'; +import { FreePropertiesTypes } from './filter-free-properties'; export const getCriteriaBasedSchema = (extraFields: any) => ({ [FieldConstants.CRITERIA_BASED]: yup.object().shape({ @@ -140,6 +140,47 @@ export const backToFrontTweak = (response: any) => { return ret; }; +function isNominalVoltageEmpty( + nominalVoltage: Record +): boolean { + return ( + nominalVoltage[FieldConstants.VALUE_1] === null && + nominalVoltage[FieldConstants.VALUE_2] === null + ); +} + +// The server expect them to be null if the user don't fill them, unlike contingency list +function cleanNominalVoltages(formValues: any) { + const cleanedFormValues = { ...formValues }; + if ( + isNominalVoltageEmpty(cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE]) + ) { + cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE] = null; + } + if ( + isNominalVoltageEmpty( + cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_1] + ) + ) { + cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_1] = null; + } + if ( + isNominalVoltageEmpty( + cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_2] + ) + ) { + cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_2] = null; + } + if ( + isNominalVoltageEmpty( + cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_3] + ) + ) { + cleanedFormValues[FieldConstants.NOMINAL_VOLTAGE_3] = null; + } + return cleanedFormValues; +} + /** * Transform * from obj.criteriaBased.freeProperties.[ @@ -205,29 +246,3 @@ export const frontToBackTweak = (id?: string, filter?: any) => { eff.freeProperties = freeProps; return ret; }; - -// The server expect them to be null if the user don't fill them, unlike contingency list -function cleanNominalVoltages(formValues: any) { - if (isNominalVoltageEmpty(formValues[FieldConstants.NOMINAL_VOLTAGE])) { - formValues[FieldConstants.NOMINAL_VOLTAGE] = null; - } - if (isNominalVoltageEmpty(formValues[FieldConstants.NOMINAL_VOLTAGE_1])) { - formValues[FieldConstants.NOMINAL_VOLTAGE_1] = null; - } - if (isNominalVoltageEmpty(formValues[FieldConstants.NOMINAL_VOLTAGE_2])) { - formValues[FieldConstants.NOMINAL_VOLTAGE_2] = null; - } - if (isNominalVoltageEmpty(formValues[FieldConstants.NOMINAL_VOLTAGE_3])) { - formValues[FieldConstants.NOMINAL_VOLTAGE_3] = null; - } - return formValues; -} - -function isNominalVoltageEmpty( - nominalVoltage: Record -): boolean { - return ( - nominalVoltage[FieldConstants.VALUE_1] === null && - nominalVoltage[FieldConstants.VALUE_2] === null - ); -} diff --git a/src/components/filter/criteria-based/criteria-based-form.tsx b/src/components/filter/criteria-based/criteria-based-form.tsx index 5a2ffb9d..23be967d 100644 --- a/src/components/filter/criteria-based/criteria-based-form.tsx +++ b/src/components/filter/criteria-based/criteria-based-form.tsx @@ -5,12 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FieldConstants } from '../../../utils/field-constants'; import { useFormContext, useWatch } from 'react-hook-form'; import { Grid } from '@mui/material'; +import FieldConstants from '../../../utils/field-constants'; import SelectInput from '../../inputs/react-hook-form/select-inputs/select-input'; import InputWithPopupConfirmation from '../../inputs/react-hook-form/select-inputs/input-with-popup-confirmation'; -import { FunctionComponent } from 'react'; import { FormEquipment } from '../utils/filter-form-utils'; export interface CriteriaBasedFormProps { @@ -18,10 +17,10 @@ export interface CriteriaBasedFormProps { defaultValues: Record; } -const CriteriaBasedForm: FunctionComponent = ({ +function CriteriaBasedForm({ equipments, defaultValues, -}) => { +}: Readonly) { const { getValues, setValue } = useFormContext(); const watchEquipmentType = useWatch({ @@ -51,19 +50,20 @@ const CriteriaBasedForm: FunctionComponent = ({ Input={SelectInput} name={FieldConstants.EQUIPMENT_TYPE} options={Object.values(equipments)} - label={'equipmentType'} + label="equipmentType" shouldOpenPopup={openConfirmationPopup} resetOnConfirmation={handleResetOnConfirmation} - message={'changeTypeMessage'} - validateButtonLabel={'button.changeType'} + message="changeTypeMessage" + validateButtonLabel="button.changeType" /> {watchEquipmentType && equipments[watchEquipmentType].fields.map( (equipment: any, index: number) => { const EquipmentForm = equipment.renderer; + const uniqueKey = `${watchEquipmentType}-${index}`; return ( - + ); @@ -71,6 +71,6 @@ const CriteriaBasedForm: FunctionComponent = ({ )} ); -}; +} export default CriteriaBasedForm; diff --git a/src/components/filter/criteria-based/filter-free-properties.tsx b/src/components/filter/criteria-based/filter-free-properties.tsx index 1681e3d0..6a5a2d21 100644 --- a/src/components/filter/criteria-based/filter-free-properties.tsx +++ b/src/components/filter/criteria-based/filter-free-properties.tsx @@ -5,34 +5,38 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import ErrorInput from '../../inputs/react-hook-form/error-management/error-input'; -import FieldErrorAlert from '../../inputs/react-hook-form/error-management/field-error-alert'; import { Button, Grid, ListItem } from '@mui/material'; -import { FieldConstants } from '../../../utils/field-constants'; import { useFieldArray, useWatch } from 'react-hook-form'; import { FormattedMessage } from 'react-intl'; -import { FreePropertiesTypes } from './filter-properties'; +import AddIcon from '@mui/icons-material/Add'; +import { useMemo } from 'react'; +import ErrorInput from '../../inputs/react-hook-form/error-management/error-input'; +import FieldErrorAlert from '../../inputs/react-hook-form/error-management/field-error-alert'; +import FieldConstants from '../../../utils/field-constants'; import FilterProperty, { PROPERTY_NAME, PROPERTY_VALUES, PROPERTY_VALUES_1, PROPERTY_VALUES_2, } from './filter-property'; -import AddIcon from '@mui/icons-material/Add'; -import { FunctionComponent, useMemo } from 'react'; import { Hvdc, Line } from '../../../utils/equipment-types'; import { PredefinedProperties } from '../../../utils/types'; +export enum FreePropertiesTypes { + SUBSTATION_FILTER_PROPERTIES = 'substationFreeProperties', + FREE_FILTER_PROPERTIES = 'freeProperties', +} + interface FilterFreePropertiesProps { freePropertiesType: FreePropertiesTypes; predefined: PredefinedProperties; } -const FilterFreeProperties: FunctionComponent = ({ +function FilterFreeProperties({ freePropertiesType, predefined, -}) => { +}: Readonly) { const watchEquipmentType = useWatch({ name: FieldConstants.EQUIPMENT_TYPE, }); @@ -80,7 +84,7 @@ const FilterFreeProperties: FunctionComponent = ({ <> - {(title) =>

{title}

} + {(formattedTitle) =>

{formattedTitle}

}
{filterProperties.map((prop, index) => ( @@ -95,8 +99,8 @@ const FilterFreeProperties: FunctionComponent = ({ ))} - @@ -104,6 +108,6 @@ const FilterFreeProperties: FunctionComponent = ({ ); -}; +} export default FilterFreeProperties; diff --git a/src/components/filter/criteria-based/filter-properties.tsx b/src/components/filter/criteria-based/filter-properties.tsx index f6938d58..83db6cb9 100644 --- a/src/components/filter/criteria-based/filter-properties.tsx +++ b/src/components/filter/criteria-based/filter-properties.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Grid from '@mui/material/Grid'; -import { FunctionComponent, useEffect, useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { useWatch } from 'react-hook-form'; import { FormattedMessage } from 'react-intl'; import { @@ -20,23 +20,20 @@ import { VoltageLevel, } from '../../../utils/equipment-types'; import { areArrayElementsUnique } from '../../../utils/functions'; -import { FieldConstants } from '../../../utils/field-constants'; +import FieldConstants from '../../../utils/field-constants'; import yup from '../../../utils/yup-config'; -import FilterFreeProperties from './filter-free-properties'; +import FilterFreeProperties, { + FreePropertiesTypes, +} from './filter-free-properties'; import { PROPERTY_NAME, PROPERTY_VALUES, PROPERTY_VALUES_1, PROPERTY_VALUES_2, } from './filter-property'; -import { usePredefinedProperties } from '../../../hooks/predefined-properties-hook'; +import usePredefinedProperties from '../../../hooks/predefined-properties-hook'; import { FilterType } from '../constants/filter-constants'; -export enum FreePropertiesTypes { - SUBSTATION_FILTER_PROPERTIES = 'substationFreeProperties', - FREE_FILTER_PROPERTIES = 'freeProperties', -} - function propertyValuesTest( values: (string | undefined)[] | undefined, context: yup.TestContext, @@ -54,9 +51,8 @@ function propertyValuesTest( equipmentType === Line.type || equipmentType === Hvdc.type; if (doublePropertyValues) { return isForLineOrHvdcLine ? values?.length! > 0 : true; - } else { - return isForLineOrHvdcLine ? true : values?.length! > 0; } + return isForLineOrHvdcLine ? true : values?.length! > 0; } export const filterPropertiesYupSchema = { @@ -148,7 +144,7 @@ export const filterPropertiesYupSchema = { ), }; -const FilterProperties: FunctionComponent = () => { +function FilterProperties() { const watchEquipmentType = useWatch({ name: FieldConstants.EQUIPMENT_TYPE, }); @@ -192,7 +188,7 @@ const FilterProperties: FunctionComponent = () => { watchEquipmentType && ( - + {(txt) =>

{txt}

}
{displayEquipmentProperties && ( @@ -215,6 +211,6 @@ const FilterProperties: FunctionComponent = () => {
) ); -}; +} export default FilterProperties; diff --git a/src/components/filter/criteria-based/filter-property.tsx b/src/components/filter/criteria-based/filter-property.tsx index f63ef769..bdc93439 100644 --- a/src/components/filter/criteria-based/filter-property.tsx +++ b/src/components/filter/criteria-based/filter-property.tsx @@ -4,14 +4,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import DeleteIcon from '@mui/icons-material/Delete'; import IconButton from '@mui/material/IconButton'; import Grid from '@mui/material/Grid'; import { useFormContext, useWatch } from 'react-hook-form'; import AutocompleteInput from '../../inputs/react-hook-form/autocomplete-inputs/autocomplete-input'; import MultipleAutocompleteInput from '../../inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input'; -import { FieldConstants } from '../../../utils/field-constants'; +import FieldConstants from '../../../utils/field-constants'; import { PredefinedProperties } from '../../../utils/types'; @@ -28,41 +28,43 @@ interface FilterPropertyProps { propertyType: string; } -const FilterProperty: FunctionComponent = (props) => { +function FilterProperty(props: Readonly) { + const { propertyType, index, predefined, valuesFields, handleDelete } = + props; const { setValue } = useFormContext(); const watchName = useWatch({ - name: `${FieldConstants.CRITERIA_BASED}.${props.propertyType}[${props.index}].${PROPERTY_NAME}`, + name: `${FieldConstants.CRITERIA_BASED}.${propertyType}[${index}].${PROPERTY_NAME}`, }); const predefinedNames = useMemo(() => { - return Object.keys(props.predefined ?? []).sort(); - }, [props.predefined]); + return Object.keys(predefined ?? []).sort(); + }, [predefined]); const predefinedValues = useMemo(() => { - const predefinedForName: string[] = props.predefined?.[watchName]; + const predefinedForName: string[] = predefined?.[watchName]; if (!predefinedForName) { return []; } return [...new Set(predefinedForName)].sort(); - }, [watchName, props.predefined]); + }, [watchName, predefined]); // We reset values when name change const onNameChange = useCallback(() => { - props.valuesFields.forEach((valuesField) => + valuesFields.forEach((valuesField) => setValue( - `${FieldConstants.CRITERIA_BASED}.${props.propertyType}[${props.index}].${valuesField.name}`, + `${FieldConstants.CRITERIA_BASED}.${propertyType}[${index}].${valuesField.name}`, [] ) ); - }, [setValue, props.index, props.valuesFields, props.propertyType]); + }, [setValue, index, valuesFields, propertyType]); return ( = (props) => { onChangeCallback={onNameChange} /> - {props.valuesFields.map((valuesField) => ( + {valuesFields.map((valuesField) => ( ))} - props.handleDelete(props.index)}> + handleDelete(index)}> ); -}; +} export default FilterProperty; diff --git a/src/components/filter/expert/expert-filter-constants.ts b/src/components/filter/expert/expert-filter-constants.ts index 043ddc06..37a06ee2 100644 --- a/src/components/filter/expert/expert-filter-constants.ts +++ b/src/components/filter/expert/expert-filter-constants.ts @@ -5,13 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { Field } from 'react-querybuilder'; import { CombinatorType, DataType, FieldType, OperatorType, } from './expert-filter.type'; -import { Field } from 'react-querybuilder'; export enum RULES { EMPTY_RULE = 'emptyRule', diff --git a/src/components/filter/expert/expert-filter-edition-dialog.tsx b/src/components/filter/expert/expert-filter-edition-dialog.tsx index 53972f79..3886399f 100644 --- a/src/components/filter/expert/expert-filter-edition-dialog.tsx +++ b/src/components/filter/expert/expert-filter-edition-dialog.tsx @@ -5,22 +5,22 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useEffect, useState } from 'react'; -import { FieldConstants } from '../../../utils/field-constants'; -import { noSelectionForCopy } from '../../../utils/equipment-types'; +import { useCallback, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; +import { UUID } from 'crypto'; +import FieldConstants from '../../../utils/field-constants'; +import { noSelectionForCopy } from '../../../utils/equipment-types'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import CustomMuiDialog from '../../dialogs/custom-mui-dialog'; import yup from '../../../utils/yup-config'; -import { FilterForm } from '../filter-form'; +import FilterForm from '../filter-form'; import { EXPERT_FILTER_QUERY, expertFilterSchema } from './expert-filter-form'; import { saveExpertFilter } from '../utils/filter-api'; import { importExpertRules } from './expert-filter-utils'; -import { UUID } from 'crypto'; -import { elementExistsType } from '../criteria-based/criteria-based-filter-edition-dialog'; import { FilterType } from '../constants/filter-constants'; -import { FetchStatus } from '../../../utils/FetchStatus'; +import FetchStatus from '../../../utils/FetchStatus'; +import { ElementExistsType } from '../../../utils/ElementType'; const formSchema = yup .object() @@ -44,13 +44,11 @@ export interface ExpertFilterEditionDialogProps { getFilterById: (id: string) => Promise<{ [prop: string]: any }>; setSelectionForCopy: (selection: any) => void; activeDirectory?: UUID; - elementExists?: elementExistsType; + elementExists?: ElementExistsType; language?: string; } -const ExpertFilterEditionDialog: FunctionComponent< - ExpertFilterEditionDialogProps -> = ({ +function ExpertFilterEditionDialog({ id, name, titleId, @@ -63,7 +61,7 @@ const ExpertFilterEditionDialog: FunctionComponent< activeDirectory, elementExists, language, -}) => { +}: Readonly) { const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); @@ -151,7 +149,7 @@ const ExpertFilterEditionDialog: FunctionComponent< formSchema={formSchema} formMethods={formMethods} titleId={titleId} - removeOptional={true} + removeOptional disabledSave={!!nameError || !!isValidating} isDataFetching={dataFetchStatus === FetchStatus.FETCHING} language={language} @@ -164,6 +162,6 @@ const ExpertFilterEditionDialog: FunctionComponent< )} ); -}; +} export default ExpertFilterEditionDialog; diff --git a/src/components/filter/expert/expert-filter-form.tsx b/src/components/filter/expert/expert-filter-form.tsx index 48285d07..03534ff3 100644 --- a/src/components/filter/expert/expert-filter-form.tsx +++ b/src/components/filter/expert/expert-filter-form.tsx @@ -5,13 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import Grid from '@mui/material/Grid'; import type { RuleGroupTypeAny } from 'react-querybuilder'; import { formatQuery } from 'react-querybuilder'; import './styles-expert-filter.css'; import { useFormContext, useWatch } from 'react-hook-form'; +import * as yup from 'yup'; +import { v4 as uuid4 } from 'uuid'; +import { useIntl } from 'react-intl'; import { testQuery } from './expert-filter-utils'; import { COMBINATOR_OPTIONS, @@ -20,16 +23,13 @@ import { OPERATOR_OPTIONS, RULES, } from './expert-filter-constants'; -import * as yup from 'yup'; import { FieldType } from './expert-filter.type'; -import { v4 as uuid4 } from 'uuid'; -import { useIntl } from 'react-intl'; -import { FieldConstants } from '../../../utils/field-constants'; -import CustomReactQueryBuilder from '../../inputs/react-query-builder/custom-react-query-builder'; +import FieldConstants from '../../../utils/field-constants'; import InputWithPopupConfirmation from '../../inputs/react-hook-form/select-inputs/input-with-popup-confirmation'; import SelectInput from '../../inputs/react-hook-form/select-inputs/select-input'; import { FilterType } from '../constants/filter-constants'; +import CustomReactQueryBuilder from '../../inputs/react-query-builder/custom-react-query-builder'; yup.setLocale({ mixed: { @@ -37,15 +37,20 @@ yup.setLocale({ notType: ({ type }) => { if (type === 'number') { return 'YupNotTypeNumber'; - } else { - return 'YupNotTypeDefault'; } + return 'YupNotTypeDefault'; }, }, }); export const EXPERT_FILTER_QUERY = 'rules'; +function isSupportedEquipmentType(equipmentType: string): boolean { + return Object.values(EXPERT_FILTER_EQUIPMENTS) + .map((equipments) => equipments.id) + .includes(equipmentType); +} + export const expertFilterSchema = { [EXPERT_FILTER_QUERY]: yup.object().when([FieldConstants.FILTER_TYPE], { is: FilterType.EXPERT.id, @@ -53,8 +58,8 @@ export const expertFilterSchema = { schema.when([FieldConstants.EQUIPMENT_TYPE], { is: (equipmentType: string) => isSupportedEquipmentType(equipmentType), - then: (schema: any) => - schema + then: (innerSchema: any) => + innerSchema .test( RULES.EMPTY_GROUP, RULES.EMPTY_GROUP, @@ -99,12 +104,6 @@ export const expertFilterSchema = { }), }; -function isSupportedEquipmentType(equipmentType: string): boolean { - return Object.values(EXPERT_FILTER_EQUIPMENTS) - .map((equipments) => equipments.id) - .includes(equipmentType); -} - const defaultQuery = { combinator: COMBINATOR_OPTIONS.AND.name, rules: [ @@ -123,7 +122,7 @@ export function getExpertFilterEmptyFormData() { }; } -const ExpertFilterForm: FunctionComponent = () => { +function ExpertFilterForm() { const intl = useIntl(); const { getValues, setValue } = useFormContext(); @@ -159,11 +158,11 @@ const ExpertFilterForm: FunctionComponent = () => { Input={SelectInput} name={FieldConstants.EQUIPMENT_TYPE} options={Object.values(EXPERT_FILTER_EQUIPMENTS)} - label={'equipmentType'} + label="equipmentType" shouldOpenPopup={openConfirmationPopup} resetOnConfirmation={handleResetOnConfirmation} - message={'changeTypeMessage'} - validateButtonLabel={'button.changeType'} + message="changeTypeMessage" + validateButtonLabel="button.changeType" />
{watchEquipmentType && @@ -175,6 +174,6 @@ const ExpertFilterForm: FunctionComponent = () => { )} ); -}; +} export default ExpertFilterForm; diff --git a/src/components/filter/expert/expert-filter-utils.ts b/src/components/filter/expert/expert-filter-utils.ts index f46a268b..98c31898 100644 --- a/src/components/filter/expert/expert-filter-utils.ts +++ b/src/components/filter/expert/expert-filter-utils.ts @@ -17,6 +17,7 @@ import { ValidationMap, } from 'react-querybuilder'; import { IntlShape } from 'react-intl'; +import { validate as uuidValidate } from 'uuid'; import { CombinatorType, DataType, @@ -25,7 +26,6 @@ import { RuleGroupTypeExport, RuleTypeExport, } from './expert-filter.type'; -import { validate as uuidValidate } from 'uuid'; import { FIELDS_OPTIONS, OPERATOR_OPTIONS, @@ -59,7 +59,7 @@ const getDataType = (fieldName: string, operator: string) => { return DataType.FILTER_UUID; } const field = Object.values(FIELDS_OPTIONS).find( - (field) => field.name === fieldName + (fieldOption) => fieldOption.name === fieldName ); return field?.dataType; @@ -67,11 +67,11 @@ const getDataType = (fieldName: string, operator: string) => { export const getOperators = (fieldName: string, intl: IntlShape) => { const field = Object.values(FIELDS_OPTIONS).find( - (field) => field.name === fieldName + (fieldOption) => fieldOption.name === fieldName ); switch (field?.dataType) { - case DataType.STRING: + case DataType.STRING: { let operators: { name: string; customName: string; @@ -98,15 +98,16 @@ export const getOperators = (fieldName: string, intl: IntlShape) => { if (field.name === FieldType.ID) { // When the ID is selected, the operators EXISTS and NOT_EXISTS must be removed. operators = operators.filter( - (field) => - field.name !== OperatorType.EXISTS && - field.name !== OperatorType.NOT_EXISTS + (fieldOption) => + fieldOption.name !== OperatorType.EXISTS && + fieldOption.name !== OperatorType.NOT_EXISTS ); } return operators.map((operator) => ({ name: operator.name, label: intl.formatMessage({ id: operator.label }), })); + } case DataType.NUMBER: return [ OPERATOR_OPTIONS.EQUALS, @@ -126,7 +127,7 @@ export const getOperators = (fieldName: string, intl: IntlShape) => { name: operator.name, label: intl.formatMessage({ id: operator.label }), })); - case DataType.ENUM: + case DataType.ENUM: { let enumOperators: { name: string; customName: string; @@ -139,15 +140,16 @@ export const getOperators = (fieldName: string, intl: IntlShape) => { if (field.name === FieldType.SHUNT_COMPENSATOR_TYPE) { // When the SHUNT_COMPENSATOR_TYPE is selected, the operator IN must be removed. enumOperators = enumOperators.filter( - (field) => field.customName !== OperatorType.IN + (fieldOption) => fieldOption.customName !== OperatorType.IN ); } return enumOperators.map((operator) => ({ name: operator.name, label: intl.formatMessage({ id: operator.label }), })); - case DataType.PROPERTY: - let propertiesOperators: { + } + case DataType.PROPERTY: { + const propertiesOperators: { name: string; customName: string; label: string; @@ -156,17 +158,18 @@ export const getOperators = (fieldName: string, intl: IntlShape) => { name: operator.name, label: intl.formatMessage({ id: operator.label }), })); + } + default: + return defaultOperators; } - return defaultOperators; }; function changeValueUnit(value: any, field: FieldType) { if (microUnits.includes(field)) { if (!Array.isArray(value)) { return microUnitToUnit(value); - } else { - return value.map((a: number) => microUnitToUnit(a)); } + return value.map((a: number) => microUnitToUnit(a)); } return value; } @@ -196,7 +199,7 @@ export function exportExpertRules( isValueAnArray && dataType !== DataType.PROPERTY ? changeValueUnit(rule.value, rule.field as FieldType) : undefined, - dataType: dataType, + dataType, propertyName: dataType === DataType.PROPERTY ? rule.value.propertyName @@ -213,9 +216,8 @@ export function exportExpertRules( const transformedRules = group.rules.map((ruleOrGroup) => { if ('rules' in ruleOrGroup) { return transformGroup(ruleOrGroup as CustomRuleGroupType); - } else { - return transformRule(ruleOrGroup as CustomRuleType); } + return transformRule(ruleOrGroup as CustomRuleType); }); return { @@ -238,7 +240,8 @@ export function importExpertRules( propertyValues: rule.propertyValues, propertyOperator: rule.operator, }; - } else if (rule.values) { + } + if (rule.values) { // values is a Set on server side, so need to sort if (rule.dataType === DataType.NUMBER) { return rule.values @@ -249,14 +252,12 @@ export function importExpertRules( : numberValue; }) .sort((a, b) => a - b); - } else { - return rule.values.sort(); } - } else { - return microUnits.includes(rule.field) - ? unitToMicroUnit(parseFloat(rule.value as string)) - : rule.value; + return rule.values.sort(); } + return microUnits.includes(rule.field) + ? unitToMicroUnit(parseFloat(rule.value as string)) + : rule.value; } function transformRule(rule: RuleTypeExport): CustomRuleType { @@ -282,9 +283,8 @@ export function importExpertRules( const transformedRules = group.rules.map((ruleOrGroup) => { if ('rules' in ruleOrGroup) { return transformGroup(ruleOrGroup as RuleGroupTypeExport); - } else { - return transformRule(ruleOrGroup as RuleTypeExport); } + return transformRule(ruleOrGroup as RuleTypeExport); }); return { @@ -305,21 +305,10 @@ export function countRules(query: RuleGroupTypeAny): number { sum + countRules(ruleOrGroup as RuleGroupTypeAny), 0 ); - } else { - return 1; } + return 1; } -export const testQuery = (check: string, query: RuleGroupTypeAny): boolean => { - const queryValidatorResult = queryValidator(query); - return !Object.values(queryValidatorResult).some((ruleValidation) => { - if (typeof ruleValidation !== 'boolean' && ruleValidation.reasons) { - return ruleValidation.reasons.includes(check); - } - return false; - }); -}; - // Fork of defaultValidator of the react-query-builder to validate rules and groups export const queryValidator: QueryValidator = (query) => { const result: ValidationMap = {}; @@ -349,8 +338,8 @@ export const queryValidator: QueryValidator = (query) => { reasons: [RULES.EMPTY_RULE], }; } else if ( - isNaN(parseFloat(rule.value[0])) || - isNaN(parseFloat(rule.value[1])) + Number.isNaN(parseFloat(rule.value[0])) || + Number.isNaN(parseFloat(rule.value[1])) ) { result[rule.id] = { valid: false, @@ -380,7 +369,11 @@ export const queryValidator: QueryValidator = (query) => { valid: false, reasons: [RULES.EMPTY_RULE], }; - } else if (rule.id && isNumberInput && isNaN(parseFloat(rule.value))) { + } else if ( + rule.id && + isNumberInput && + Number.isNaN(parseFloat(rule.value)) + ) { result[rule.id] = { valid: false, reasons: [RULES.INCORRECT_RULE], @@ -436,6 +429,26 @@ export const queryValidator: QueryValidator = (query) => { return result; }; +export const testQuery = (check: string, query: RuleGroupTypeAny): boolean => { + const queryValidatorResult = queryValidator(query); + return !Object.values(queryValidatorResult).some((ruleValidation) => { + if (typeof ruleValidation !== 'boolean' && ruleValidation.reasons) { + return ruleValidation.reasons.includes(check); + } + return false; + }); +}; + +// cf path concept https://react-querybuilder.js.org/docs/tips/path +export function getNumberOfSiblings(path: number[], query: RuleGroupTypeAny) { + // Get the path of this rule's parent group + const parentPath = getParentPath(path); + // Find the parent group object in the query + const parentGroup = findPath(parentPath, query) as RuleGroupType; + // Return the number of siblings + return parentGroup.rules.length; +} + // Remove a rule or group and its parents if they become empty export function recursiveRemove( query: RuleGroupTypeAny, @@ -449,17 +462,6 @@ export function recursiveRemove( return recursiveRemove(query, getParentPath(path)); } // Otherwise, we can safely remove it - else { - return remove(query, path); - } -} -// cf path concept https://react-querybuilder.js.org/docs/tips/path -export function getNumberOfSiblings(path: number[], query: RuleGroupTypeAny) { - // Get the path of this rule's parent group - const parentPath = getParentPath(path); - // Find the parent group object in the query - const parentGroup = findPath(parentPath, query) as RuleGroupType; - // Return the number of siblings - return parentGroup.rules.length; + return remove(query, path); } diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx index 4b6ee5fe..20208bfd 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx @@ -5,11 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { saveExplicitNamingFilter } from '../utils/filter-api'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; +import { v4 as uuid4 } from 'uuid'; +import { UUID } from 'crypto'; +import { saveExplicitNamingFilter } from '../utils/filter-api'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import CustomMuiDialog from '../../dialogs/custom-mui-dialog'; import yup from '../../../utils/yup-config'; @@ -17,15 +19,13 @@ import { explicitNamingFilterSchema, FILTER_EQUIPMENTS_ATTRIBUTES, } from './explicit-naming-filter-form'; -import { FieldConstants } from '../../../utils/field-constants'; +import FieldConstants from '../../../utils/field-constants'; -import { FilterForm } from '../filter-form'; -import { v4 as uuid4 } from 'uuid'; +import FilterForm from '../filter-form'; import { noSelectionForCopy } from '../../../utils/equipment-types'; -import { UUID } from 'crypto'; -import { elementExistsType } from '../criteria-based/criteria-based-filter-edition-dialog'; import { FilterType } from '../constants/filter-constants'; -import { FetchStatus } from '../../../utils/FetchStatus'; +import FetchStatus from '../../../utils/FetchStatus'; +import { ElementExistsType } from '../../../utils/ElementType'; const formSchema = yup .object() @@ -48,13 +48,11 @@ interface ExplicitNamingFilterEditionDialogProps { setSelectionForCopy: (selection: any) => void; getFilterById: (id: string) => Promise; activeDirectory?: UUID; - elementExists?: elementExistsType; + elementExists?: ElementExistsType; language?: string; } -const ExplicitNamingFilterEditionDialog: FunctionComponent< - ExplicitNamingFilterEditionDialogProps -> = ({ +function ExplicitNamingFilterEditionDialog({ id, name, titleId, @@ -67,7 +65,7 @@ const ExplicitNamingFilterEditionDialog: FunctionComponent< activeDirectory, elementExists, language, -}) => { +}: Readonly) { const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); @@ -157,7 +155,7 @@ const ExplicitNamingFilterEditionDialog: FunctionComponent< formSchema={formSchema} formMethods={formMethods} titleId={titleId} - removeOptional={true} + removeOptional disabledSave={!!nameError || !!isValidating} isDataFetching={dataFetchStatus === FetchStatus.FETCHING} language={language} @@ -170,7 +168,7 @@ const ExplicitNamingFilterEditionDialog: FunctionComponent< )} ); -}; +} ExplicitNamingFilterEditionDialog.prototype = { id: PropTypes.string, diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx index 6b8bc837..74c0f5f3 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx @@ -4,32 +4,36 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useEffect, useMemo } from 'react'; -import { FieldConstants } from '../../../utils/field-constants'; +import { useCallback, useEffect, useMemo } from 'react'; +import { useIntl } from 'react-intl'; +import { useFormContext, useWatch } from 'react-hook-form'; +import Grid from '@mui/material/Grid'; +import { ValueParserParams } from 'ag-grid-community'; +import { v4 as uuid4 } from 'uuid'; +import { UUID } from 'crypto'; +import FieldConstants from '../../../utils/field-constants'; import yup from '../../../utils/yup-config'; import CustomAgGridTable, { ROW_DRAGGING_SELECTION_COLUMN_DEF, } from '../../inputs/react-hook-form/ag-grid-table/custom-ag-grid-table'; -import { useIntl } from 'react-intl'; -import { useFormContext, useWatch } from 'react-hook-form'; -import Grid from '@mui/material/Grid'; import SelectInput from '../../inputs/react-hook-form/select-inputs/select-input'; -import { ValueParserParams } from 'ag-grid-community'; import { Generator, Load } from '../../../utils/equipment-types'; -import { NumericEditor } from '../../inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor'; +import NumericEditor from '../../inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor'; import InputWithPopupConfirmation from '../../inputs/react-hook-form/select-inputs/input-with-popup-confirmation'; -import { v4 as uuid4 } from 'uuid'; import { toFloatOrNullValue } from '../../inputs/react-hook-form/utils/functions'; import { DISTRIBUTION_KEY, FilterType } from '../constants/filter-constants'; import { FILTER_EQUIPMENTS } from '../utils/filter-form-utils'; -import { UUID } from 'crypto'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { ElementType } from '../../../utils/ElementType'; import ModifyElementSelection from '../../dialogs/modify-element-selection'; -import { exportFilter } from '../../../services/study'; +import exportFilter from '../../../services/study'; export const FILTER_EQUIPMENTS_ATTRIBUTES = 'filterEquipmentsAttributes'; +function isGeneratorOrLoad(equipmentType: string): boolean { + return equipmentType === Generator.type || equipmentType === Load.type; +} + export const explicitNamingFilterSchema = { [FILTER_EQUIPMENTS_ATTRIBUTES]: yup .array() @@ -51,8 +55,8 @@ export const explicitNamingFilterSchema = { .when([FieldConstants.EQUIPMENT_TYPE], { is: (equipmentType: string) => isGeneratorOrLoad(equipmentType), - then: (schema) => - schema + then: (innerSchema) => + innerSchema .test( 'noKeyWithoutId', 'distributionKeyWithMissingIdError', @@ -83,10 +87,6 @@ export const explicitNamingFilterSchema = { }), }; -function isGeneratorOrLoad(equipmentType: string): boolean { - return equipmentType === Generator.type || equipmentType === Load.type; -} - interface FilterTableRow { [FieldConstants.AG_GRID_ROW_UUID]: string; [FieldConstants.EQUIPMENT_ID]: string; @@ -113,16 +113,16 @@ export function getExplicitNamingFilterEmptyFormData() { export interface FilterForExplicitConversionProps { id: UUID; - equipmentType: String; + equipmentType: string; } interface ExplicitNamingFilterFormProps { sourceFilterForExplicitNamingConversion?: FilterForExplicitConversionProps; } -const ExplicitNamingFilterForm: FunctionComponent< - ExplicitNamingFilterFormProps -> = ({ sourceFilterForExplicitNamingConversion }) => { +function ExplicitNamingFilterForm({ + sourceFilterForExplicitNamingConversion, +}: Readonly) { const intl = useIntl(); const { snackError } = useSnackMessage(); @@ -144,7 +144,7 @@ const ExplicitNamingFilterForm: FunctionComponent< }, [sourceFilterForExplicitNamingConversion, setValue]); const columnDefs = useMemo(() => { - const columnDefs: any[] = [ + const newColumnDefs: any[] = [ ...ROW_DRAGGING_SELECTION_COLUMN_DEF, { headerName: intl.formatMessage({ @@ -158,7 +158,7 @@ const ExplicitNamingFilterForm: FunctionComponent< }, ]; if (forGeneratorOrLoad) { - columnDefs.push({ + newColumnDefs.push({ headerName: intl.formatMessage({ id: DISTRIBUTION_KEY }), field: DISTRIBUTION_KEY, editable: true, @@ -167,7 +167,7 @@ const ExplicitNamingFilterForm: FunctionComponent< maxWidth: 200, }); } - return columnDefs; + return newColumnDefs; }, [intl, forGeneratorOrLoad]); const defaultColDef = useMemo( @@ -178,13 +178,15 @@ const ExplicitNamingFilterForm: FunctionComponent< ); const csvFileHeaders = useMemo(() => { - const csvFileHeaders = [ + const newCsvFileHeaders = [ intl.formatMessage({ id: FieldConstants.EQUIPMENT_ID }), ]; if (forGeneratorOrLoad) { - csvFileHeaders.push(intl.formatMessage({ id: DISTRIBUTION_KEY })); + newCsvFileHeaders.push( + intl.formatMessage({ id: DISTRIBUTION_KEY }) + ); } - return csvFileHeaders; + return newCsvFileHeaders; }, [intl, forGeneratorOrLoad]); const getDataFromCsvFile = useCallback((csvData: any) => { @@ -196,15 +198,14 @@ const ExplicitNamingFilterForm: FunctionComponent< [DISTRIBUTION_KEY]: toFloatOrNullValue(value[1]?.trim()), }; }); - } else { - return []; } + return []; }, []); const openConfirmationPopup = () => { return getValues(FILTER_EQUIPMENTS_ATTRIBUTES).some( (row: FilterTableRow) => - row[DISTRIBUTION_KEY] || row[FieldConstants.EQUIPMENT_ID] + row[DISTRIBUTION_KEY] ?? row[FieldConstants.EQUIPMENT_ID] ); }; @@ -242,20 +243,20 @@ const ExplicitNamingFilterForm: FunctionComponent< name={FieldConstants.EQUIPMENT_TYPE} options={Object.values(FILTER_EQUIPMENTS)} disabled={sourceFilterForExplicitNamingConversion} - label={'equipmentType'} + label="equipmentType" shouldOpenPopup={openConfirmationPopup} resetOnConfirmation={handleResetOnConfirmation} - message={'changeTypeMessage'} - validateButtonLabel={'button.changeType'} + message="changeTypeMessage" + validateButtonLabel="button.changeType" /> {sourceFilterForExplicitNamingConversion && ( )} @@ -266,7 +267,7 @@ const ExplicitNamingFilterForm: FunctionComponent< columnDefs={columnDefs} defaultColDef={defaultColDef} makeDefaultRowData={makeDefaultRowData} - pagination={true} + pagination paginationPageSize={100} suppressRowClickSelection alwaysShowVerticalScroll @@ -288,6 +289,6 @@ const ExplicitNamingFilterForm: FunctionComponent< )} ); -}; +} export default ExplicitNamingFilterForm; diff --git a/src/components/filter/filter-creation-dialog.tsx b/src/components/filter/filter-creation-dialog.tsx index fda316ae..738b5ab3 100644 --- a/src/components/filter/filter-creation-dialog.tsx +++ b/src/components/filter/filter-creation-dialog.tsx @@ -5,13 +5,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback } from 'react'; +import { useCallback } from 'react'; +import { Resolver, useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { UUID } from 'crypto'; import { saveCriteriaBasedFilter, saveExpertFilter, saveExplicitNamingFilter, } from './utils/filter-api'; -import { Resolver, useForm } from 'react-hook-form'; import { useSnackMessage } from '../../hooks/useSnackMessage'; import CustomMuiDialog from '../dialogs/custom-mui-dialog'; import { @@ -23,18 +25,16 @@ import { FILTER_EQUIPMENTS_ATTRIBUTES, getExplicitNamingFilterEmptyFormData, } from './explicit-naming/explicit-naming-filter-form'; -import { FieldConstants } from '../../utils/field-constants'; +import FieldConstants from '../../utils/field-constants'; import yup from '../../utils/yup-config'; -import { FilterForm } from './filter-form'; +import FilterForm from './filter-form'; import { EXPERT_FILTER_QUERY, expertFilterSchema, getExpertFilterEmptyFormData, } from './expert/expert-filter-form'; -import { yupResolver } from '@hookform/resolvers/yup'; -import { elementExistsType } from './criteria-based/criteria-based-filter-edition-dialog'; -import { UUID } from 'crypto'; import { FilterType } from './constants/filter-constants'; +import { ElementExistsType } from '../../utils/ElementType'; const emptyFormData = { [FieldConstants.NAME]: '', @@ -66,7 +66,7 @@ export interface FilterCreationDialogProps { open: boolean; onClose: () => void; activeDirectory?: UUID; - elementExists?: elementExistsType; + elementExists?: ElementExistsType; language?: string; sourceFilterForExplicitNamingConversion?: { id: UUID; @@ -74,14 +74,14 @@ export interface FilterCreationDialogProps { }; } -const FilterCreationDialog: FunctionComponent = ({ +function FilterCreationDialog({ open, onClose, activeDirectory, elementExists, language, sourceFilterForExplicitNamingConversion = undefined, -}) => { +}: Readonly) { const { snackError } = useSnackMessage(); const formMethods = useForm({ @@ -166,7 +166,7 @@ const FilterCreationDialog: FunctionComponent = ({ ? 'convertIntoExplicitNamingFilter' : 'createNewFilter' } - removeOptional={true} + removeOptional disabledSave={!!nameError || !!isValidating} language={language} > @@ -180,6 +180,6 @@ const FilterCreationDialog: FunctionComponent = ({ /> ); -}; +} export default FilterCreationDialog; diff --git a/src/components/filter/filter-form.tsx b/src/components/filter/filter-form.tsx index c7298807..04a2dda6 100644 --- a/src/components/filter/filter-form.tsx +++ b/src/components/filter/filter-form.tsx @@ -5,32 +5,37 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { UniqueNameInput } from '../inputs/react-hook-form/unique-name-input'; -import { FieldConstants } from '../../utils/field-constants'; +import React, { useEffect } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; +import { Box, Grid } from '@mui/material'; +import { UUID } from 'crypto'; +import FieldConstants from '../../utils/field-constants'; import CriteriaBasedFilterForm from './criteria-based/criteria-based-filter-form'; import ExplicitNamingFilterForm from './explicit-naming/explicit-naming-filter-form'; -import React, { FunctionComponent, useEffect } from 'react'; -import { useFormContext, useWatch } from 'react-hook-form'; import ExpertFilterForm from './expert/expert-filter-form'; -import { Box, Grid } from '@mui/material'; import RadioInput from '../inputs/react-hook-form/radio-input'; -import { ElementType } from '../../utils/ElementType'; -import { UUID } from 'crypto'; -import { elementExistsType } from './criteria-based/criteria-based-filter-edition-dialog'; +import { ElementExistsType, ElementType } from '../../utils/ElementType'; import ExpandingTextField from '../inputs/react-hook-form/ExpandingTextField'; import { FilterType } from './constants/filter-constants'; +import UniqueNameInput from '../inputs/react-hook-form/unique-name-input'; interface FilterFormProps { creation?: boolean; activeDirectory?: UUID; - elementExists?: elementExistsType; + elementExists?: ElementExistsType; sourceFilterForExplicitNamingConversion?: { id: UUID; equipmentType: string; }; } -export const FilterForm: FunctionComponent = (props) => { +function FilterForm(props: Readonly) { + const { + sourceFilterForExplicitNamingConversion, + creation, + activeDirectory, + elementExists, + } = props; const { setValue } = useFormContext(); const filterType = useWatch({ name: FieldConstants.FILTER_TYPE }); @@ -44,36 +49,36 @@ export const FilterForm: FunctionComponent = (props) => { }; useEffect(() => { - if (props.sourceFilterForExplicitNamingConversion) { + if (sourceFilterForExplicitNamingConversion) { setValue(FieldConstants.FILTER_TYPE, FilterType.EXPLICIT_NAMING.id); } - }, [props.sourceFilterForExplicitNamingConversion, setValue]); + }, [sourceFilterForExplicitNamingConversion, setValue]); return ( - {props.creation && ( + {creation && ( <> - {!props.sourceFilterForExplicitNamingConversion && ( + {!sourceFilterForExplicitNamingConversion && ( = (props) => { {filterType === FilterType.EXPLICIT_NAMING.id && ( )} {filterType === FilterType.EXPERT.id && } ); -}; +} + +export default FilterForm; diff --git a/src/components/filter/utils/filter-api.ts b/src/components/filter/utils/filter-api.ts index c9fabfac..3178e8d0 100644 --- a/src/components/filter/utils/filter-api.ts +++ b/src/components/filter/utils/filter-api.ts @@ -5,11 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FieldConstants } from '../../../utils/field-constants'; +import { UUID } from 'crypto'; +import FieldConstants from '../../../utils/field-constants'; import { frontToBackTweak } from '../criteria-based/criteria-based-filter-utils'; import { Generator, Load } from '../../../utils/equipment-types'; import { exportExpertRules } from '../expert/expert-filter-utils'; -import { UUID } from 'crypto'; import { DISTRIBUTION_KEY, FilterType } from '../constants/filter-constants'; import { createFilter, saveFilter } from '../../../services/explore'; @@ -43,7 +43,7 @@ export const saveExplicitNamingFilter = ( createFilter( { type: FilterType.EXPLICIT_NAMING.id, - equipmentType: equipmentType, + equipmentType, filterEquipmentsAttributes: cleanedTableValues, }, name, @@ -60,9 +60,9 @@ export const saveExplicitNamingFilter = ( } else { saveFilter( { - id: id, + id, type: FilterType.EXPLICIT_NAMING.id, - equipmentType: equipmentType, + equipmentType, filterEquipmentsAttributes: cleanedTableValues, }, name, @@ -116,7 +116,7 @@ export const saveExpertFilter = ( createFilter( { type: FilterType.EXPERT.id, - equipmentType: equipmentType, + equipmentType, rules: exportExpertRules(query), }, name, @@ -133,9 +133,9 @@ export const saveExpertFilter = ( } else { saveFilter( { - id: id, + id, type: FilterType.EXPERT.id, - equipmentType: equipmentType, + equipmentType, rules: exportExpertRules(query), }, name, diff --git a/src/components/filter/utils/filter-form-utils.ts b/src/components/filter/utils/filter-form-utils.ts index e44a4854..a8bbb9e6 100644 --- a/src/components/filter/utils/filter-form-utils.ts +++ b/src/components/filter/utils/filter-form-utils.ts @@ -4,9 +4,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FieldConstants } from '../../../utils/field-constants'; -import RangeInput from '../../inputs/react-hook-form/range-input'; import { FunctionComponent } from 'react'; +import FieldConstants from '../../../utils/field-constants'; +import RangeInput from '../../inputs/react-hook-form/range-input'; import CountriesInput from '../../inputs/react-hook-form/select-inputs/countries-input'; import SelectInput from '../../inputs/react-hook-form/select-inputs/select-input'; import { EquipmentType } from '../../../utils/EquipmentType'; diff --git a/src/components/inputs/react-hook-form/ExpandingTextField.tsx b/src/components/inputs/react-hook-form/ExpandingTextField.tsx index 8aa14dde..bcbbd250 100644 --- a/src/components/inputs/react-hook-form/ExpandingTextField.tsx +++ b/src/components/inputs/react-hook-form/ExpandingTextField.tsx @@ -5,10 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useState } from 'react'; +import { useState } from 'react'; import { TextFieldProps, Theme, Typography } from '@mui/material'; import { useWatch } from 'react-hook-form'; -import { useCustomFormContext } from './provider/use-custom-form-context'; +import useCustomFormContext from './provider/use-custom-form-context'; import TextInput, { TextInputProps } from './text-input'; interface ExpandingTextFieldProps extends TextInputProps { @@ -21,7 +21,7 @@ interface ExpandingTextFieldProps extends TextInputProps { textFieldFormProps?: TextFieldProps; } -const ExpandingTextField: FunctionComponent = ({ +function ExpandingTextField({ name, maxCharactersNumber = 500, rows, @@ -30,11 +30,11 @@ const ExpandingTextField: FunctionComponent = ({ label, textFieldFormProps, ...otherTexFieldProps -}) => { +}: Readonly) { const [isFocused, setIsFocused] = useState(false); const { control } = useCustomFormContext(); const descriptionWatch = useWatch({ - name: name, + name, control, }); const handleFocus = () => { @@ -46,7 +46,7 @@ const ExpandingTextField: FunctionComponent = ({ }; const isOverTheLimit = descriptionWatch?.length > maxCharactersNumber; const descriptionLength = descriptionWatch?.length ?? 0; - const descriptionCounter = descriptionLength + '/' + maxCharactersNumber; + const descriptionCounter = `${descriptionLength}/${maxCharactersNumber}`; const rowsToDisplay = isFocused ? rows : minRows; @@ -76,7 +76,7 @@ const ExpandingTextField: FunctionComponent = ({ }, }, ...(rowsToDisplay && { rows: rowsToDisplay }), - ...(sx && { sx: sx }), + ...(sx && { sx }), ...textFieldFormProps, }; return ( @@ -87,6 +87,6 @@ const ExpandingTextField: FunctionComponent = ({ {...otherTexFieldProps} /> ); -}; +} export default ExpandingTextField; diff --git a/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx b/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx index 1cd7c9ae..ea7617d7 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx @@ -9,13 +9,13 @@ import IconButton from '@mui/material/IconButton'; import { ArrowCircleDown, ArrowCircleUp, Upload } from '@mui/icons-material'; import AddIcon from '@mui/icons-material/ControlPoint'; import DeleteIcon from '@mui/icons-material/Delete'; -import CsvUploader from './csv-uploader/csv-uploader'; -import { FunctionComponent, useState } from 'react'; +import { useState } from 'react'; import { useIntl } from 'react-intl'; import { styled } from '@mui/material/styles'; +import { FieldValues, UseFieldArrayReturn } from 'react-hook-form'; import ErrorInput from '../error-management/error-input'; import FieldErrorAlert from '../error-management/field-error-alert'; -import { FieldValues, UseFieldArrayReturn } from 'react-hook-form'; +import CsvUploader from './csv-uploader/csv-uploader'; const InnerColoredButton = styled(IconButton)(({ theme }) => { return { @@ -36,7 +36,7 @@ export interface BottomRightButtonsProps { csvProps: any; } -const BottomRightButtons: FunctionComponent = ({ +function BottomRightButtons({ name, disableUp, disableDown, @@ -47,7 +47,7 @@ const BottomRightButtons: FunctionComponent = ({ handleMoveRowDown, useFieldArrayOutput, csvProps, -}) => { +}: Readonly) { const [uploaderOpen, setUploaderOpen] = useState(false); const intl = useIntl(); @@ -75,28 +75,25 @@ const BottomRightButtons: FunctionComponent = ({ xs={11} sx={{ display: 'flex', justifyContent: 'right' }} > - + @@ -116,6 +113,6 @@ const BottomRightButtons: FunctionComponent = ({ /> ); -}; +} export default BottomRightButtons; diff --git a/src/components/inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor.ts b/src/components/inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor.ts index 123d48cc..144747c5 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor.ts +++ b/src/components/inputs/react-hook-form/ag-grid-table/cell-editors/numericEditor.ts @@ -16,8 +16,9 @@ const KEY_BACKSPACE = 'Backspace'; * React version if you want to check, with forwardRef, useEffect and useImperativeHandle : * https://www.ag-grid.com/react-data-grid/component-cell-editor/#cell-editor-example */ -export class NumericEditor implements ICellEditorComp { +class NumericEditor implements ICellEditorComp { eInput!: HTMLInputElement; + cancelBeforeStart!: boolean; // gets called once before the renderer is used @@ -28,24 +29,25 @@ export class NumericEditor implements ICellEditorComp { if (params.eventKey === KEY_BACKSPACE) { this.eInput.value = ''; - } else if (this.isCharNumeric(params.eventKey)) { + } else if (NumericEditor.isCharNumeric(params.eventKey)) { this.eInput.value = params.eventKey!; - } else { - if (params.value !== undefined && params.value !== null) { - this.eInput.value = params.value; - } + } else if (params.value !== undefined && params.value !== null) { + this.eInput.value = params.value; } this.eInput.addEventListener('keydown', (event) => { if (!event.key || event.key.length !== 1) { return; } - if (!this.isNumericKey(event)) { + if (!NumericEditor.isNumericKey(event)) { this.eInput.focus(); if (event.preventDefault) { event.preventDefault(); } - } else if (this.isNavigationKey(event) || this.isBackspace(event)) { + } else if ( + NumericEditor.isNavigationKey(event) || + NumericEditor.isBackspace(event) + ) { event.stopPropagation(); } }); @@ -59,11 +61,11 @@ export class NumericEditor implements ICellEditorComp { this.cancelBeforeStart = !!isNotANumber; } - isBackspace(event: any) { + static isBackspace(event: any) { return event.key === KEY_BACKSPACE; } - isNavigationKey(event: any) { + static isNavigationKey(event: any) { return event.key === 'ArrowLeft' || event.key === 'ArrowRight'; } @@ -84,24 +86,26 @@ export class NumericEditor implements ICellEditorComp { // returns the new value after editing getValue() { - const value = this.eInput.value; + const { value } = this.eInput; // FM : some modifications here const tmp = value?.replace(',', '.') || ''; return parseFloat(tmp) || null; } - isCharNumeric(charStr: string | null) { + static isCharNumeric(charStr: string | null) { // FM : I added ',' and '.' return charStr && !!/\d|,|\./.test(charStr); } - isNumericKey(event: any) { + static isNumericKey(event: any) { const charStr = event.key; - return this.isCharNumeric(charStr); + return NumericEditor.isCharNumeric(charStr); } // force call when focus is leaving the editor - focusOut() { + static focusOut() { return true; } } + +export default NumericEditor; diff --git a/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx b/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx index caf44059..ec7234c2 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx @@ -11,15 +11,15 @@ import DialogContent from '@mui/material/DialogContent'; import DialogActions from '@mui/material/DialogActions'; import { useCSVReader } from 'react-papaparse'; import Button from '@mui/material/Button'; -import React, { FunctionComponent, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import Grid from '@mui/material/Grid'; import { FormattedMessage, useIntl } from 'react-intl'; import CsvDownloader from 'react-csv-downloader'; import Alert from '@mui/material/Alert'; import { DialogContentText } from '@mui/material'; import { useWatch } from 'react-hook-form'; -import { CancelButton } from '../../../../../index'; -import { FieldConstants } from '../../../../../utils/field-constants'; +import FieldConstants from '../../../../../utils/field-constants'; +import CancelButton from '../../utils/cancel-button'; interface CsvUploaderProps { name: string; @@ -34,7 +34,7 @@ interface CsvUploaderProps { useFieldArrayOutput: any; } -const CsvUploader: FunctionComponent = ({ +function CsvUploader({ name, onClose, open, @@ -42,24 +42,25 @@ const CsvUploader: FunctionComponent = ({ fileHeaders, fileName, csvData, - validateData = (_rows) => true, + validateData = () => true, getDataFromCsv, useFieldArrayOutput, -}) => { +}: Readonly) { const watchTableValues = useWatch({ name }); const { append, replace } = useFieldArrayOutput; const [createError, setCreateError] = React.useState(''); const intl = useIntl(); const { CSVReader } = useCSVReader(); const [importedData, setImportedData] = useState([]); - const [isConfirmationPopupOpen, setOpenConfirmationPopup] = useState(false); + const [isConfirmationPopupOpen, setIsConfirmationPopupOpen] = + useState(false); const data = useMemo(() => { - const data = [...[fileHeaders]]; + const newData = [...[fileHeaders]]; if (Array.isArray(csvData)) { - csvData.forEach((row) => data.push([row])); + csvData.forEach((row) => newData.push([row])); } - return data; + return newData; }, [csvData, fileHeaders]); const handleClose = () => { onClose(); @@ -73,7 +74,7 @@ const CsvUploader: FunctionComponent = ({ } // validate the headers - for (let i = 0; i < fileHeaders.length; i++) { + for (let i = 0; i < fileHeaders.length; i += 1) { if (fileHeaders[i] !== '' && rows[0][i] !== fileHeaders[i]) { setCreateError( intl.formatMessage({ id: 'wrongCsvHeadersError' }) @@ -136,25 +137,25 @@ const CsvUploader: FunctionComponent = ({ ); if (isValuesInTable && getResultsFromImportedData().length > 0) { - setOpenConfirmationPopup(true); + setIsConfirmationPopupOpen(true); } else { - setOpenConfirmationPopup(false); + setIsConfirmationPopupOpen(false); handleFileSubmit(false); } }; const handleAddPopupConfirmation = () => { handleFileSubmit(true); - setOpenConfirmationPopup(false); + setIsConfirmationPopupOpen(false); }; const handleReplacePopupConfirmation = () => { handleFileSubmit(false); - setOpenConfirmationPopup(false); + setIsConfirmationPopupOpen(false); }; const handleCancelDialog = () => { - setOpenConfirmationPopup(false); + setIsConfirmationPopupOpen(false); }; const renderConfirmationCsvData = () => { return ( @@ -162,8 +163,8 @@ const CsvUploader: FunctionComponent = ({ open={isConfirmationPopupOpen} aria-labelledby="dialog-confirmation-csv-data" > - - {'Confirmation'} + + Confirmation @@ -201,9 +202,9 @@ const CsvUploader: FunctionComponent = ({ - @@ -217,28 +218,26 @@ const CsvUploader: FunctionComponent = ({ }} > {({ getRootProps, acceptedFile }: any) => ( - <> - - - - {acceptedFile - ? acceptedFile.name - : intl.formatMessage({ - id: 'uploadMessage', - })} - - - + + + + {acceptedFile + ? acceptedFile.name + : intl.formatMessage({ + id: 'uploadMessage', + })} + + )} @@ -262,6 +261,6 @@ const CsvUploader: FunctionComponent = ({ {renderConfirmationCsvData()} ); -}; +} export default CsvUploader; diff --git a/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx b/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx index a0a2874e..34b06686 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx @@ -5,15 +5,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; import { AgGridReact } from 'ag-grid-react'; import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-theme-alpine.css'; import { Grid, useTheme } from '@mui/material'; -import BottomRightButtons from './bottom-right-buttons'; import { useIntl } from 'react-intl'; -import { FieldConstants } from '../../../../utils/field-constants'; +import BottomRightButtons from './bottom-right-buttons'; +import FieldConstants from '../../../../utils/field-constants'; export const ROW_DRAGGING_SELECTION_COLUMN_DEF = [ { @@ -33,21 +33,17 @@ const style = (customProps: any) => ({ // - AG Grid colors override - // It shouldn't be exactly like this, but I couldn't make it works otherwise // https://www.ag-grid.com/react-data-grid/global-style-customisation/ - '--ag-alpine-active-color': theme.palette.primary.main + ' !important', - '--ag-checkbox-indeterminate-color': - theme.palette.primary.main + ' !important', - '--ag-background-color': theme.agGridBackground.color + ' !important', - '--ag-header-background-color': - theme.agGridBackground.color + ' !important', - '--ag-odd-row-background-color': - theme.agGridBackground.color + ' !important', - '--ag-modal-overlay-background-color': - theme.agGridBackground.color + ' !important', + '--ag-alpine-active-color': `${theme.palette.primary.main} !important`, + '--ag-checkbox-indeterminate-color': `${theme.palette.primary.main} !important`, + '--ag-background-color': `${theme.agGridBackground.color} !important`, + '--ag-header-background-color': `${theme.agGridBackground.color} !important`, + '--ag-odd-row-background-color': `${theme.agGridBackground.color} !important`, + '--ag-modal-overlay-background-color': `${theme.agGridBackground.color} !important`, '--ag-selected-row-background-color': 'transparent !important', '--ag-range-selection-border-color': 'transparent !important', - //overrides the default computed max height for ag grid default selector editor to make it more usable - //can be removed if a custom selector editor is implemented + // overrides the default computed max height for ag grid default selector editor to make it more usable + // can be removed if a custom selector editor is implemented '& .ag-select-list': { maxHeight: '300px !important', }, @@ -101,7 +97,7 @@ export interface CustomAgGridTableProps { stopEditingWhenCellsLoseFocus: boolean; } -export const CustomAgGridTable: FunctionComponent = ({ +function CustomAgGridTable({ name, columnDefs, makeDefaultRowData, @@ -114,7 +110,7 @@ export const CustomAgGridTable: FunctionComponent = ({ alwaysShowVerticalScroll, stopEditingWhenCellsLoseFocus, ...props -}) => { +}: Readonly) { const theme: any = useTheme(); const [gridApi, setGridApi] = useState(null); const [selectedRows, setSelectedRows] = useState([]); @@ -123,7 +119,7 @@ export const CustomAgGridTable: FunctionComponent = ({ const { control, getValues, watch } = useFormContext(); const useFieldArrayOutput = useFieldArray({ control, - name: name, + name, }); const { append, remove, update, swap, move } = useFieldArrayOutput; @@ -145,6 +141,14 @@ export const CustomAgGridTable: FunctionComponent = ({ const noRowSelected = selectedRows.length === 0; + const getIndex = (val: any) => { + return getValues(name).findIndex( + (row: any) => + row[FieldConstants.AG_GRID_ROW_UUID] === + val[FieldConstants.AG_GRID_ROW_UUID] + ); + }; + const handleMoveRowUp = () => { selectedRows .map((row) => getIndex(row)) @@ -188,14 +192,6 @@ export const CustomAgGridTable: FunctionComponent = ({ setNewRowAdded(true); }; - const getIndex = (val: any) => { - return getValues(name).findIndex( - (row: any) => - row[FieldConstants.AG_GRID_ROW_UUID] === - val[FieldConstants.AG_GRID_ROW_UUID] - ); - }; - useEffect(() => { if (gridApi) { gridApi.api.sizeColumnsToFit(); @@ -205,7 +201,7 @@ export const CustomAgGridTable: FunctionComponent = ({ const intl = useIntl(); const getLocaleText = useCallback( (params: any) => { - const key = 'agGrid.' + params.key; + const key = `agGrid.${params.key}`; return intl.messages[key] || params.defaultValue; }, [intl] @@ -238,8 +234,8 @@ export const CustomAgGridTable: FunctionComponent = ({ onGridReady={onGridReady} getLocaleText={getLocaleText} cacheOverflowSize={10} - rowSelection={'multiple'} - domLayout={'autoHeight'} + rowSelection="multiple" + domLayout="autoHeight" rowDragEntireRow rowDragManaged onRowDragEnd={(e) => @@ -247,8 +243,8 @@ export const CustomAgGridTable: FunctionComponent = ({ } suppressBrowserResizeObserver columnDefs={columnDefs} - detailRowAutoHeight={true} - onSelectionChanged={(event) => { + detailRowAutoHeight + onSelectionChanged={() => { setSelectedRows(gridApi.api.getSelectedRows()); }} onRowDataUpdated={ @@ -268,7 +264,7 @@ export const CustomAgGridTable: FunctionComponent = ({ stopEditingWhenCellsLoseFocus } {...props} - > + /> = ({ /> ); -}; +} export default CustomAgGridTable; diff --git a/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx b/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx index 9f99e3b7..65067d67 100644 --- a/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx +++ b/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx @@ -19,8 +19,7 @@ import { isFieldRequired, } from '../utils/functions'; import FieldLabel from '../utils/field-label'; -import { useCustomFormContext } from '../provider/use-custom-form-context'; -import { FunctionComponent } from 'react'; +import useCustomFormContext from '../provider/use-custom-form-context'; import { Option } from '../../../../utils/types'; export interface AutocompleteInputProps @@ -49,19 +48,19 @@ export interface AutocompleteInputProps >; } -const AutocompleteInput: FunctionComponent = ({ +function AutocompleteInput({ name, label, options, - outputTransform = identity, //transform materialUi input value before sending it to react hook form, mostly used to deal with select fields that need to return a string - inputTransform = identity, //transform react hook form value before sending it to materialUi input, mostly used to deal with select fields that need to return a string + outputTransform = identity, // transform materialUi input value before sending it to react hook form, mostly used to deal with select fields that need to return a string + inputTransform = identity, // transform react hook form value before sending it to materialUi input, mostly used to deal with select fields that need to return a string readOnly = false, previousValue, allowNewValue, onChangeCallback, // method called when input value is changing formProps, ...props -}) => { +}: Readonly) { const { validationSchema, getValues, removeOptional } = useCustomFormContext(); const { @@ -69,27 +68,27 @@ const AutocompleteInput: FunctionComponent = ({ fieldState: { error }, } = useController({ name }); - const handleChange = (value: Option) => { - onChangeCallback && onChangeCallback(); - //if free solo not enabled or if value is not of string type, we call onChange right away - if (!allowNewValue || typeof value !== 'string') { - onChange(outputTransform(value)); + const handleChange = (newValue: Option) => { + onChangeCallback?.(); + // if free solo not enabled or if value is not of string type, we call onChange right away + if (!allowNewValue || typeof newValue !== 'string') { + onChange(outputTransform(newValue)); return; } - //otherwise, we check if user input matches with one of the options + // otherwise, we check if user input matches with one of the options const matchingOption = options.find( (option: Option) => - typeof option !== 'string' && option.id === value + typeof option !== 'string' && option.id === newValue ); - //if it does, we send the matching option to react hook form + // if it does, we send the matching option to react hook form if (matchingOption) { onChange(outputTransform(matchingOption)); return; } - //otherwise, we send the user input - onChange(outputTransform(value)); + // otherwise, we send the user input + onChange(outputTransform(newValue)); }; return ( @@ -110,7 +109,7 @@ const AutocompleteInput: FunctionComponent = ({ = ({ }), })} inputRef={ref} - inputProps={{ ...inputProps, readOnly: readOnly }} + inputProps={{ ...inputProps, readOnly }} {...genHelperPreviousValue(previousValue!)} {...genHelperError(error?.message)} {...formProps} @@ -132,6 +131,6 @@ const AutocompleteInput: FunctionComponent = ({ {...props} /> ); -}; +} export default AutocompleteInput; diff --git a/src/components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input.tsx b/src/components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input.tsx index e002488f..b00d55cd 100644 --- a/src/components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input.tsx +++ b/src/components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input.tsx @@ -8,7 +8,7 @@ import { useState } from 'react'; import { useFieldArray, useWatch } from 'react-hook-form'; import AutocompleteInput from './autocomplete-input'; -const MultipleAutocompleteInput = ({ name, ...props }: any) => { +function MultipleAutocompleteInput({ name, ...props }: any) { const [unsavedInput, setUnsavedInput] = useState(''); const watchAutocompleteValues = useWatch({ name, @@ -40,7 +40,7 @@ const MultipleAutocompleteInput = ({ name, ...props }: any) => { options={[]} allowNewValue clearOnBlur - disableClearable={true} + disableClearable outputTransform={outputTransform} onInputChange={(_: unknown, val: string) => setUnsavedInput(val.trim() ?? '') @@ -52,6 +52,6 @@ const MultipleAutocompleteInput = ({ name, ...props }: any) => { {...props} /> ); -}; +} export default MultipleAutocompleteInput; diff --git a/src/components/inputs/react-hook-form/booleans/boolean-input.tsx b/src/components/inputs/react-hook-form/booleans/boolean-input.tsx index 30724e9e..bea80ac1 100644 --- a/src/components/inputs/react-hook-form/booleans/boolean-input.tsx +++ b/src/components/inputs/react-hook-form/booleans/boolean-input.tsx @@ -17,7 +17,12 @@ export interface BooleanInputProps { Input: typeof Switch | typeof Checkbox; } -const BooleanInput = ({ name, label, formProps, Input }: BooleanInputProps) => { +function BooleanInput({ + name, + label, + formProps, + Input, +}: Readonly) { const { field: { onChange, value, ref }, } = useController>({ name }); @@ -55,6 +60,6 @@ const BooleanInput = ({ name, label, formProps, Input }: BooleanInputProps) => { } return CustomInput; -}; +} export default BooleanInput; diff --git a/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx b/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx index 4e92a77c..87e81893 100644 --- a/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx +++ b/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import BooleanInput from './boolean-input'; import { Checkbox, CheckboxProps } from '@mui/material'; +import BooleanInput from './boolean-input'; export interface CheckboxInputProps { name: string; @@ -14,7 +14,11 @@ export interface CheckboxInputProps { formProps?: CheckboxProps; } -const CheckboxInput = ({ name, label, formProps }: CheckboxInputProps) => { +function CheckboxInput({ + name, + label, + formProps, +}: Readonly) { return ( { Input={Checkbox} /> ); -}; +} export default CheckboxInput; diff --git a/src/components/inputs/react-hook-form/booleans/switch-input.tsx b/src/components/inputs/react-hook-form/booleans/switch-input.tsx index cb3eec76..1943d212 100644 --- a/src/components/inputs/react-hook-form/booleans/switch-input.tsx +++ b/src/components/inputs/react-hook-form/booleans/switch-input.tsx @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import BooleanInput from './boolean-input'; import { Switch, SwitchProps } from '@mui/material'; +import BooleanInput from './boolean-input'; export interface SwitchInputProps { name: string; @@ -14,7 +14,7 @@ export interface SwitchInputProps { formProps?: SwitchProps; } -const SwitchInput = ({ name, label, formProps }: SwitchInputProps) => { +function SwitchInput({ name, label, formProps }: Readonly) { return ( { Input={Switch} /> ); -}; +} export default SwitchInput; diff --git a/src/components/inputs/react-hook-form/directory-items-input.tsx b/src/components/inputs/react-hook-form/directory-items-input.tsx index 14bdbe1e..7801855f 100644 --- a/src/components/inputs/react-hook-form/directory-items-input.tsx +++ b/src/components/inputs/react-hook-form/directory-items-input.tsx @@ -13,20 +13,20 @@ import { Theme, Tooltip, } from '@mui/material'; -import FieldLabel from './utils/field-label'; import FolderIcon from '@mui/icons-material/Folder'; -import { FunctionComponent, useCallback, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { useController, useFieldArray } from 'react-hook-form'; import { useIntl } from 'react-intl'; -import { RawReadOnlyInput } from './raw-read-only-input'; import { UUID } from 'crypto'; -import { useCustomFormContext } from './provider/use-custom-form-context'; +import RawReadOnlyInput from './raw-read-only-input'; +import FieldLabel from './utils/field-label'; +import useCustomFormContext from './provider/use-custom-form-context'; import { isFieldRequired } from './utils/functions'; import ErrorInput from './error-management/error-input'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { TreeViewFinderNodeProps } from '../../TreeViewFinder'; import { mergeSx } from '../../../utils/styles'; -import OverflowableText from '../../OverflowableText'; +import { OverflowableText } from '../../OverflowableText'; import MidFormError from './error-management/mid-form-error'; import DirectoryItemSelector from '../../DirectoryItemSelector/directory-item-selector'; import { fetchDirectoryElementPath } from '../../../services'; @@ -75,7 +75,7 @@ export interface DirectoryItemsInputProps { labelRequiredFromContext?: boolean; } -const DirectoryItemsInput: FunctionComponent = ({ +function DirectoryItemsInput({ label, name, elementType, // Used to specify type of element (Filter, Contingency list, ...) @@ -87,7 +87,7 @@ const DirectoryItemsInput: FunctionComponent = ({ onChange, disable = false, labelRequiredFromContext = true, -}) => { +}: Readonly) { const { snackError } = useSnackMessage(); const intl = useIntl(); const [selected, setSelected] = useState([]); @@ -143,8 +143,8 @@ const DirectoryItemsInput: FunctionComponent = ({ }); } else { append(otherElementAttributes); - onRowChanged && onRowChanged(true); - onChange && onChange(getValues(name)); + onRowChanged?.(true); + onChange?.(getValues(name)); } }); setDirectoryItemSelectorOpen(false); @@ -165,8 +165,8 @@ const DirectoryItemsInput: FunctionComponent = ({ const removeElements = useCallback( (index: number) => { remove(index); - onRowChanged && onRowChanged(true); - onChange && onChange(getValues(name)); + onRowChanged?.(true); + onChange?.(getValues(name)); }, [onRowChanged, remove, getValues, name, onChange] ); @@ -197,7 +197,7 @@ const DirectoryItemsInput: FunctionComponent = ({ = ({ { setDirectoryItemSelectorOpen(true); @@ -273,6 +273,6 @@ const DirectoryItemsInput: FunctionComponent = ({ /> ); -}; +} export default DirectoryItemsInput; diff --git a/src/components/inputs/react-hook-form/error-management/error-input.tsx b/src/components/inputs/react-hook-form/error-management/error-input.tsx index a6def880..efbc6784 100644 --- a/src/components/inputs/react-hook-form/error-management/error-input.tsx +++ b/src/components/inputs/react-hook-form/error-management/error-input.tsx @@ -5,12 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { - FunctionComponent, - MutableRefObject, - useEffect, - useRef, -} from 'react'; +import React, { MutableRefObject, useEffect, useRef } from 'react'; import { FormattedMessage } from 'react-intl'; import { useController } from 'react-hook-form'; @@ -30,10 +25,7 @@ export interface ErrorInputProps { }) => React.ReactNode; } -const ErrorInput: FunctionComponent = ({ - name, - InputField, -}) => { +function ErrorInput({ name, InputField }: Readonly) { const { fieldState: { error }, formState: { isSubmitting }, @@ -48,7 +40,8 @@ const ErrorInput: FunctionComponent = ({ return { id: errorMsg, }; - } else if (typeof errorMsg === 'object') { + } + if (typeof errorMsg === 'object') { return { id: errorMsg.id, values: { @@ -64,22 +57,21 @@ const ErrorInput: FunctionComponent = ({ if (error && errorRef.current) { errorRef.current.scrollIntoView({ behavior: 'smooth' }); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSubmitting]); - return ( - <> - {error?.message && ( -
- {InputField({ - message: ( - - ), - })} -
- )} - - ); -}; + if (error?.message) { + return ( +
+ {InputField({ + message: ( + + ), + })} +
+ ); + } + + return null; +} export default ErrorInput; diff --git a/src/components/inputs/react-hook-form/error-management/field-error-alert.tsx b/src/components/inputs/react-hook-form/error-management/field-error-alert.tsx index aa648b60..939b8e7d 100644 --- a/src/components/inputs/react-hook-form/error-management/field-error-alert.tsx +++ b/src/components/inputs/react-hook-form/error-management/field-error-alert.tsx @@ -6,7 +6,7 @@ */ import { Alert, Grid } from '@mui/material'; -import React, { FunctionComponent } from 'react'; +import React from 'react'; interface FieldErrorAlertProps { message: string | React.ReactNode; @@ -14,14 +14,12 @@ interface FieldErrorAlertProps { // component to display alert when a specific rhf field is in error // this component needs to be isolated to avoid too many rerenders -const FieldErrorAlert: FunctionComponent = ({ - message, -}) => { +function FieldErrorAlert({ message }: Readonly) { return ( {message} ); -}; +} export default FieldErrorAlert; diff --git a/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx b/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx index 6dc849c1..a16e8ccb 100644 --- a/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx +++ b/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx @@ -9,7 +9,7 @@ import { Box } from '@mui/material'; import { ReactNode } from 'react'; // component to display error message in the middle of dialog -const MidFormError = ({ message }: { message: string | ReactNode }) => { +function MidFormError({ message }: { message: string | ReactNode }) { return ( ({ @@ -22,6 +22,6 @@ const MidFormError = ({ message }: { message: string | ReactNode }) => { {message} ); -}; +} export default MidFormError; diff --git a/src/components/inputs/react-hook-form/numbers/float-input.tsx b/src/components/inputs/react-hook-form/numbers/float-input.tsx index a077f47b..58aacf14 100644 --- a/src/components/inputs/react-hook-form/numbers/float-input.tsx +++ b/src/components/inputs/react-hook-form/numbers/float-input.tsx @@ -7,7 +7,6 @@ import TextInput, { TextInputProps } from '../text-input'; import { isFloatNumber } from './utils'; -import { FunctionComponent } from 'react'; import { Input } from '../../../../utils/types'; export type FloatInputProps = Omit< @@ -38,21 +37,20 @@ const normalizeFixed = (number: number) => { }); }; -const FloatInput: FunctionComponent = ( - props: FloatInputProps -) => { +function FloatInput(props: Readonly) { const inputTransform = (value: Input) => { - if (typeof value == 'number' && !isNaN(value)) { + if (typeof value === 'number' && !Number.isNaN(value)) { // if we have a parsed real number, normalize like we do after each // keystroke in outputTransform for consistency. We get parsed // numbers when the data doesn't come from a user edit in the form, // but from data persisted as a float. return normalizeFixed(value); - //We explicitly test for 'string' type for code clarity, it enables + // We explicitly test for 'string' type for code clarity, it enables // use to avoid casting the value variable from 'number | string' // to 'string' on multiple statements - } else if (typeof value == 'string') { + } + if (typeof value === 'string') { // The user is editing, leave as is because we already did what we // need to do in outputTransform after the previous keystroke. // NOTE: To avoid "bad things" we haven't predicted and be extra @@ -108,7 +106,7 @@ const FloatInput: FunctionComponent = ( // restrict what the user can type with "acceptValue" but if we // have a bug just clear the data instead of sending "NaN" const parsed = parseFloat(tmp); - return isNaN(parsed) ? null : normalizeFixed(parsed); + return Number.isNaN(parsed) ? null : normalizeFixed(parsed); }; return ( @@ -119,6 +117,6 @@ const FloatInput: FunctionComponent = ( {...props} /> ); -}; +} export default FloatInput; diff --git a/src/components/inputs/react-hook-form/numbers/integer-input.tsx b/src/components/inputs/react-hook-form/numbers/integer-input.tsx index 7be3c387..1e74f581 100644 --- a/src/components/inputs/react-hook-form/numbers/integer-input.tsx +++ b/src/components/inputs/react-hook-form/numbers/integer-input.tsx @@ -7,12 +7,13 @@ import TextInput, { TextInputProps } from '../text-input'; import { isIntegerNumber } from './utils'; -const IntegerInput = (props: TextInputProps) => { +function IntegerInput(props: Readonly) { const inputTransform = (value: string | number | null) => { - if ('-' === value) { + if (value === '-') { return value; } - return value === null || (typeof value === 'number' && isNaN(value)) + return value === null || + (typeof value === 'number' && Number.isNaN(value)) ? '' : value.toString(); }; @@ -24,7 +25,7 @@ const IntegerInput = (props: TextInputProps) => { if (value === '0') { return 0; } - return parseInt(value) || null; + return parseInt(value, 10) || null; }; return ( @@ -35,6 +36,6 @@ const IntegerInput = (props: TextInputProps) => { {...props} /> ); -}; +} export default IntegerInput; diff --git a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx index 1c28e469..9eb31a66 100644 --- a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx +++ b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx @@ -27,7 +27,7 @@ export const CustomFormContext = createContext({ language: getSystemLanguage(), }); -const CustomFormProvider = (props: CustomFormProviderProps) => { +function CustomFormProvider(props: CustomFormProviderProps) { const { validationSchema, removeOptional, @@ -39,16 +39,24 @@ const CustomFormProvider = (props: CustomFormProviderProps) => { return ( ({ + validationSchema, + removeOptional, + language, + }), + [validationSchema, removeOptional, language] + )} > {children} ); +} + +CustomFormProvider.defaultProps = { + removeOptional: false, + language: undefined, }; export default CustomFormProvider; diff --git a/src/components/inputs/react-hook-form/provider/use-custom-form-context.ts b/src/components/inputs/react-hook-form/provider/use-custom-form-context.ts index 75a4abd2..75b6efcc 100644 --- a/src/components/inputs/react-hook-form/provider/use-custom-form-context.ts +++ b/src/components/inputs/react-hook-form/provider/use-custom-form-context.ts @@ -12,9 +12,11 @@ import { MergedFormContextProps, } from './custom-form-provider'; -export const useCustomFormContext = (): MergedFormContextProps => { +const useCustomFormContext = (): MergedFormContextProps => { const formMethods = useFormContext(); const customFormMethods = useContext(CustomFormContext); return { ...formMethods, ...customFormMethods }; }; + +export default useCustomFormContext; diff --git a/src/components/inputs/react-hook-form/radio-input.tsx b/src/components/inputs/react-hook-form/radio-input.tsx index 5ab5dbef..1f0ce3bf 100644 --- a/src/components/inputs/react-hook-form/radio-input.tsx +++ b/src/components/inputs/react-hook-form/radio-input.tsx @@ -30,13 +30,13 @@ interface RadioInputProps { formProps?: Omit; } -const RadioInput = ({ +function RadioInput({ name, label, id, options, formProps, -}: RadioInputProps) => { +}: Readonly) { const { field: { onChange, value }, } = useController({ name }); @@ -66,6 +66,6 @@ const RadioInput = ({
); -}; +} export default RadioInput; diff --git a/src/components/inputs/react-hook-form/range-input.tsx b/src/components/inputs/react-hook-form/range-input.tsx index e555577e..774fa8a5 100644 --- a/src/components/inputs/react-hook-form/range-input.tsx +++ b/src/components/inputs/react-hook-form/range-input.tsx @@ -5,15 +5,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { useWatch } from 'react-hook-form'; -import FloatInput from './numbers/float-input'; -import yup from '../../../utils/yup-config'; import { FormattedMessage } from 'react-intl'; -import { FunctionComponent, useMemo } from 'react'; +import { useMemo } from 'react'; import InputLabel from '@mui/material/InputLabel'; import { Grid } from '@mui/material'; import FormControl from '@mui/material/FormControl'; +import FloatInput from './numbers/float-input'; +import yup from '../../../utils/yup-config'; import MuiSelectInput from './select-inputs/mui-select-input'; -import { FieldConstants } from '../../../utils/field-constants'; +import FieldConstants from '../../../utils/field-constants'; const style = { inputLegend: (theme: any) => ({ @@ -72,7 +72,7 @@ interface RangeInputProps { label: string; } -const RangeInput: FunctionComponent = ({ name, label }) => { +function RangeInput({ name, label }: Readonly) { const watchOperationType = useWatch({ name: `${name}.${FieldConstants.OPERATION_TYPE}`, }); @@ -84,7 +84,7 @@ const RangeInput: FunctionComponent = ({ name, label }) => { const firstValueField = ( = ({ name, label }) => { = ({ name, label }) => { ); -}; +} export default RangeInput; diff --git a/src/components/inputs/react-hook-form/raw-read-only-input.ts b/src/components/inputs/react-hook-form/raw-read-only-input.ts index 0acfc3d2..dbfdad53 100644 --- a/src/components/inputs/react-hook-form/raw-read-only-input.ts +++ b/src/components/inputs/react-hook-form/raw-read-only-input.ts @@ -7,10 +7,12 @@ import { useController } from 'react-hook-form'; -export function RawReadOnlyInput({ name }: { name: string }) { +function RawReadOnlyInput({ name }: { name: string }) { const { field: { value }, } = useController({ name }); return value; } + +export default RawReadOnlyInput; diff --git a/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx b/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx index d8a2518a..836ae713 100644 --- a/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx @@ -4,11 +4,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback } from 'react'; +import { useCallback } from 'react'; import { Chip } from '@mui/material'; import AutocompleteInput from '../autocomplete-inputs/autocomplete-input'; import { useLocalizedCountries } from '../../../../hooks/localized-countries-hook'; -import { useCustomFormContext } from '../provider/use-custom-form-context'; +import useCustomFormContext from '../provider/use-custom-form-context'; import { Option } from '../../../../utils/types'; interface CountryInputProps { @@ -16,10 +16,7 @@ interface CountryInputProps { label: string; } -const CountriesInput: FunctionComponent = ({ - name, - label, -}) => { +function CountriesInput({ name, label }: Readonly) { const { language } = useCustomFormContext(); const { translate, countryCodes } = useLocalizedCountries(language); @@ -27,9 +24,8 @@ const CountriesInput: FunctionComponent = ({ (option: Option) => { if (typeof option === 'string') { return translate(option); - } else { - return translate(option.label); } + return translate(option.label); }, [translate] ); @@ -46,7 +42,7 @@ const CountriesInput: FunctionComponent = ({ val.map((code: string, index: number) => ( @@ -54,6 +50,6 @@ const CountriesInput: FunctionComponent = ({ } /> ); -}; +} export default CountriesInput; diff --git a/src/components/inputs/react-hook-form/select-inputs/input-with-popup-confirmation.tsx b/src/components/inputs/react-hook-form/select-inputs/input-with-popup-confirmation.tsx index 64536763..c221b39f 100644 --- a/src/components/inputs/react-hook-form/select-inputs/input-with-popup-confirmation.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/input-with-popup-confirmation.tsx @@ -8,7 +8,7 @@ import { useController } from 'react-hook-form'; import { useState } from 'react'; import PopupConfirmationDialog from '../../../dialogs/popup-confirmation-dialog'; -const InputWithPopupConfirmation = ({ +function InputWithPopupConfirmation({ Input, name, shouldOpenPopup, // condition to open popup confirmation @@ -16,7 +16,7 @@ const InputWithPopupConfirmation = ({ message, validateButtonLabel, ...props -}: any) => { +}: any) { const [newValue, setNewValue] = useState(null); const [openPopup, setOpenPopup] = useState(false); const { @@ -35,7 +35,9 @@ const InputWithPopupConfirmation = ({ }; const handlePopupConfirmation = () => { - resetOnConfirmation && resetOnConfirmation(); + if (resetOnConfirmation) { + resetOnConfirmation(); + } onChange(newValue); setOpenPopup(false); }; @@ -58,6 +60,6 @@ const InputWithPopupConfirmation = ({ /> ); -}; +} export default InputWithPopupConfirmation; diff --git a/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx b/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx index d0164f0a..23da98c5 100644 --- a/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx @@ -5,23 +5,20 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { MenuItem, Select, SelectProps } from '@mui/material'; import { useController } from 'react-hook-form'; -import { FunctionComponent } from 'react'; interface MuiSelectInputProps { name: string; options: { id: string; label: string }[]; } -// This input use Mui select instead of Autocomplete which can be needed some time (like in FormControl) -const MuiSelectInput: FunctionComponent = ({ +function MuiSelectInput({ name, options, ...props -}) => { +}: MuiSelectInputProps & SelectProps) { const { field: { value, onChange }, } = useController({ @@ -30,19 +27,16 @@ const MuiSelectInput: FunctionComponent = ({ return ( ); -}; - -MuiSelectInput.propTypes = { - name: PropTypes.string.isRequired, - label: PropTypes.string, - options: PropTypes.array.isRequired, -}; +} export default MuiSelectInput; diff --git a/src/components/inputs/react-hook-form/select-inputs/select-input.tsx b/src/components/inputs/react-hook-form/select-inputs/select-input.tsx index 297792be..c8ed7528 100644 --- a/src/components/inputs/react-hook-form/select-inputs/select-input.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/select-input.tsx @@ -5,11 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { useIntl } from 'react-intl'; import AutocompleteInput, { AutocompleteInputProps, } from '../autocomplete-inputs/autocomplete-input'; -import { useIntl } from 'react-intl'; -import { FunctionComponent } from 'react'; import { Option } from '../../../../utils/types'; export interface SelectInputProps @@ -20,23 +19,23 @@ export interface SelectInputProps options: Option[]; } -const SelectInput: FunctionComponent = (props) => { +function SelectInput(props: Readonly) { const intl = useIntl(); - + const { options } = props; const inputTransform = (value: Option | null) => { if (value === null) { return null; } if (typeof value === 'string') { return ( - props.options.find( + options.find( (option) => typeof option !== 'string' && option?.id === value ) || null ); } return ( - props.options.find( + options.find( (option) => typeof option !== 'string' && option?.id === value.id ) || null @@ -50,21 +49,25 @@ const SelectInput: FunctionComponent = (props) => { return value?.id ?? null; }; + const getOptionLabel = (option: Option) => { + if (typeof option === 'string') { + return option; + } + if (option.label) { + return intl.formatMessage({ id: option.label }); + } + return option.id; + }; + return ( { - return typeof option !== 'string' - ? option?.label - ? intl.formatMessage({ id: option?.label }) // If the option has a label property, display the label using internationalization - : option?.id // If the option doesn't have a label property, display the ID instead - : option; - }} + getOptionLabel={getOptionLabel} inputTransform={inputTransform} outputTransform={outputTransform} - readOnly={true} + readOnly {...props} /> ); -}; +} export default SelectInput; diff --git a/src/components/inputs/react-hook-form/slider-input.tsx b/src/components/inputs/react-hook-form/slider-input.tsx index ad83517b..b316f452 100644 --- a/src/components/inputs/react-hook-form/slider-input.tsx +++ b/src/components/inputs/react-hook-form/slider-input.tsx @@ -14,25 +14,21 @@ export interface SliderInputProps extends SliderProps { onValueChanged: (value: any) => void; } -const SliderInput = ({ +function SliderInput({ name, min, max, step, size = 'small', onValueChanged = identity, -}: SliderInputProps) => { +}: Readonly) { const { field: { onChange, value }, } = useController({ name }); - const handleValueChange = ( - event: Event, - value: number | number[], - activeThumb: number - ) => { - onValueChanged(value); - onChange(value); + const handleValueChange = (event: Event, newValue: number | number[]) => { + onValueChanged(newValue); + onChange(newValue); }; return ( @@ -45,6 +41,6 @@ const SliderInput = ({ onChange={handleValueChange} /> ); -}; +} export default SliderInput; diff --git a/src/components/inputs/react-hook-form/text-input.tsx b/src/components/inputs/react-hook-form/text-input.tsx index 6bce3144..d0f68cdc 100644 --- a/src/components/inputs/react-hook-form/text-input.tsx +++ b/src/components/inputs/react-hook-form/text-input.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, ReactElement } from 'react'; +import { ReactElement } from 'react'; import { IconButton, InputAdornment, @@ -24,7 +24,7 @@ import { identity, isFieldRequired, } from './utils/functions'; -import { useCustomFormContext } from './provider/use-custom-form-context'; +import useCustomFormContext from './provider/use-custom-form-context'; import { Input } from '../../../utils/types'; @@ -49,20 +49,20 @@ export interface TextInputProps { >; } -const TextInput: FunctionComponent = ({ +function TextInput({ name, label, labelValues, // this prop is used to add a value to label. this value is displayed without being translated id, adornment, customAdornment, - outputTransform = identity, //transform materialUi input value before sending it to react hook form, mostly used to deal with number fields - inputTransform = identity, //transform react hook form value before sending it to materialUi input, mostly used to deal with number fields - acceptValue = () => true, //used to check user entry before committing the input change, used mostly to prevent user from typing a character in number field + outputTransform = identity, // transform materialUi input value before sending it to react hook form, mostly used to deal with number fields + inputTransform = identity, // transform react hook form value before sending it to materialUi input, mostly used to deal with number fields + acceptValue = () => true, // used to check user entry before committing the input change, used mostly to prevent user from typing a character in number field previousValue, clearable, formProps, -}) => { +}: Readonly) { const { validationSchema, getValues, removeOptional } = useCustomFormContext(); const { @@ -101,10 +101,10 @@ const TextInput: FunctionComponent = ({ return ( = ({ inputRef={ref} {...(clearable && adornment && { - handleClearValue: handleClearValue, + handleClearValue, })} {...genHelperPreviousValue(previousValue!, adornment)} {...genHelperError(error?.message)} @@ -133,6 +133,6 @@ const TextInput: FunctionComponent = ({ {...finalAdornment} /> ); -}; +} export default TextInput; diff --git a/src/components/inputs/react-hook-form/unique-name-input.tsx b/src/components/inputs/react-hook-form/unique-name-input.tsx index c66d78ae..7ea823a0 100644 --- a/src/components/inputs/react-hook-form/unique-name-input.tsx +++ b/src/components/inputs/react-hook-form/unique-name-input.tsx @@ -5,18 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { ChangeEvent, FunctionComponent, useCallback, useEffect } from 'react'; -import { useDebounce } from '../../../hooks/useDebounce'; +import { ChangeEvent, useCallback, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; import { InputAdornment, TextFieldProps } from '@mui/material'; import CheckIcon from '@mui/icons-material/Check'; import { useController, useFormContext } from 'react-hook-form'; import CircularProgress from '@mui/material/CircularProgress'; import TextField from '@mui/material/TextField'; -import { FieldConstants } from '../../../utils/field-constants'; -import { ElementType } from '../../../utils/ElementType'; import { UUID } from 'crypto'; -import { elementExistsType } from '../../filter/criteria-based/criteria-based-filter-edition-dialog'; +import useDebounce from '../../../hooks/useDebounce'; +import FieldConstants from '../../../utils/field-constants'; +import { ElementExistsType, ElementType } from '../../../utils/ElementType'; interface UniqueNameInputProps { name: string; @@ -35,13 +34,13 @@ interface UniqueNameInputProps { | 'InputProps' >; activeDirectory?: UUID; - elementExists?: elementExistsType; + elementExists?: ElementExistsType; } /** * Input component that constantly check if the field's value is available or not */ -export const UniqueNameInput: FunctionComponent = ({ +function UniqueNameInput({ name, label, elementType, @@ -50,12 +49,12 @@ export const UniqueNameInput: FunctionComponent = ({ formProps, activeDirectory, elementExists, -}) => { +}: Readonly) { const { field: { onChange, onBlur, value, ref }, fieldState: { error, isDirty }, } = useController({ - name: name, + name, }); const { @@ -76,28 +75,27 @@ export const UniqueNameInput: FunctionComponent = ({ const directory = selectedDirectory || activeDirectory; const handleCheckName = useCallback( - (value: string) => { - if (value) { - elementExists && - elementExists(directory, value, elementType) - .then((alreadyExist) => { - if (alreadyExist) { - setError(name, { - type: 'validate', - message: 'nameAlreadyUsed', - }); - } - }) - .catch((error) => { + (nameValue: string) => { + if (nameValue) { + elementExists?.(directory, nameValue, elementType) + .then((alreadyExist) => { + if (alreadyExist) { setError(name, { type: 'validate', - message: 'nameValidityCheckErrorMsg', + message: 'nameAlreadyUsed', }); - console.error(error?.message); - }) - .finally(() => { - clearErrors('root.isValidating'); + } + }) + .catch((e) => { + setError(name, { + type: 'validate', + message: 'nameValidityCheckErrorMsg', }); + console.error(e?.message); + }) + .finally(() => { + clearErrors('root.isValidating'); + }); } }, [setError, clearErrors, name, elementType, elementExists, directory] @@ -147,7 +145,9 @@ export const UniqueNameInput: FunctionComponent = ({ e: ChangeEvent ) => { onChange(e.target.value); - onManualChangeCallback && onManualChangeCallback(); + if (onManualChangeCallback) { + onManualChangeCallback(); + } }; const translatedLabel = ; @@ -176,8 +176,10 @@ export const UniqueNameInput: FunctionComponent = ({ fullWidth error={!!error} helperText={translatedError} - InputProps={{ endAdornment: endAdornment }} + InputProps={{ endAdornment }} {...formProps} /> ); -}; +} + +export default UniqueNameInput; diff --git a/src/components/inputs/react-hook-form/utils/cancel-button.tsx b/src/components/inputs/react-hook-form/utils/cancel-button.tsx index 44048e2d..f41fc272 100644 --- a/src/components/inputs/react-hook-form/utils/cancel-button.tsx +++ b/src/components/inputs/react-hook-form/utils/cancel-button.tsx @@ -7,20 +7,15 @@ import { Button } from '@mui/material'; import { FormattedMessage } from 'react-intl'; -import PropTypes from 'prop-types'; import { useThemeProps } from '@mui/material/styles'; -const CancelButton = ({ ...inProps }) => { +function CancelButton({ ...inProps }) { const props = useThemeProps({ props: inProps, name: 'CancelButton' }); return ( ); -}; - -CancelButton.propTypes = { - buttonProps: PropTypes.object, -}; +} export default CancelButton; diff --git a/src/components/inputs/react-hook-form/utils/field-label.tsx b/src/components/inputs/react-hook-form/utils/field-label.tsx index 69daf2bf..749b4cad 100644 --- a/src/components/inputs/react-hook-form/utils/field-label.tsx +++ b/src/components/inputs/react-hook-form/utils/field-label.tsx @@ -12,17 +12,22 @@ type FieldLabelProps = { optional?: boolean; values?: any; }; -const FieldLabel = ({ +function FieldLabel({ label, optional = false, values = undefined, -}: FieldLabelProps) => { +}: Readonly) { return ( <> {optional && } ); +} + +FieldLabel.defaultProps = { + optional: false, + values: undefined, }; export default FieldLabel; diff --git a/src/components/inputs/react-hook-form/utils/functions.tsx b/src/components/inputs/react-hook-form/utils/functions.tsx index afd801ec..77cf5b32 100644 --- a/src/components/inputs/react-hook-form/utils/functions.tsx +++ b/src/components/inputs/react-hook-form/utils/functions.tsx @@ -17,7 +17,7 @@ export function genHelperPreviousValue( ...((previousValue || previousValue === 0) && { error: false, helperText: - previousValue + (adornment ? ' ' + adornment?.text : ''), + previousValue + (adornment ? ` ${adornment?.text}` : ''), }), }; } @@ -49,13 +49,13 @@ export const isFieldRequired = ( ?.optional === false ); - //static way, not working when using "when" in schema, but does not need form values - //return yup.reach(schema, fieldName)?.exclusiveTests?.required === true; + // static way, not working when using "when" in schema, but does not need form values + // return yup.reach(schema, fieldName)?.exclusiveTests?.required === true; }; export const gridItem = (field: string | ReactElement, size: number = 6) => { return ( - + {field} ); diff --git a/src/components/inputs/react-hook-form/utils/submit-button.tsx b/src/components/inputs/react-hook-form/utils/submit-button.tsx index 2c92f25c..4b38fcaf 100644 --- a/src/components/inputs/react-hook-form/utils/submit-button.tsx +++ b/src/components/inputs/react-hook-form/utils/submit-button.tsx @@ -8,9 +8,8 @@ import { Button } from '@mui/material'; import { useFormState } from 'react-hook-form'; import { FormattedMessage } from 'react-intl'; -import PropTypes from 'prop-types'; -const SubmitButton = ({ ...buttonProps }) => { +function SubmitButton({ ...buttonProps }) { const { isDirty } = useFormState(); return ( @@ -21,10 +20,6 @@ const SubmitButton = ({ ...buttonProps }) => { ); -}; - -SubmitButton.propTypes = { - buttonProps: PropTypes.object, -}; +} export default SubmitButton; diff --git a/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx b/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx index 8d0e1373..c350b0bb 100644 --- a/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx +++ b/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useState } from 'react'; +import { useCallback, useState } from 'react'; import { Clear as ClearIcon } from '@mui/icons-material'; import { IconButton, @@ -24,9 +24,7 @@ export type TextFieldWithAdornmentProps = TextFieldProps & { handleClearValue?: () => void; }; -const TextFieldWithAdornment: FunctionComponent = ( - props -) => { +function TextFieldWithAdornment(props: TextFieldWithAdornmentProps) { const { adornmentPosition, adornmentText, @@ -39,14 +37,14 @@ const TextFieldWithAdornment: FunctionComponent = ( const [isFocused, setIsFocused] = useState(false); const getAdornmentStyle = useCallback( - (variant: 'standard' | 'filled' | 'outlined') => { - if (variant === 'filled') { + (innerVariant: 'standard' | 'filled' | 'outlined') => { + if (innerVariant === 'filled') { return { alignItems: 'start', marginBottom: '0.4em', }; } - if (variant === 'standard') { + if (innerVariant === 'standard') { return { marginBottom: '0.3em', }; @@ -121,7 +119,7 @@ const TextFieldWithAdornment: FunctionComponent = ( return ( = ( ? withStartAdornmentText() : withEndAdornmentText() } - onFocus={(e) => setIsFocused(true)} - onBlur={(e) => setIsFocused(false)} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} /> ); +} + +TextFieldWithAdornment.defaultProps = { + handleClearValue: undefined, }; export default TextFieldWithAdornment; diff --git a/src/components/inputs/react-query-builder/add-button.tsx b/src/components/inputs/react-query-builder/add-button.tsx index 549c8e42..8dd35ac8 100644 --- a/src/components/inputs/react-query-builder/add-button.tsx +++ b/src/components/inputs/react-query-builder/add-button.tsx @@ -9,26 +9,25 @@ import { ActionWithRulesAndAddersProps } from 'react-querybuilder'; import { Button } from '@mui/material'; import AddIcon from '@mui/icons-material/ControlPoint'; import { FormattedMessage } from 'react-intl'; -import React, { FunctionComponent } from 'react'; interface ActionWithRulesAndAddersWithLabelProps extends ActionWithRulesAndAddersProps { label: string; } -const AddButton: FunctionComponent = ( - props -) => ( - - - -); - +function AddButton(props: Readonly) { + const { label, handleOnClick } = props; + return ( + + + + ); +} export default AddButton; diff --git a/src/components/inputs/react-query-builder/combinator-selector.tsx b/src/components/inputs/react-query-builder/combinator-selector.tsx index 9bc2bf95..75bc5332 100644 --- a/src/components/inputs/react-query-builder/combinator-selector.tsx +++ b/src/components/inputs/react-query-builder/combinator-selector.tsx @@ -6,26 +6,25 @@ */ import { CombinatorSelectorProps } from 'react-querybuilder'; -import { FunctionComponent, useCallback, useState } from 'react'; -import PopupConfirmationDialog from '../../dialogs/popup-confirmation-dialog'; +import { useCallback, useState } from 'react'; import { MaterialValueSelector } from '@react-querybuilder/material'; +import PopupConfirmationDialog from '../../dialogs/popup-confirmation-dialog'; -const CombinatorSelector: FunctionComponent = ( - props -) => { - const [tempCombinator, setTempCombinator] = useState(props.value); +function CombinatorSelector(props: Readonly) { + const { value, handleOnChange } = props; + const [tempCombinator, setTempCombinator] = useState(value); const [openPopup, setOpenPopup] = useState(false); const handlePopupConfirmation = useCallback(() => { - props.handleOnChange(tempCombinator); + handleOnChange(tempCombinator); setOpenPopup(false); }, [props, tempCombinator]); return ( <> = ( /> ); -}; +} export default CombinatorSelector; diff --git a/src/components/inputs/react-query-builder/country-value-editor.tsx b/src/components/inputs/react-query-builder/country-value-editor.tsx index 21c6dba8..d1d97bc9 100644 --- a/src/components/inputs/react-query-builder/country-value-editor.tsx +++ b/src/components/inputs/react-query-builder/country-value-editor.tsx @@ -8,15 +8,16 @@ import { ValueEditorProps } from 'react-querybuilder'; import { MaterialValueEditor } from '@react-querybuilder/material'; import { Autocomplete, TextField } from '@mui/material'; +import { useMemo } from 'react'; import useConvertValue from './use-convert-value'; import useValid from './use-valid'; import { useLocalizedCountries } from '../../../hooks/localized-countries-hook'; -import { useCustomFormContext } from '../react-hook-form/provider/use-custom-form-context'; -import { FunctionComponent, useMemo } from 'react'; +import useCustomFormContext from '../react-hook-form/provider/use-custom-form-context'; -const CountryValueEditor: FunctionComponent = (props) => { +function CountryValueEditor(props: Readonly) { const { language } = useCustomFormContext(); const { translate, countryCodes } = useLocalizedCountries(language); + const { value, handleOnChange } = props; const countriesList = useMemo( () => @@ -31,7 +32,7 @@ const CountryValueEditor: FunctionComponent = (props) => { const valid = useValid(props); // The displayed component totally depends on the value type and not the operator. This way, we have smoother transition. - if (!Array.isArray(props.value)) { + if (!Array.isArray(value)) { return ( = (props) => { title={undefined} // disable the tooltip /> ); - } else { - return ( - translate(code)} - onChange={(event, value: any) => props.handleOnChange(value)} - multiple - fullWidth - renderInput={(params) => ( - - )} - /> - ); } -}; + return ( + translate(code)} + onChange={(event, newValue: any) => handleOnChange(newValue)} + multiple + fullWidth + renderInput={(params) => } + /> + ); +} export default CountryValueEditor; diff --git a/src/components/inputs/react-query-builder/custom-react-query-builder.tsx b/src/components/inputs/react-query-builder/custom-react-query-builder.tsx index a0d15223..71351063 100644 --- a/src/components/inputs/react-query-builder/custom-react-query-builder.tsx +++ b/src/components/inputs/react-query-builder/custom-react-query-builder.tsx @@ -18,12 +18,11 @@ import { } from 'react-querybuilder'; import { useIntl } from 'react-intl'; import { useFormContext } from 'react-hook-form'; -import RemoveButton from './remove-button'; +import { useCallback, useMemo } from 'react'; import CombinatorSelector from './combinator-selector'; import AddButton from './add-button'; import ValueEditor from './value-editor'; import ValueSelector from './value-selector'; -import { useCallback, useMemo } from 'react'; import { COMBINATOR_OPTIONS } from '../../filter/expert/expert-filter-constants'; import ErrorInput from '../react-hook-form/error-management/error-input'; @@ -33,13 +32,25 @@ import { getOperators, queryValidator, } from '../../filter/expert/expert-filter-utils'; +import RemoveButton from './remove-button'; interface CustomReactQueryBuilderProps { name: string; fields: Field[]; } -const CustomReactQueryBuilder = (props: CustomReactQueryBuilderProps) => { +function RuleAddButton(props: any) { + return ; +} + +function GroupAddButton(props: any) { + return ; +} + +function CustomReactQueryBuilder( + props: Readonly +) { + const { name, fields } = props; const { getValues, setValue, @@ -48,25 +59,25 @@ const CustomReactQueryBuilder = (props: CustomReactQueryBuilderProps) => { } = useFormContext(); const intl = useIntl(); - const query = watch(props.name); + const query = watch(name); // Ideally we should "clean" the empty groups after DnD as we do for the remove button // But it's the only callback we have access to in this case, // and we don't have access to the path, so it can't be done in a proper way const handleQueryChange = useCallback( (newQuery: RuleGroupTypeAny) => { - const oldQuery = getValues(props.name); + const oldQuery = getValues(name); const hasQueryChanged = formatQuery(oldQuery, 'json_without_ids') !== formatQuery(newQuery, 'json_without_ids'); const hasAddedRules = countRules(newQuery) > countRules(oldQuery); - setValue(props.name, newQuery, { + setValue(name, newQuery, { shouldDirty: hasQueryChanged, shouldValidate: isSubmitted && hasQueryChanged && !hasAddedRules, }); }, - [getValues, setValue, isSubmitted, props.name] + [getValues, setValue, isSubmitted, name] ); const combinators = useMemo(() => { @@ -84,9 +95,9 @@ const CustomReactQueryBuilder = (props: CustomReactQueryBuilderProps) => { dnd={{ ...ReactDnD, ...ReactDndHtml5Backend }} > @@ -97,12 +108,8 @@ const CustomReactQueryBuilder = (props: CustomReactQueryBuilderProps) => { queryBuilder: 'queryBuilder-branches', }} controlElements={{ - addRuleAction: (props) => ( - - ), - addGroupAction: (props) => ( - - ), + addRuleAction: RuleAddButton, + addGroupAction: GroupAddButton, combinatorSelector: CombinatorSelector, removeRuleAction: RemoveButton, removeGroupAction: RemoveButton, @@ -117,10 +124,10 @@ const CustomReactQueryBuilder = (props: CustomReactQueryBuilderProps) => { - + ); -}; +} export default CustomReactQueryBuilder; diff --git a/src/components/inputs/react-query-builder/element-value-editor.tsx b/src/components/inputs/react-query-builder/element-value-editor.tsx index a83e27a1..9299974b 100644 --- a/src/components/inputs/react-query-builder/element-value-editor.tsx +++ b/src/components/inputs/react-query-builder/element-value-editor.tsx @@ -5,13 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { FunctionComponent, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { validate as uuidValidate } from 'uuid'; -import { - DirectoryItemsInput, - fetchElementsInfos, - useCustomFormContext, -} from '../../../index'; +import useCustomFormContext from '../react-hook-form/provider/use-custom-form-context'; +import { fetchElementsInfos } from '../../../services'; +import DirectoryItemsInput from '../react-hook-form/directory-items-input'; interface ElementValueEditorProps { name: string; @@ -24,48 +22,54 @@ interface ElementValueEditorProps { defaultValue?: any; } -const ElementValueEditor: FunctionComponent = ( - props -) => { +function ElementValueEditor(props: Readonly) { + const { + defaultValue, + name, + elementType, + equipmentTypes, + titleId, + hideErrorMessage, + itemFilter, + onChange, + } = props; const { setValue } = useCustomFormContext(); useEffect(() => { if ( - props.defaultValue && - Array.isArray(props.defaultValue) && - props.defaultValue.length > 0 && - props.defaultValue[0].length > 0 && - uuidValidate(props.defaultValue[0]) + defaultValue && + Array.isArray(defaultValue) && + defaultValue.length > 0 && + defaultValue[0].length > 0 && + uuidValidate(defaultValue[0]) ) { - fetchElementsInfos(props.defaultValue).then( - (childrenWithMetadata) => { - setValue( - props.name, - childrenWithMetadata.map((v: any) => { - return { - id: v.elementUuid, - name: v.elementName, - specificMetadata: v.specificMetadata, - }; - }) - ); - } - ); + fetchElementsInfos(defaultValue).then((childrenWithMetadata) => { + setValue( + name, + childrenWithMetadata.map((v: any) => { + return { + id: v.elementUuid, + name: v.elementName, + specificMetadata: v.specificMetadata, + }; + }) + ); + }); } - }, [props.name, props.defaultValue, props.elementType, setValue]); + }, [name, defaultValue, elementType, setValue]); return ( + /> ); -}; +} export default ElementValueEditor; diff --git a/src/components/inputs/react-query-builder/property-value-editor.tsx b/src/components/inputs/react-query-builder/property-value-editor.tsx index 0f87d1a7..4c9b1e59 100644 --- a/src/components/inputs/react-query-builder/property-value-editor.tsx +++ b/src/components/inputs/react-query-builder/property-value-editor.tsx @@ -5,16 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FunctionComponent, useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import Grid from '@mui/material/Grid'; import { Autocomplete, MenuItem, Select, TextField } from '@mui/material'; import { ValueEditorProps } from 'react-querybuilder'; -import useValid from './use-valid'; import { useIntl } from 'react-intl'; +import useValid from './use-valid'; -import { FieldConstants } from '../../../utils/field-constants'; import { OPERATOR_OPTIONS } from '../../filter/expert/expert-filter-constants'; -import { usePredefinedProperties } from '../../../hooks/predefined-properties-hook'; +import FieldConstants from '../../../utils/field-constants'; +import usePredefinedProperties from '../../../hooks/predefined-properties-hook'; const PROPERTY_VALUE_OPERATORS = [OPERATOR_OPTIONS.IN]; @@ -23,9 +23,7 @@ interface ExpertFilterPropertyProps { valueEditorProps: ValueEditorProps; } -const PropertyValueEditor: FunctionComponent = ( - props -) => { +function PropertyValueEditor(props: Readonly) { const { equipmentType, valueEditorProps } = props; const valid = useValid(valueEditorProps); const intl = useIntl(); @@ -98,7 +96,7 @@ const PropertyValueEditor: FunctionComponent = ( propertyOperator ?? PROPERTY_VALUE_OPERATORS[0].customName } - size={'medium'} + size="medium" error={!valid} onChange={(event, value: any) => { onChange(FieldConstants.PROPERTY_OPERATOR, value); @@ -131,6 +129,6 @@ const PropertyValueEditor: FunctionComponent = ( ); -}; +} export default PropertyValueEditor; diff --git a/src/components/inputs/react-query-builder/remove-button.tsx b/src/components/inputs/react-query-builder/remove-button.tsx index c52903d3..2d01f2aa 100644 --- a/src/components/inputs/react-query-builder/remove-button.tsx +++ b/src/components/inputs/react-query-builder/remove-button.tsx @@ -9,37 +9,39 @@ import { ActionWithRulesProps } from 'react-querybuilder'; import IconButton from '@mui/material/IconButton'; import DeleteIcon from '@mui/icons-material/Delete'; import { useController } from 'react-hook-form'; -import { EXPERT_FILTER_QUERY } from '../../filter/expert/expert-filter-form'; + import { getNumberOfSiblings, recursiveRemove, } from '../../filter/expert/expert-filter-utils'; -import { FunctionComponent } from 'react'; -const RemoveButton: FunctionComponent = (props) => { +const EXPERT_FILTER_QUERY = 'rules'; + +function RemoveButton(props: Readonly) { + const { path, className } = props; const { field: { value: query, onChange }, } = useController({ name: EXPERT_FILTER_QUERY }); - function handleDelete(e: React.MouseEvent) { + function handleDelete() { // We don't want groups with no rules // So if we have only empty subgroups above the removed rule, we want to remove all of them - onChange(recursiveRemove(query, props.path)); + onChange(recursiveRemove(query, path)); } const isLastRuleOrGroup = - props.path.toString() === [0].toString() && - getNumberOfSiblings(props.path, query) === 1; + path.toString() === [0].toString() && + getNumberOfSiblings(path, query) === 1; return ( handleDelete()} + className={className} > {!isLastRuleOrGroup && } ); -}; +} export default RemoveButton; diff --git a/src/components/inputs/react-query-builder/text-value-editor.tsx b/src/components/inputs/react-query-builder/text-value-editor.tsx index 805151f3..67b9dd0f 100644 --- a/src/components/inputs/react-query-builder/text-value-editor.tsx +++ b/src/components/inputs/react-query-builder/text-value-editor.tsx @@ -7,38 +7,35 @@ import { ValueEditorProps } from 'react-querybuilder'; import { MaterialValueEditor } from '@react-querybuilder/material'; -import useConvertValue from './use-convert-value'; import { Autocomplete, TextField } from '@mui/material'; +import useConvertValue from './use-convert-value'; import useValid from './use-valid'; -import { FunctionComponent } from 'react'; -const TextValueEditor: FunctionComponent = (props) => { +function TextValueEditor(props: Readonly) { useConvertValue(props); const valid = useValid(props); + const { value, handleOnChange } = props; // The displayed component totally depends on the value type and not the operator. This way, we have smoother transition. - if (!Array.isArray(props.value)) { + if (!Array.isArray(value)) { return ( ); - } else { - return ( - props.handleOnChange(value)} - multiple - fullWidth - renderInput={(params) => ( - - )} - /> - ); } -}; + return ( + handleOnChange(newValue)} + multiple + fullWidth + renderInput={(params) => } + /> + ); +} export default TextValueEditor; diff --git a/src/components/inputs/react-query-builder/translated-value-editor.tsx b/src/components/inputs/react-query-builder/translated-value-editor.tsx index 098062d3..edfb4564 100644 --- a/src/components/inputs/react-query-builder/translated-value-editor.tsx +++ b/src/components/inputs/react-query-builder/translated-value-editor.tsx @@ -6,43 +6,41 @@ */ import { ValueEditorProps } from 'react-querybuilder'; -import { FunctionComponent, useMemo } from 'react'; +import { useMemo } from 'react'; import { MaterialValueEditor } from '@react-querybuilder/material'; import { useIntl } from 'react-intl'; -import useConvertValue from './use-convert-value'; import { Autocomplete, TextField } from '@mui/material'; +import useConvertValue from './use-convert-value'; import useValid from './use-valid'; -const TranslatedValueEditor: FunctionComponent = (props) => { +function TranslatedValueEditor(props: Readonly) { const intl = useIntl(); + const { values, value, handleOnChange } = props; const translatedValues = useMemo(() => { - return props.values?.map((v) => { + return values?.map((v) => { return { name: v.name, label: intl.formatMessage({ id: v.label }), }; }); - }, [intl, props.values]); + }, [intl, values]); const translatedValuesAutocomplete = useMemo(() => { - if (!props.values) { + if (!values) { return {}; } return Object.fromEntries( - props.values.map((v) => [ - v.name, - intl.formatMessage({ id: v.label }), - ]) + values.map((v) => [v.name, intl.formatMessage({ id: v.label })]) ); - }, [intl, props.values]); + }, [intl, values]); useConvertValue(props); const valid = useValid(props); // The displayed component totally depends on the value type and not the operator. This way, we have smoother transition. - if (!Array.isArray(props.value)) { + if (!Array.isArray(value)) { return ( = (props) => { title={undefined} // disable the tooltip /> ); - } else { - return ( - - translatedValuesAutocomplete[code] - } - onChange={(event, value: any) => props.handleOnChange(value)} - multiple - fullWidth - renderInput={(params) => ( - - )} - /> - ); } -}; + return ( + + translatedValuesAutocomplete[code] + } + onChange={(event, newValue: any) => handleOnChange(newValue)} + multiple + fullWidth + renderInput={(params) => } + /> + ); +} export default TranslatedValueEditor; diff --git a/src/components/inputs/react-query-builder/use-convert-value.ts b/src/components/inputs/react-query-builder/use-convert-value.ts index d8cc1892..1af875a4 100644 --- a/src/components/inputs/react-query-builder/use-convert-value.ts +++ b/src/components/inputs/react-query-builder/use-convert-value.ts @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { useEffect } from 'react'; -import { OPERATOR_OPTIONS } from '../../filter/expert/expert-filter-constants'; import { ValueEditorProps } from 'react-querybuilder'; +import { OPERATOR_OPTIONS } from '../../filter/expert/expert-filter-constants'; /** * Hook that convert a value of RQB from any to any[] and vice versa when the operator changes diff --git a/src/components/inputs/react-query-builder/value-editor.tsx b/src/components/inputs/react-query-builder/value-editor.tsx index 692cad22..08a7266b 100644 --- a/src/components/inputs/react-query-builder/value-editor.tsx +++ b/src/components/inputs/react-query-builder/value-editor.tsx @@ -6,16 +6,14 @@ */ import { ValueEditorProps } from 'react-querybuilder'; -import React, { FunctionComponent, useCallback } from 'react'; +import React, { useCallback } from 'react'; import { MaterialValueEditor } from '@react-querybuilder/material'; - +import Box from '@mui/material/Box'; +import { useFormContext } from 'react-hook-form'; import CountryValueEditor from './country-value-editor'; import TranslatedValueEditor from './translated-value-editor'; import TextValueEditor from './text-value-editor'; -import Box from '@mui/material/Box'; -import { useFormContext } from 'react-hook-form'; -import { FieldConstants } from '../../../utils/field-constants'; import { DataType, FieldType, @@ -26,6 +24,7 @@ import ElementValueEditor from './element-value-editor'; import { ElementType } from '../../../utils/ElementType'; import PropertyValueEditor from './property-value-editor'; import { FilterType } from '../../filter/constants/filter-constants'; +import FieldConstants from '../../../utils/field-constants'; const styles = { noArrows: { @@ -39,112 +38,116 @@ const styles = { }, }; -const ValueEditor: FunctionComponent = (props) => { +function ValueEditor(props: Readonly) { + const { field, operator, value } = props; const formContext = useFormContext(); const { getValues } = formContext; - + const { rule, handleOnChange, inputType } = props; const itemFilter = useCallback( - (value: any) => { - if (value?.type === ElementType.FILTER) { + (filterValue: any) => { + if (filterValue?.type === ElementType.FILTER) { return ( // we do not authorize to use an expert filter in the rules of // another expert filter, to prevent potential cycle problems - value?.specificMetadata?.type !== FilterType.EXPERT.id && - ((props.field === FieldType.ID && - value?.specificMetadata?.equipmentType === + filterValue?.specificMetadata?.type !== + FilterType.EXPERT.id && + ((field === FieldType.ID && + filterValue?.specificMetadata?.equipmentType === getValues(FieldConstants.EQUIPMENT_TYPE)) || - ((props.field === FieldType.VOLTAGE_LEVEL_ID || - props.field === FieldType.VOLTAGE_LEVEL_ID_1 || - props.field === FieldType.VOLTAGE_LEVEL_ID_2) && - value?.specificMetadata?.equipmentType === + ((field === FieldType.VOLTAGE_LEVEL_ID || + field === FieldType.VOLTAGE_LEVEL_ID_1 || + field === FieldType.VOLTAGE_LEVEL_ID_2) && + filterValue?.specificMetadata?.equipmentType === VoltageLevel.type)) ); } return true; }, - [props.field, getValues] + [field, getValues] ); if ( - props.operator === OperatorType.EXISTS || - props.operator === OperatorType.NOT_EXISTS + operator === OperatorType.EXISTS || + operator === OperatorType.NOT_EXISTS ) { // No value needed for these operators return null; } if ( [FieldType.COUNTRY, FieldType.COUNTRY_1, FieldType.COUNTRY_2].includes( - props.field as FieldType + field as FieldType ) ) { return ; } if ( - props.field === FieldType.ENERGY_SOURCE || - props.field === FieldType.SHUNT_COMPENSATOR_TYPE || - props.field === FieldType.LOAD_TYPE || - props.field === FieldType.RATIO_REGULATION_MODE || - props.field === FieldType.PHASE_REGULATION_MODE + field === FieldType.ENERGY_SOURCE || + field === FieldType.SHUNT_COMPENSATOR_TYPE || + field === FieldType.LOAD_TYPE || + field === FieldType.RATIO_REGULATION_MODE || + field === FieldType.PHASE_REGULATION_MODE ) { return ; } if ( - props.operator === OperatorType.IS_PART_OF || - props.operator === OperatorType.IS_NOT_PART_OF + operator === OperatorType.IS_PART_OF || + operator === OperatorType.IS_NOT_PART_OF ) { let equipmentTypes; if ( - props.field === FieldType.VOLTAGE_LEVEL_ID || - props.field === FieldType.VOLTAGE_LEVEL_ID_1 || - props.field === FieldType.VOLTAGE_LEVEL_ID_2 + field === FieldType.VOLTAGE_LEVEL_ID || + field === FieldType.VOLTAGE_LEVEL_ID_1 || + field === FieldType.VOLTAGE_LEVEL_ID_2 ) { equipmentTypes = [VoltageLevel.type]; - } else if (props.field === FieldType.ID) { + } else if (field === FieldType.ID) { equipmentTypes = [getValues(FieldConstants.EQUIPMENT_TYPE)]; } return ( { - props.handleOnChange(e.map((v: any) => v.id)); + handleOnChange(e.map((v: any) => v.id)); }} itemFilter={itemFilter} - defaultValue={props.value} + defaultValue={value} /> ); - } else if ( - props.field === FieldType.ID || - props.field === FieldType.NAME || - props.field === FieldType.VOLTAGE_LEVEL_ID || - props.field === FieldType.VOLTAGE_LEVEL_ID_1 || - props.field === FieldType.VOLTAGE_LEVEL_ID_2 + } + if ( + field === FieldType.ID || + field === FieldType.NAME || + field === FieldType.VOLTAGE_LEVEL_ID || + field === FieldType.VOLTAGE_LEVEL_ID_1 || + field === FieldType.VOLTAGE_LEVEL_ID_2 ) { return ; - } else if ( - props.field === FieldType.PROPERTY || - props.field === FieldType.SUBSTATION_PROPERTY || - props.field === FieldType.SUBSTATION_PROPERTY_1 || - props.field === FieldType.SUBSTATION_PROPERTY_2 || - props.field === FieldType.VOLTAGE_LEVEL_PROPERTY || - props.field === FieldType.VOLTAGE_LEVEL_PROPERTY_1 || - props.field === FieldType.VOLTAGE_LEVEL_PROPERTY_2 + } + if ( + field === FieldType.PROPERTY || + field === FieldType.SUBSTATION_PROPERTY || + field === FieldType.SUBSTATION_PROPERTY_1 || + field === FieldType.SUBSTATION_PROPERTY_2 || + field === FieldType.VOLTAGE_LEVEL_PROPERTY || + field === FieldType.VOLTAGE_LEVEL_PROPERTY_1 || + field === FieldType.VOLTAGE_LEVEL_PROPERTY_2 ) { let equipmentType; if ( - props.field === FieldType.SUBSTATION_PROPERTY || - props.field === FieldType.SUBSTATION_PROPERTY_1 || - props.field === FieldType.SUBSTATION_PROPERTY_2 + field === FieldType.SUBSTATION_PROPERTY || + field === FieldType.SUBSTATION_PROPERTY_1 || + field === FieldType.SUBSTATION_PROPERTY_2 ) { equipmentType = Substation.type; } else if ( - props.field === FieldType.VOLTAGE_LEVEL_PROPERTY || - props.field === FieldType.VOLTAGE_LEVEL_PROPERTY_1 || - props.field === FieldType.VOLTAGE_LEVEL_PROPERTY_2 + field === FieldType.VOLTAGE_LEVEL_PROPERTY || + field === FieldType.VOLTAGE_LEVEL_PROPERTY_1 || + field === FieldType.VOLTAGE_LEVEL_PROPERTY_2 ) { equipmentType = VoltageLevel.type; } else { @@ -159,12 +162,12 @@ const ValueEditor: FunctionComponent = (props) => { ); } return ( - + ); -}; +} export default ValueEditor; diff --git a/src/components/inputs/react-query-builder/value-selector.tsx b/src/components/inputs/react-query-builder/value-selector.tsx index ad554142..05649118 100644 --- a/src/components/inputs/react-query-builder/value-selector.tsx +++ b/src/components/inputs/react-query-builder/value-selector.tsx @@ -9,12 +9,12 @@ import { ValueSelectorProps } from 'react-querybuilder'; import React from 'react'; import { MaterialValueSelector } from '@react-querybuilder/material'; -const ValueSelector = (props: ValueSelectorProps) => { +function ValueSelector(props: ValueSelectorProps) { return ( ); -}; +} export default ValueSelector; diff --git a/src/components/inputs/select-clearable.tsx b/src/components/inputs/select-clearable.tsx index bc0861fc..c59d96b2 100644 --- a/src/components/inputs/select-clearable.tsx +++ b/src/components/inputs/select-clearable.tsx @@ -5,11 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import type { FunctionComponent } from 'react'; import { useIntl } from 'react-intl'; import { Autocomplete, TextField } from '@mui/material'; import { AutocompleteProps } from '@mui/material/Autocomplete/Autocomplete'; -import FieldLabel from '../inputs/react-hook-form/utils/field-label'; +import FieldLabel from './react-hook-form/utils/field-label'; type SelectOption = { id: string; label?: string }; @@ -23,14 +22,14 @@ interface SelectClearableProps label?: string; } -const SelectClearable: FunctionComponent = (props) => { - const { value, onChange, label, ...otherProps } = props; +function SelectClearable(props: Readonly) { + const { value, onChange, label, options, ...otherProps } = props; const intl = useIntl(); - const inputTransform = (value: string | null) => { + const inputTransform = (inputValue: string | null) => { return ( - (value && props.options.find((option) => option.id === value)) || + (value && options.find((option) => option.id === inputValue)) || null ); }; @@ -53,15 +52,20 @@ const SelectClearable: FunctionComponent = (props) => { {...otherParams} {...(label && { label: FieldLabel({ - label: label, + label, }), })} inputProps={{ ...inputProps, readOnly: true }} /> )} + options={options} {...otherProps} /> ); +} + +SelectClearable.defaultProps = { + label: undefined, }; export default SelectClearable; diff --git a/src/components/translations/card-error-boundary-en.ts b/src/components/translations/card-error-boundary-en.ts index 0085e44a..8331e86f 100644 --- a/src/components/translations/card-error-boundary-en.ts +++ b/src/components/translations/card-error-boundary-en.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const card_error_boundary_en = { +const cardErrorBoundaryEn = { 'card_error_boundary/title': 'Sorry, unexpected error :(', 'card_error_boundary/content': 'Please reload, or close and reopen this application, or contact support.', @@ -13,4 +13,4 @@ const card_error_boundary_en = { 'Error message (and see more information in the developper console):', }; -export default card_error_boundary_en; +export default cardErrorBoundaryEn; diff --git a/src/components/translations/card-error-boundary-fr.ts b/src/components/translations/card-error-boundary-fr.ts index 5db22960..20c42297 100644 --- a/src/components/translations/card-error-boundary-fr.ts +++ b/src/components/translations/card-error-boundary-fr.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const card_error_boundary_fr = { +const cardErrorBoundaryFr = { 'card_error_boundary/title': 'Désolé, erreur inattendue :(', 'card_error_boundary/content': 'Veuillez recharger, ou fermer et réouvrir cette application, ou contacter le support.', @@ -13,4 +13,4 @@ const card_error_boundary_fr = { "Message d'erreur (et voir plus d'informations dans la console developpeur):", }; -export default card_error_boundary_fr; +export default cardErrorBoundaryFr; diff --git a/src/components/translations/common-button-en.ts b/src/components/translations/common-button-en.ts index e92d99be..d8764222 100644 --- a/src/components/translations/common-button-en.ts +++ b/src/components/translations/common-button-en.ts @@ -5,9 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const common_button_en = { +const commonButtonEn = { cancel: 'Cancel', validate: 'Validate', }; -export default common_button_en; +export default commonButtonEn; diff --git a/src/components/translations/common-button-fr.ts b/src/components/translations/common-button-fr.ts index 1252a9cf..10997b40 100644 --- a/src/components/translations/common-button-fr.ts +++ b/src/components/translations/common-button-fr.ts @@ -5,9 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const common_button_fr = { +const commonButtonFr = { cancel: 'Annuler', validate: 'Valider', }; -export default common_button_fr; +export default commonButtonFr; diff --git a/src/components/translations/directory-items-input-en.ts b/src/components/translations/directory-items-input-en.ts index 44abf8ab..68ebdf98 100644 --- a/src/components/translations/directory-items-input-en.ts +++ b/src/components/translations/directory-items-input-en.ts @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const directory_items_input_en = { +const directoryItemsInputEn = { 'directory_items_input/ElementAlreadyUsed': 'This element is already used', }; -export default directory_items_input_en; +export default directoryItemsInputEn; diff --git a/src/components/translations/directory-items-input-fr.ts b/src/components/translations/directory-items-input-fr.ts index f535ca0e..bc6d94f9 100644 --- a/src/components/translations/directory-items-input-fr.ts +++ b/src/components/translations/directory-items-input-fr.ts @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const directory_items_input_fr = { +const directoryItemsInputFr = { 'directory_items_input/ElementAlreadyUsed': 'Cet élément est déjà utilisé', }; -export default directory_items_input_fr; +export default directoryItemsInputFr; diff --git a/src/components/translations/element-search-en.ts b/src/components/translations/element-search-en.ts index c9b4bdf1..57ec2083 100644 --- a/src/components/translations/element-search-en.ts +++ b/src/components/translations/element-search-en.ts @@ -5,9 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const element_search_en = { +const elementSearchEn = { 'element_search/label': 'Search for an element', 'element_search/noResult': 'No result', }; -export default element_search_en; +export default elementSearchEn; diff --git a/src/components/translations/element-search-fr.ts b/src/components/translations/element-search-fr.ts index 8df70413..54e84de5 100644 --- a/src/components/translations/element-search-fr.ts +++ b/src/components/translations/element-search-fr.ts @@ -5,9 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const element_search_fr = { +const elementSearchFr = { 'element_search/label': 'Rechercher un élément', 'element_search/noResult': 'Aucun résultat', }; -export default element_search_fr; +export default elementSearchFr; diff --git a/src/components/translations/equipment-search-en.ts b/src/components/translations/equipment-search-en.ts index a0c910e3..151005f4 100644 --- a/src/components/translations/equipment-search-en.ts +++ b/src/components/translations/equipment-search-en.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const equipment_search_en = { +const equipmentSearchEn = { 'equipment_search/label': 'Search for an equipment', 'equipment_search/switchTag': 'SWITCH', 'equipment_search/busbarSectionTag': 'BBS', @@ -24,4 +24,4 @@ const equipment_search_en = { 'equipment_search/busTag': 'BUS', }; -export default equipment_search_en; +export default equipmentSearchEn; diff --git a/src/components/translations/equipment-search-fr.ts b/src/components/translations/equipment-search-fr.ts index 15c7c572..c797e979 100644 --- a/src/components/translations/equipment-search-fr.ts +++ b/src/components/translations/equipment-search-fr.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const equipment_search_fr = { +const equipmentSearchFr = { 'equipment_search/label': 'Rechercher un ouvrage', 'equipment_search/switchTag': 'SWITCH', 'equipment_search/busbarSectionTag': 'SJB', @@ -24,4 +24,4 @@ const equipment_search_fr = { 'equipment_search/busTag': 'NOEUD', }; -export default equipment_search_fr; +export default equipmentSearchFr; diff --git a/src/components/translations/filter-en.ts b/src/components/translations/filter-en.ts index eef45172..3963c8f8 100644 --- a/src/components/translations/filter-en.ts +++ b/src/components/translations/filter-en.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const filter_en = { +const filterEn = { OR: 'OR', AND: 'AND', rule: 'rule', @@ -29,4 +29,4 @@ const filter_en = { Hvdc: 'HVDC', }; -export default filter_en; +export default filterEn; diff --git a/src/components/translations/filter-expert-en.ts b/src/components/translations/filter-expert-en.ts index e09e343b..ca805497 100644 --- a/src/components/translations/filter-expert-en.ts +++ b/src/components/translations/filter-expert-en.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const filter_expert_en = { +const filterExpertEn = { id: 'ID', name: 'Name', energySource: 'Energy source', @@ -72,4 +72,4 @@ const filter_expert_en = { voltageLevelProperty2: 'Voltage level property 2', }; -export default filter_expert_en; +export default filterExpertEn; diff --git a/src/components/translations/filter-expert-fr.ts b/src/components/translations/filter-expert-fr.ts index b4a67a40..80cf0362 100644 --- a/src/components/translations/filter-expert-fr.ts +++ b/src/components/translations/filter-expert-fr.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const filter_expert_fr = { +const filterExpertFr = { id: 'ID', name: 'Nom', energySource: "Source d'énergie", @@ -72,4 +72,4 @@ const filter_expert_fr = { voltageLevelProperty2: 'Propriété poste 2', }; -export default filter_expert_fr; +export default filterExpertFr; diff --git a/src/components/translations/filter-fr.ts b/src/components/translations/filter-fr.ts index ffc724d4..295fa458 100644 --- a/src/components/translations/filter-fr.ts +++ b/src/components/translations/filter-fr.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const filter_fr = { +const filterFr = { OR: 'OU', AND: 'ET', rule: 'règle', @@ -29,4 +29,4 @@ const filter_fr = { Hvdc: 'HVDC', }; -export default filter_fr; +export default filterFr; diff --git a/src/components/translations/flat-parameters-en.ts b/src/components/translations/flat-parameters-en.ts index 9491a883..740f4141 100644 --- a/src/components/translations/flat-parameters-en.ts +++ b/src/components/translations/flat-parameters-en.ts @@ -5,10 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const flat_parameters_en = { +const flatParametersEn = { 'flat_parameters/none': 'None', 'flat_parameters/some': 'Some', 'flat_parameters/all': 'All', }; -export default flat_parameters_en; +export default flatParametersEn; diff --git a/src/components/translations/flat-parameters-fr.ts b/src/components/translations/flat-parameters-fr.ts index 95f6accf..c39af416 100644 --- a/src/components/translations/flat-parameters-fr.ts +++ b/src/components/translations/flat-parameters-fr.ts @@ -5,10 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const flat_parameters_fr = { +const flatParametersFr = { 'flat_parameters/none': 'Aucune', 'flat_parameters/some': 'Quelques-unes', 'flat_parameters/all': 'Toutes', }; -export default flat_parameters_fr; +export default flatParametersFr; diff --git a/src/components/translations/inputs-en.ts b/src/components/translations/inputs-en.ts index dce23199..4762a5fd 100644 --- a/src/components/translations/inputs-en.ts +++ b/src/components/translations/inputs-en.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const inputs_en = { +const inputsEn = { 'inputs/kiki': 'Kylian Mbappe', 'inputs/ney': 'Neymar', 'inputs/lapulga': 'Lionel Messi', @@ -24,4 +24,4 @@ const inputs_en = { Optional: 'Optional', }; -export default inputs_en; +export default inputsEn; diff --git a/src/components/translations/inputs-fr.ts b/src/components/translations/inputs-fr.ts index f034570e..b21f5cc5 100644 --- a/src/components/translations/inputs-fr.ts +++ b/src/components/translations/inputs-fr.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const inputs_fr = { +const inputsFr = { 'inputs/kiki': 'Kylian Mbappe', 'inputs/ney': 'Neymar', 'inputs/lapulga': 'Lionel Messi', @@ -24,4 +24,4 @@ const inputs_fr = { Optional: 'Optional', }; -export default inputs_fr; +export default inputsFr; diff --git a/src/components/translations/login-en.ts b/src/components/translations/login-en.ts index b21cdb79..dd2ceb48 100644 --- a/src/components/translations/login-en.ts +++ b/src/components/translations/login-en.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const login_en = { +const loginEn = { 'login/login': 'Login', 'login/connection': 'Connection', 'login/unauthorizedAccess': 'Unauthorized access', @@ -20,4 +20,4 @@ const login_en = { 'login/logout': 'Logout', }; -export default login_en; +export default loginEn; diff --git a/src/components/translations/login-fr.ts b/src/components/translations/login-fr.ts index 1632e792..cadd5f1b 100644 --- a/src/components/translations/login-fr.ts +++ b/src/components/translations/login-fr.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const login_fr = { +const loginFr = { 'login/login': 'Se connecter', 'login/connection': 'Connexion', 'login/unauthorizedAccess': 'Accès non autorisé', @@ -21,4 +21,4 @@ const login_fr = { 'login/logout': 'Se déconnecter', }; -export default login_fr; +export default loginFr; diff --git a/src/components/translations/multiple-selection-dialog-en.ts b/src/components/translations/multiple-selection-dialog-en.ts index 92725d84..48047ada 100644 --- a/src/components/translations/multiple-selection-dialog-en.ts +++ b/src/components/translations/multiple-selection-dialog-en.ts @@ -5,10 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const multiple_selection_dialog_en = { +const multipleSelectionDialogEn = { 'multiple_selection_dialog/cancel': 'Cancel', 'multiple_selection_dialog/validate': 'Validate', 'multiple_selection_dialog/selectAll': 'Select all', }; -export default multiple_selection_dialog_en; +export default multipleSelectionDialogEn; diff --git a/src/components/translations/multiple-selection-dialog-fr.ts b/src/components/translations/multiple-selection-dialog-fr.ts index 8c6ea753..2151bdc0 100644 --- a/src/components/translations/multiple-selection-dialog-fr.ts +++ b/src/components/translations/multiple-selection-dialog-fr.ts @@ -5,10 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const multiple_selection_dialog_fr = { +const multipleSelectionDialogFr = { 'multiple_selection_dialog/cancel': 'Annuler', 'multiple_selection_dialog/validate': 'Valider', 'multiple_selection_dialog/selectAll': 'Tout sélectionner', }; -export default multiple_selection_dialog_fr; +export default multipleSelectionDialogFr; diff --git a/src/components/translations/report-viewer-en.ts b/src/components/translations/report-viewer-en.ts index a7bd6340..06ca12a6 100644 --- a/src/components/translations/report-viewer-en.ts +++ b/src/components/translations/report-viewer-en.ts @@ -5,10 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const report_viewer_en = { +const reportViewerEn = { 'report_viewer/close': 'Close', 'report_viewer/severity': 'Severity', 'report_viewer/message': 'Message', }; -export default report_viewer_en; +export default reportViewerEn; diff --git a/src/components/translations/report-viewer-fr.ts b/src/components/translations/report-viewer-fr.ts index 80914e06..0141a7eb 100644 --- a/src/components/translations/report-viewer-fr.ts +++ b/src/components/translations/report-viewer-fr.ts @@ -5,10 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const report_viewer_fr = { +const reportViewerFr = { 'report_viewer/close': 'Fermer', 'report_viewer/severity': 'Sévérité', 'report_viewer/message': 'Message', }; -export default report_viewer_fr; +export default reportViewerFr; diff --git a/src/components/translations/table-en.ts b/src/components/translations/table-en.ts index 0a094cc9..2f2d789b 100644 --- a/src/components/translations/table-en.ts +++ b/src/components/translations/table-en.ts @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const table_en = { +const tableEn = { 'MuiVirtualizedTable/exportCSV': 'Download CSV', }; -export default table_en; +export default tableEn; diff --git a/src/components/translations/table-fr.ts b/src/components/translations/table-fr.ts index bf950b89..27f8b16d 100644 --- a/src/components/translations/table-fr.ts +++ b/src/components/translations/table-fr.ts @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const table_fr = { +const tableFr = { 'MuiVirtualizedTable/exportCSV': 'Télécharger un CSV', }; -export default table_fr; +export default tableFr; diff --git a/src/components/translations/top-bar-en.ts b/src/components/translations/top-bar-en.ts index d51f2b44..c772cdd0 100644 --- a/src/components/translations/top-bar-en.ts +++ b/src/components/translations/top-bar-en.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const top_bar_en = { +const topBarEn = { 'top-bar/settings': 'Settings', 'top-bar/logout': 'Logout', 'top-bar/goFullScreen': 'Full screen', @@ -31,4 +31,4 @@ const top_bar_en = { 'about-dialog/module-tooltip-other': 'other', }; -export default top_bar_en; +export default topBarEn; diff --git a/src/components/translations/top-bar-fr.ts b/src/components/translations/top-bar-fr.ts index a9e10b3d..405c2629 100644 --- a/src/components/translations/top-bar-fr.ts +++ b/src/components/translations/top-bar-fr.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const top_bar_fr = { +const topBarFr = { 'top-bar/settings': 'Paramètres', 'top-bar/logout': 'Se déconnecter', 'top-bar/goFullScreen': 'Plein écran', @@ -31,4 +31,4 @@ const top_bar_fr = { 'about-dialog/module-tooltip-other': 'autre', }; -export default top_bar_fr; +export default topBarFr; diff --git a/src/components/translations/treeview-finder-en.ts b/src/components/translations/treeview-finder-en.ts index 007e1241..2e089690 100644 --- a/src/components/translations/treeview-finder-en.ts +++ b/src/components/translations/treeview-finder-en.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const treeview_finder_en = { +const treeviewFinderEn = { 'treeview_finder/close': 'Close', 'treeview_finder/validate': 'Validate', 'treeview_finder/add': 'Add...', @@ -20,4 +20,4 @@ const treeview_finder_en = { '{nbElements, plural, =0 {Please select an element} =1 {Replace with this element} other{Replace with # elements}}', }; -export default treeview_finder_en; +export default treeviewFinderEn; diff --git a/src/components/translations/treeview-finder-fr.ts b/src/components/translations/treeview-finder-fr.ts index d83ed0b9..41121be1 100644 --- a/src/components/translations/treeview-finder-fr.ts +++ b/src/components/translations/treeview-finder-fr.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const treeview_finder_fr = { +const treeviewFinderFr = { 'treeview_finder/close': 'Fermer', 'treeview_finder/validate': 'Valider', 'treeview_finder/add': 'Ajouter...', @@ -20,4 +20,4 @@ const treeview_finder_fr = { '{nbElements, plural, =0 {Veuillez sélectionner un élément} =1 {Remplacer par cet élément} other {Remplacer par # éléments}}', }; -export default treeview_finder_fr; +export default treeviewFinderFr; diff --git a/src/hooks/localized-countries-hook.ts b/src/hooks/localized-countries-hook.ts index ef4d94d4..b3ac6da7 100644 --- a/src/hooks/localized-countries-hook.ts +++ b/src/hooks/localized-countries-hook.ts @@ -36,7 +36,7 @@ export const useLocalizedCountries = (language: string | undefined) => { const [localizedCountriesModule, setLocalizedCountriesModule] = useState(); - //TODO FM this is disgusting, can we make it better ? + // TODO FM this is disgusting, can we make it better ? useEffect(() => { const lang = getComputedLanguage(language).substring(0, 2); let localizedCountriesResult; @@ -48,9 +48,7 @@ export const useLocalizedCountries = (language: string | undefined) => { localizedCountriesResult = localizedCountries(countriesEn); } else { console.warn( - 'Unsupported language "' + - lang + - '" for countries translation, we use english as default' + `Unsupported language "${lang}" for countries translation, we use english as default` ); localizedCountriesResult = localizedCountries(countriesEn); } diff --git a/src/hooks/predefined-properties-hook.ts b/src/hooks/predefined-properties-hook.ts index 8f563cfb..4282895c 100644 --- a/src/hooks/predefined-properties-hook.ts +++ b/src/hooks/predefined-properties-hook.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Dispatch, SetStateAction, useEffect, useState } from 'react'; -import { mapEquipmentTypeForPredefinedProperties } from '../utils/equipment-types-for-predefined-properties-mapper'; +import mapEquipmentTypeForPredefinedProperties from '../utils/equipment-types-for-predefined-properties-mapper'; import { useSnackMessage } from './useSnackMessage'; import { EquipmentType, PredefinedProperties } from '../utils/types'; import { fetchStudyMetadata } from '../services'; @@ -22,7 +22,7 @@ const fetchPredefinedProperties = async ( return studyMetadata.predefinedEquipmentProperties?.[networkEquipmentType]; }; -export const usePredefinedProperties = ( +const usePredefinedProperties = ( initialType: EquipmentType | null ): [PredefinedProperties, Dispatch>] => { const [type, setType] = useState(initialType); @@ -48,3 +48,5 @@ export const usePredefinedProperties = ( return [equipmentPredefinedProps, setType]; }; + +export default usePredefinedProperties; diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts index 4d7a36d0..df8092c4 100644 --- a/src/hooks/useDebounce.ts +++ b/src/hooks/useDebounce.ts @@ -8,7 +8,7 @@ import { useEffect, useMemo } from 'react'; import { debounce } from '@mui/material'; -export const useDebounce = any>( +const useDebounce = any>( func: T, delay = 700 ) => { @@ -27,3 +27,5 @@ export const useDebounce = any>( return debouncedChangeHandler; }; + +export default useDebounce; diff --git a/src/hooks/useIntlRef.ts b/src/hooks/useIntlRef.ts index f5a48264..3fe04f31 100644 --- a/src/hooks/useIntlRef.ts +++ b/src/hooks/useIntlRef.ts @@ -8,11 +8,11 @@ import { useEffect, useRef } from 'react'; import { useIntl } from 'react-intl'; -//This useEffect must be at the beginning to be executed before other useEffects which use intlRef. -//This ref is used to avoid redoing other useEffects when the language (intl) is changed for things that produce temporary messages using the snackbar. -//The drawback to this custom hook is that a ref and a useEffect are created in each component that needs this hook. -//Can we avoid this overhead ? -export function useIntlRef() { +// This useEffect must be at the beginning to be executed before other useEffects which use intlRef. +// This ref is used to avoid redoing other useEffects when the language (intl) is changed for things that produce temporary messages using the snackbar. +// The drawback to this custom hook is that a ref and a useEffect are created in each component that needs this hook. +// Can we avoid this overhead ? +function useIntlRef() { const intl = useIntl(); const intlRef = useRef(intl); @@ -22,3 +22,5 @@ export function useIntlRef() { return intlRef; } + +export default useIntlRef; diff --git a/src/hooks/useSnackMessage.ts b/src/hooks/useSnackMessage.ts index e8bf93ee..2973733d 100644 --- a/src/hooks/useSnackMessage.ts +++ b/src/hooks/useSnackMessage.ts @@ -12,8 +12,8 @@ import { closeSnackbar, useSnackbar, } from 'notistack'; -import { useIntlRef } from './useIntlRef'; import { IntlShape } from 'react-intl'; +import useIntlRef from './useIntlRef'; interface SnackInputs extends Omit { messageTxt?: string; @@ -31,57 +31,10 @@ export interface UseSnackMessageReturn { closeSnackbar: typeof closeSnackbar; } -export function useSnackMessage(): UseSnackMessageReturn { - const intlRef = useIntlRef(); - const { enqueueSnackbar, closeSnackbar } = useSnackbar(); - - const enqueue = useCallback( - (snackInputs: SnackInputs, variant: BaseVariant) => { - const message = makeMessage(intlRef, snackInputs); - if (message === null) { - return; - } - return enqueueSnackbar(message, { - ...snackInputs, - variant: variant, - style: { whiteSpace: 'pre-line' }, - }); - }, - [enqueueSnackbar, intlRef] - ); - - /* - There is two kind of messages : the message itself (bottom of snackbar), and the header (top of snackbar). - As inputs, you can give either a text message, or an ID with optional values (for translation with intl). - snackInputs: { - messageTxt, - messageId, - messageValues, - headerTxt, - headerId, - headerValues, - key?, // optional key to close the snackbar - persist - } - */ - const snackError = useCallback( - (snackInputs: SnackInputs) => enqueue(snackInputs, 'error'), - [enqueue] - ); - - /* see snackError */ - const snackWarning = useCallback( - (snackInputs: SnackInputs) => enqueue(snackInputs, 'warning'), - [enqueue] - ); - - /* see snackError */ - const snackInfo = useCallback( - (snackInputs: SnackInputs) => enqueue(snackInputs, 'info'), - [enqueue] - ); - - return { snackError, snackInfo, snackWarning, closeSnackbar }; +function checkInputs(txt?: string, id?: string, values?: any) { + if (txt && (id || values)) { + console.warn('Snack inputs should be [*Txt] OR [*Id, *Values]'); + } } function checkAndTranslateIfNecessary( @@ -96,7 +49,7 @@ function checkAndTranslateIfNecessary( (id ? intlRef.current.formatMessage( { - id: id, + id, }, values ) @@ -104,12 +57,6 @@ function checkAndTranslateIfNecessary( ); } -function checkInputs(txt?: string, id?: string, values?: any) { - if (txt && (id || values)) { - console.warn('Snack inputs should be [*Txt] OR [*Id, *Values]'); - } -} - function makeMessage( intlRef: MutableRefObject, snackInputs: SnackInputs @@ -139,3 +86,57 @@ function makeMessage( } return fullMessage; } + +export function useSnackMessage(): UseSnackMessageReturn { + const intlRef = useIntlRef(); + // eslint-disable-next-line @typescript-eslint/no-shadow + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + + const enqueue = useCallback( + (snackInputs: SnackInputs, variant: BaseVariant) => { + const message = makeMessage(intlRef, snackInputs); + if (message === null) { + return; + } + enqueueSnackbar(message, { + ...snackInputs, + variant, + style: { whiteSpace: 'pre-line' }, + }); + }, + [enqueueSnackbar, intlRef] + ); + + /* + There is two kind of messages : the message itself (bottom of snackbar), and the header (top of snackbar). + As inputs, you can give either a text message, or an ID with optional values (for translation with intl). + snackInputs: { + messageTxt, + messageId, + messageValues, + headerTxt, + headerId, + headerValues, + key?, // optional key to close the snackbar + persist + } + */ + const snackError = useCallback( + (snackInputs: SnackInputs) => enqueue(snackInputs, 'error'), + [enqueue] + ); + + /* see snackError */ + const snackWarning = useCallback( + (snackInputs: SnackInputs) => enqueue(snackInputs, 'warning'), + [enqueue] + ); + + /* see snackError */ + const snackInfo = useCallback( + (snackInputs: SnackInputs) => enqueue(snackInputs, 'info'), + [enqueue] + ); + + return { snackError, snackInfo, snackWarning, closeSnackbar }; +} diff --git a/src/index.ts b/src/index.ts index af585252..ad8e3142 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,22 +5,22 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export { default as TreeViewFinder } from './components/TreeViewFinder'; -export { default as TopBar } from './components/TopBar'; +export { TreeViewFinder } from './components/TreeViewFinder'; +export { TopBar } from './components/TopBar'; export { default as AboutDialog } from './components/TopBar/AboutDialog'; -export { default as SnackbarProvider } from './components/SnackbarProvider'; -export { default as AuthenticationRouter } from './components/AuthenticationRouter'; -export { default as MuiVirtualizedTable } from './components/MuiVirtualizedTable'; +export { SnackbarProvider } from './components/SnackbarProvider'; +export { AuthenticationRouter } from './components/AuthenticationRouter'; +export { MuiVirtualizedTable } from './components/MuiVirtualizedTable'; export { KeyedColumnsRowIndexer, - CHANGE_WAYS, + ChangeWays, } from './components/MuiVirtualizedTable'; -export { default as ReportViewer } from './components/ReportViewer'; -export { default as ReportViewerDialog } from './components/ReportViewerDialog'; -export { default as OverflowableText } from './components/OverflowableText'; -export { default as ElementSearchDialog } from './components/ElementSearchDialog'; -export { default as FlatParameters } from './components/FlatParameters'; -export { default as MultipleSelectionDialog } from './components/MultipleSelectionDialog'; +export { ReportViewer } from './components/ReportViewer'; +export { ReportViewerDialog } from './components/ReportViewerDialog'; +export { OverflowableText } from './components/OverflowableText'; +export { ElementSearchDialog } from './components/ElementSearchDialog'; +export { FlatParameters } from './components/FlatParameters'; +export { MultipleSelectionDialog } from './components/MultipleSelectionDialog'; export { default as CustomMuiDialog } from './components/dialogs/custom-mui-dialog'; export { default as DescriptionModificationDialog } from './components/dialogs/description-modification-dialog'; export { default as ModifyElementSelection } from './components/dialogs/modify-element-selection'; @@ -49,7 +49,7 @@ export { noSelectionForCopy, } from './utils/equipment-types'; -export { FieldConstants } from './utils/field-constants'; +export { default as FieldConstants } from './utils/field-constants'; export type { TreeViewFinderNodeProps } from './components/TreeViewFinder/TreeViewFinder'; @@ -80,7 +80,7 @@ export { getPreLoginPath, } from './utils/AuthService'; -export { getFileIcon } from './utils/ElementIcon'; +export { default as getFileIcon } from './utils/ElementIcon'; export { DEFAULT_CELL_PADDING, @@ -138,12 +138,12 @@ export { default as directory_items_input_fr } from './components/translations/d export { TagRenderer } from './components/ElementSearchDialog'; export { EquipmentItem } from './components/ElementSearchDialog/equipment-item'; -export { default as CardErrorBoundary } from './components/CardErrorBoundary'; -export { useIntlRef } from './hooks/useIntlRef'; +export { CardErrorBoundary } from './components/CardErrorBoundary'; +export { default as useIntlRef } from './hooks/useIntlRef'; export { useSnackMessage } from './hooks/useSnackMessage'; -export { useDebounce } from './hooks/useDebounce'; +export { default as useDebounce } from './hooks/useDebounce'; export { default as SelectClearable } from './components/inputs/select-clearable'; -export { useCustomFormContext } from './components/inputs/react-hook-form/provider/use-custom-form-context'; +export { default as useCustomFormContext } from './components/inputs/react-hook-form/provider/use-custom-form-context'; export { default as CustomFormProvider } from './components/inputs/react-hook-form/provider/custom-form-provider'; export { default as AutocompleteInput } from './components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input'; export { default as TextInput } from './components/inputs/react-hook-form/text-input'; @@ -178,7 +178,7 @@ export { } from './utils/functions'; export { default as DirectoryItemsInput } from './components/inputs/react-hook-form/directory-items-input'; export { default as DirectoryItemSelector } from './components/DirectoryItemSelector/directory-item-selector'; -export { RawReadOnlyInput } from './components/inputs/react-hook-form/raw-read-only-input'; +export { default as RawReadOnlyInput } from './components/inputs/react-hook-form/raw-read-only-input'; export { default as FilterCreationDialog } from './components/filter/filter-creation-dialog'; export { default as ExpertFilterEditionDialog } from './components/filter/expert/expert-filter-edition-dialog'; @@ -205,8 +205,8 @@ export { } from './hooks/localized-countries-hook'; export { default as MultipleAutocompleteInput } from './components/inputs/react-hook-form/autocomplete-inputs/multiple-autocomplete-input'; export { default as CsvUploader } from './components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader'; -export { UniqueNameInput } from './components/inputs/react-hook-form/unique-name-input'; -export { UserManagerMock } from './utils/UserManagerMock'; +export { default as UniqueNameInput } from './components/inputs/react-hook-form/unique-name-input'; +export { default as UserManagerMock } from './utils/UserManagerMock'; export { FILTER_EQUIPMENTS, CONTINGENCY_LIST_EQUIPMENTS, @@ -223,4 +223,3 @@ export { setCommonStore } from './redux/commonStore'; export type { EquipmentInfos } from './utils/EquipmentType'; export * from './services'; -export type * from './services'; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index e9d73c9d..7a1de630 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -9,7 +9,7 @@ import { User } from 'oidc-client'; export const USER = 'USER'; export function setLoggedUser(user: User | null) { - return { type: USER, user: user }; + return { type: USER, user }; } export const SIGNIN_CALLBACK_ERROR = 'SIGNIN_CALLBACK_ERROR'; @@ -17,7 +17,7 @@ export const SIGNIN_CALLBACK_ERROR = 'SIGNIN_CALLBACK_ERROR'; export function setSignInCallbackError(signInCallbackError: string | null) { return { type: SIGNIN_CALLBACK_ERROR, - signInCallbackError: signInCallbackError, + signInCallbackError, }; } @@ -30,8 +30,8 @@ export function setUnauthorizedUserInfo( return { type: UNAUTHORIZED_USER_INFO, authenticationRouterError: { - userName: userName, - unauthorizedUserInfo: unauthorizedUserInfo, + userName, + unauthorizedUserInfo, }, }; } @@ -45,8 +45,8 @@ export function setLogoutError( return { type: LOGOUT_ERROR, authenticationRouterError: { - userName: userName, - logoutError: logoutError, + userName, + logoutError, }, }; } @@ -60,8 +60,8 @@ export function setUserValidationError( return { type: USER_VALIDATION_ERROR, authenticationRouterError: { - userName: userName, - userValidationError: userValidationError, + userName, + userValidationError, }, }; } @@ -83,6 +83,6 @@ export function setShowAuthenticationRouterLogin( ) { return { type: SHOW_AUTH_INFO_LOGIN, - showAuthenticationRouterLogin: showAuthenticationRouterLogin, + showAuthenticationRouterLogin, }; } diff --git a/src/services/apps-metadata.ts b/src/services/apps-metadata.ts index 716fec06..f14cb3de 100644 --- a/src/services/apps-metadata.ts +++ b/src/services/apps-metadata.ts @@ -17,7 +17,7 @@ export type Env = { // https://github.com/gridsuite/deployment/blob/main/k8s/live/azure-dev/env.json // https://github.com/gridsuite/deployment/blob/main/k8s/live/azure-integ/env.json // https://github.com/gridsuite/deployment/blob/main/k8s/live/local/env.json - //[key: string]: string; + // [key: string]: string; }; export async function fetchEnv(): Promise { @@ -42,8 +42,8 @@ export type StudyMetadata = CommonMetadata & { }; defaultParametersValues?: { fluxConvention?: string; - enableDeveloperMode?: string; //maybe 'true'|'false' type? - mapManualRefresh?: string; //maybe 'true'|'false' type? + enableDeveloperMode?: string; // maybe 'true'|'false' type? + mapManualRefresh?: string; // maybe 'true'|'false' type? }; defaultCountry?: string; }; @@ -51,7 +51,7 @@ export type StudyMetadata = CommonMetadata & { export async function fetchAppsMetadata(): Promise { console.info(`Fetching apps and urls...`); const env = await fetchEnv(); - const res = await fetch(env.appsMetadataServerUrl + '/apps-metadata.json'); + const res = await fetch(`${env.appsMetadataServerUrl}/apps-metadata.json`); return res.json(); } diff --git a/src/services/directory.ts b/src/services/directory.ts index 1a4599ff..22b88c63 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -5,12 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { backendFetchJson, getRequestParamFromList } from './utils'; import { UUID } from 'crypto'; +import { backendFetchJson, getRequestParamFromList } from './utils'; import { ElementAttributes } from '../utils/types'; -const PREFIX_DIRECTORY_SERVER_QUERIES = - import.meta.env.VITE_API_GATEWAY + '/directory'; +const PREFIX_DIRECTORY_SERVER_QUERIES = `${ + import.meta.env.VITE_API_GATEWAY +}/directory`; export function fetchRootFolders( types: string[] @@ -19,8 +20,8 @@ export function fetchRootFolders( // Add params to Url const urlSearchParams = getRequestParamFromList( - types, - 'elementTypes' + 'elementTypes', + types ).toString(); const fetchRootFoldersUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/root-directories?${urlSearchParams}`; return backendFetchJson(fetchRootFoldersUrl, { @@ -37,12 +38,12 @@ export function fetchDirectoryContent( // Add params to Url const urlSearchParams = getRequestParamFromList( - types, - 'elementTypes' + 'elementTypes', + types ).toString(); - let fetchDirectoryContentUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/directories/${directoryUuid}/elements${ - urlSearchParams ? '?' + urlSearchParams : '' + const fetchDirectoryContentUrl = `${PREFIX_DIRECTORY_SERVER_QUERIES}/v1/directories/${directoryUuid}/elements${ + urlSearchParams ? `?${urlSearchParams}` : '' }`; return backendFetchJson(fetchDirectoryContentUrl, { method: 'get', diff --git a/src/services/explore.ts b/src/services/explore.ts index c0c85e28..4e2fc6a8 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -13,8 +13,9 @@ import { } from './utils'; import { ElementAttributes } from '../utils/types'; -const PREFIX_EXPLORE_SERVER_QUERIES = - import.meta.env.VITE_API_GATEWAY + '/explore'; +const PREFIX_EXPLORE_SERVER_QUERIES = `${ + import.meta.env.VITE_API_GATEWAY +}/explore`; export function createFilter( newFilter: any, @@ -23,15 +24,14 @@ export function createFilter( parentDirectoryUuid?: UUID, token?: string ) { - let urlSearchParams = new URLSearchParams(); + const urlSearchParams = new URLSearchParams(); urlSearchParams.append('name', name); urlSearchParams.append('description', description); - parentDirectoryUuid && + if (parentDirectoryUuid) { urlSearchParams.append('parentDirectoryUuid', parentDirectoryUuid); + } return backendFetch( - PREFIX_EXPLORE_SERVER_QUERIES + - '/v1/explore/filters?' + - urlSearchParams.toString(), + `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/filters?${urlSearchParams.toString()}`, { method: 'post', headers: { 'Content-Type': 'application/json' }, @@ -42,14 +42,12 @@ export function createFilter( } export function saveFilter(filter: any, name: string, token?: string) { - let urlSearchParams = new URLSearchParams(); + const urlSearchParams = new URLSearchParams(); urlSearchParams.append('name', name); return backendFetch( - PREFIX_EXPLORE_SERVER_QUERIES + - '/v1/explore/filters/' + - filter.id + - '?' + - urlSearchParams.toString(), + `${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/filters/${ + filter.id + }?${urlSearchParams.toString()}`, { method: 'put', headers: { 'Content-Type': 'application/json' }, @@ -68,18 +66,18 @@ export function fetchElementsInfos( // Add params to Url const idsParams = getRequestParamFromList( - ids.filter((id) => id), // filter falsy elements - 'ids' + 'ids', + ids.filter((id) => id) // filter falsy elements ); const equipmentTypesParams = getRequestParamFromList( - equipmentTypes, - 'equipmentTypes' + 'equipmentTypes', + equipmentTypes ); const elementTypesParams = getRequestParamFromList( - elementTypes, - 'elementTypes' + 'elementTypes', + elementTypes ); const urlSearchParams = new URLSearchParams([ diff --git a/src/services/index.ts b/src/services/index.ts index 3c64e4cd..babd8d49 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -5,12 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export * from './utils'; -export type * from './utils'; export * from './explore'; -export type * from './explore'; export * from './apps-metadata'; -export type * from './apps-metadata'; export * from './directory'; -export type * from './directory'; -export * from './study'; -export type * from './study'; +export { default as exportFilter } from './study'; diff --git a/src/services/study.ts b/src/services/study.ts index e2392895..7235fb58 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -8,21 +8,12 @@ import { UUID } from 'crypto'; import { backendFetchJson } from './utils'; -const PREFIX_STUDY_QUERIES = import.meta.env.VITE_API_GATEWAY + '/study'; +const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; -export function exportFilter( - studyUuid: UUID, - filterUuid?: UUID, - token?: string -) { +function exportFilter(studyUuid: UUID, filterUuid?: UUID, token?: string) { console.info('get filter export on study root node'); return backendFetchJson( - PREFIX_STUDY_QUERIES + - '/v1/studies/' + - studyUuid + - '/filters/' + - filterUuid + - '/elements', + `${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/filters/${filterUuid}/elements`, { method: 'get', headers: { 'Content-Type': 'application/json' }, @@ -30,3 +21,5 @@ export function exportFilter( token ); } + +export default exportFilter; diff --git a/src/services/utils.ts b/src/services/utils.ts index 6b4a9b4e..408eb0d1 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -7,37 +7,27 @@ import { getUserToken } from '../redux/commonStore'; -export const backendFetch = (url: string, init: any, token?: string) => { - const initCopy = prepareRequest(init, token); - return safeFetch(url, initCopy); -}; - -export const backendFetchJson = (url: string, init: any, token?: string) => { - const initCopy = prepareRequest(init, token); - return safeFetch(url, initCopy).then((safeResponse) => - safeResponse.status === 204 ? null : safeResponse.json() - ); +const parseError = (text: string) => { + try { + return JSON.parse(text); + } catch (err) { + return null; + } }; const prepareRequest = (init: any, token?: string) => { if (!(typeof init === 'undefined' || typeof init === 'object')) { throw new TypeError( - 'First argument of prepareRequest is not an object : ' + typeof init + `First argument of prepareRequest is not an object : ${typeof init}` ); } - const initCopy = Object.assign({}, init); + const initCopy = { ...init }; initCopy.headers = new Headers(initCopy.headers || {}); const tokenCopy = token ?? getUserToken(); - initCopy.headers.append('Authorization', 'Bearer ' + tokenCopy); + initCopy.headers.append('Authorization', `Bearer ${tokenCopy}`); return initCopy; }; -const safeFetch = (url: string, initCopy: any) => { - return fetch(url, initCopy).then((response) => - response.ok ? response : handleError(response) - ); -}; - const handleError = (response: any) => { return response.text().then((text: string) => { const errorName = 'HttpResponseError : '; @@ -50,22 +40,16 @@ const handleError = (response: any) => { errorJson.message ) { customError = new Error( - errorName + - errorJson.status + - ' ' + - errorJson.error + - ', message : ' + - errorJson.message + `${errorName + errorJson.status} ${ + errorJson.error + }, message : ${errorJson.message}` ); customError.status = errorJson.status; } else { customError = new Error( - errorName + - response.status + - ' ' + - response.statusText + - ', message : ' + - text + `${errorName + response.status} ${ + response.statusText + }, message : ${text}` ); customError.status = response.status; } @@ -73,17 +57,27 @@ const handleError = (response: any) => { }); }; -const parseError = (text: string) => { - try { - return JSON.parse(text); - } catch (err) { - return null; - } +const safeFetch = (url: string, initCopy: any) => { + return fetch(url, initCopy).then((response) => + response.ok ? response : handleError(response) + ); +}; + +export const backendFetch = (url: string, init: any, token?: string) => { + const initCopy = prepareRequest(init, token); + return safeFetch(url, initCopy); +}; + +export const backendFetchJson = (url: string, init: any, token?: string) => { + const initCopy = prepareRequest(init, token); + return safeFetch(url, initCopy).then((safeResponse) => + safeResponse.status === 204 ? null : safeResponse.json() + ); }; export const getRequestParamFromList = ( - params: string[] = [], - paramName: string + paramName: string, + params: string[] = [] ) => { return new URLSearchParams(params.map((param) => [paramName, param])); }; diff --git a/src/utils/AuthService.ts b/src/utils/AuthService.ts index 3157dcf3..54233836 100644 --- a/src/utils/AuthService.ts +++ b/src/utils/AuthService.ts @@ -5,7 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Log, User, UserManager } from 'oidc-client'; -import { UserManagerMock } from './UserManagerMock'; +import { jwtDecode } from 'jwt-decode'; +import { Dispatch } from 'react'; +import { NavigateFunction } from 'react-router-dom'; + import { setLoggedUser, setSignInCallbackError, @@ -15,9 +18,7 @@ import { resetAuthenticationRouterError, setShowAuthenticationRouterLogin, } from '../redux/actions'; -import { jwtDecode } from 'jwt-decode'; -import { Dispatch } from 'react'; -import { NavigateFunction } from 'react-router-dom'; +import UserManagerMock from './UserManagerMock'; type UserValidationFunc = (user: User) => Promise; @@ -29,13 +30,15 @@ type CustomUserManager = UserManager & { }; // set as a global variable to allow log level configuration at runtime -//@ts-ignore +// @ts-ignore window.OIDCLog = Log; const hackAuthorityKey = 'oidc.hack.authority'; const oidcHackReloadedKey = 'gridsuite-oidc-hack-reloaded'; const pathKey = 'powsybl-gridsuite-current-path'; +const accessTokenExpiringNotificationTime = 60; // seconds + function isIssuerErrorForCodeFlow(error: Error) { return error.message.includes('Invalid issuer in token'); } @@ -60,15 +63,204 @@ function reloadTimerOnExpiresIn( userManager: UserManager, expiresIn: number ) { - // TODO: Can we stop doing it in the hash for implicit flow ? To make it common for both flows - // Not allowed by TS because expires_in is supposed to be readonly - // @ts-ignore - user.expires_in = expiresIn; - userManager.storeUser(user).then(() => { + const updatedUser: User = { + ...user, + expires_in: expiresIn, + toStorageString: () => JSON.stringify(user), + }; + userManager.storeUser(updatedUser).then(() => { userManager.getUser(); }); } +function getIdTokenExpiresIn(user: User) { + const now = Date.now() / 1000; + const { exp } = jwtDecode(user.id_token); + if (exp === undefined) { + return 0; + } + return exp - now; +} + +function computeMinExpiresIn( + expiresIn: number, + idToken: string, + maxExpiresIn: number | undefined +) { + const now = Date.now() / 1000; + const { exp } = jwtDecode(idToken); + if (exp === undefined) { + return expiresIn; + } + const idTokenExpiresIn = exp - now; + let newExpiresIn = expiresIn; + let newExpiresInReplaceReason; + if (expiresIn === undefined || idTokenExpiresIn < newExpiresIn) { + newExpiresIn = idTokenExpiresIn; + newExpiresInReplaceReason = 'idtoken.exp is earlier'; + } + if (maxExpiresIn && maxExpiresIn < newExpiresIn) { + newExpiresIn = maxExpiresIn; + newExpiresInReplaceReason = 'idpSettings.maxExpiresIn is smaller'; + } + if (newExpiresInReplaceReason) { + console.debug( + `Replacing expiresIn in user to ${newExpiresIn} because ${newExpiresInReplaceReason}. `, + 'debug:', + `original expires_in: ${expiresIn}, `, + `idTokenExpiresIn: ${idTokenExpiresIn}, idpSettings maxExpiresIn: ${maxExpiresIn}` + ); + } + return newExpiresIn; +} + +function dispatchUser( + dispatch: Dispatch, + userManagerInstance: CustomUserManager, + validateUser: UserValidationFunc +) { + return userManagerInstance.getUser().then((user) => { + if (user) { + // If session storage contains a expired token at initialization + // We do not dispatch the user + // Our explicit SigninSilent will attempt to connect once + if (getIdTokenExpiresIn(user) < 0) { + console.debug( + 'User token is expired and will not be dispatched' + ); + return; + } + // without validateUser defined, valid user by default + const validateUserPromise = + (validateUser && validateUser(user)) || Promise.resolve(true); + validateUserPromise + .then((valid) => { + if (!valid) { + console.debug( + "User isn't authorized to log in and will not be dispatched" + ); + return dispatch( + setUnauthorizedUserInfo(user?.profile?.name, '') + ); + } + console.debug( + 'User has been successfully loaded from store.' + ); + + // In authorization code flow we have to make the oidc-client lib re-evaluate the date of the token renewal timers + // because it is not hacked at page loading on the fragment before oidc-client lib initialization + if (userManagerInstance.authorizationCodeFlowEnabled) { + reloadTimerOnExpiresIn( + user, + userManagerInstance, + computeMinExpiresIn( + user.expires_in, + user.id_token, + userManagerInstance.idpSettings?.maxExpiresIn + ) + ); + } + return dispatch(setLoggedUser(user)); + }) + .catch((e) => { + console.log('Error in dispatchUser', e); + return dispatch( + setUserValidationError(user?.profile?.name, { + error: e, + }) + ); + }); + } else { + console.debug('You are not logged in.'); + } + }); +} + +function handleUser( + dispatch: Dispatch, + userManager: CustomUserManager, + validateUser: UserValidationFunc +) { + userManager.events.addUserLoaded((user) => { + console.debug('user loaded', user); + dispatchUser(dispatch, userManager, validateUser); + }); + + userManager.events.addSilentRenewError((error) => { + console.debug(error); + // Wait for accessTokenExpiringNotificationTime so that the user is expired and not between expiring and expired + // otherwise the library will fire AccessTokenExpiring everytime we do getUser() + // Indeed, getUSer() => loadUser() => load() on events => if it's already expiring it will be init and triggerred again + window.setTimeout(() => { + userManager.getUser().then((user) => { + if (!user) { + console.error( + "user is null at silent renew error, it shouldn't happen." + ); + return; + } + const idTokenExpiresIn = getIdTokenExpiresIn(user); + if (idTokenExpiresIn < 0) { + console.log( + `Error in silent renew, idtoken expired: ${idTokenExpiresIn} => Logging out.`, + error + ); + // remove the user from our app, but don't sso logout on all other apps + dispatch(setShowAuthenticationRouterLogin(true)); + // logout during token expiration, show login without errors + dispatch(resetAuthenticationRouterError()); + dispatch(setLoggedUser(null)); + } + if (userManager.idpSettings?.maxExpiresIn) { + if ( + idTokenExpiresIn < userManager.idpSettings.maxExpiresIn + ) { + // TODO here attempt last chance login ? snackbar to notify the user ? Popup ? + // for now we do the same thing as in the else block + console.log( + `Error in silent renew, but idtoken ALMOST expiring (expiring in${idTokenExpiresIn}) => last chance, next error will logout`, + `maxExpiresIn = ${userManager.idpSettings.maxExpiresIn}`, + `last renew attempt in ${ + idTokenExpiresIn - + accessTokenExpiringNotificationTime + }seconds`, + error + ); + reloadTimerOnExpiresIn( + user, + userManager, + idTokenExpiresIn + ); + } else { + console.log( + `Error in silent renew, but idtoken NOT expiring (expiring in${idTokenExpiresIn}) => postponing expiration to${userManager.idpSettings.maxExpiresIn}`, + error + ); + reloadTimerOnExpiresIn( + user, + userManager, + userManager.idpSettings.maxExpiresIn + ); + } + } else { + console.log( + `Error in silent renew, unsupported configuration: token still valid for ${idTokenExpiresIn} but maxExpiresIn is not configured:${userManager.idpSettings?.maxExpiresIn}`, + error + ); + } + }); + }, accessTokenExpiringNotificationTime * 1000); + // Should be min(accessTokenExpiringNotificationTime * 1000, idTokenExpiresIn) to avoid rare case + // when user connection is dying and you refresh the page between expiring and expired. + // but gateway has a DEFAULT_MAX_CLOCK_SKEW = 60s then the token is still valid for this time + // even if expired + // We accept to not manage this case further + }); + + console.debug('dispatch user'); + dispatchUser(dispatch, userManager, validateUser); +} + function handleSigninSilent( dispatch: Dispatch, userManager: UserManager @@ -91,6 +283,7 @@ function handleSigninSilent( } }); } + return null; }); } @@ -100,7 +293,7 @@ function initializeAuthenticationDev( validateUser: UserValidationFunc, isSigninCallback: boolean ) { - let userManager: UserManager = new UserManagerMock( + const userManager: UserManager = new UserManagerMock( {} ) as unknown as UserManager; if (!isSilentRenew) { @@ -112,8 +305,6 @@ function initializeAuthenticationDev( return Promise.resolve(userManager); } -const accessTokenExpiringNotificationTime = 60; // seconds - function initializeAuthenticationProd( dispatch: Dispatch, isSilentRenew: boolean, @@ -124,21 +315,21 @@ function initializeAuthenticationProd( ) { return idpSettings .then((r) => r.json()) - .then((idpSettings) => { + .then((idpSetting) => { /* hack to ignore the iss check. XXX TODO to remove */ const regextoken = /id_token=[^&]*/; const regexstate = /state=[^&]*/; const regexexpires = /expires_in=[^&]*/; let authority: string | undefined; if (window.location.hash) { - const matched_id_token = window.location.hash.match(regextoken); - const matched_state = window.location.hash.match(regexstate); - if (matched_id_token != null && matched_state != null) { - const id_token = matched_id_token[0].split('=')[1]; - const state = matched_state[0].split('=')[1]; - const strState = localStorage.getItem('oidc.' + state); + const matchedIdToken = window.location.hash.match(regextoken); + const matchedState = window.location.hash.match(regexstate); + if (matchedIdToken != null && matchedState != null) { + const idToken = matchedIdToken[0].split('=')[1]; + const state = matchedState[0].split('=')[1]; + const strState = localStorage.getItem(`oidc.${state}`); if (strState != null) { - const decoded = jwtDecode(id_token); + const decoded = jwtDecode(idToken); authority = decoded.iss; const storedState = JSON.parse(strState); console.debug( @@ -149,26 +340,26 @@ function initializeAuthenticationProd( ); storedState.authority = authority; localStorage.setItem( - 'oidc.' + state, + `oidc.${state}`, JSON.stringify(storedState) ); if (authority !== undefined) { sessionStorage.setItem(hackAuthorityKey, authority); } - const matched_expires = + const matchedExpires = window.location.hash.match(regexexpires); - if (matched_expires != null) { - const expires_in = parseInt( - matched_expires[0].split('=')[1] + if (matchedExpires != null) { + const expiresIn = parseInt( + matchedExpires[0].split('=')[1], + 10 ); window.location.hash = window.location.hash.replace( - matched_expires[0], - 'expires_in=' + - computeMinExpiresIn( - expires_in, - id_token, - idpSettings.maxExpiresIn - ) + matchedExpires[0], + `expires_in=${computeMinExpiresIn( + expiresIn, + idToken, + idpSetting.maxExpiresIn + )}` ); } } @@ -177,7 +368,7 @@ function initializeAuthenticationProd( authority = authority || sessionStorage.getItem(hackAuthorityKey) || - idpSettings.authority; + idpSetting.authority; const responseSettings = authorizationCodeFlowEnabled ? { response_type: 'code' } @@ -187,19 +378,18 @@ function initializeAuthenticationProd( }; const settings = { authority, - client_id: idpSettings.client_id, - redirect_uri: idpSettings.redirect_uri, - post_logout_redirect_uri: idpSettings.post_logout_redirect_uri, - silent_redirect_uri: idpSettings.silent_redirect_uri, - scope: idpSettings.scope, + client_id: idpSetting.client_id, + redirect_uri: idpSetting.redirect_uri, + post_logout_redirect_uri: idpSetting.post_logout_redirect_uri, + silent_redirect_uri: idpSetting.silent_redirect_uri, + scope: idpSetting.scope, automaticSilentRenew: !isSilentRenew, - accessTokenExpiringNotificationTime: - accessTokenExpiringNotificationTime, + accessTokenExpiringNotificationTime, ...responseSettings, }; - let userManager: CustomUserManager = new UserManager(settings); + const userManager: CustomUserManager = new UserManager(settings); // Hack to enrich UserManager object - userManager.idpSettings = idpSettings; //store our settings in there as well to use it later + userManager.idpSettings = idpSetting; // store our settings in there as well to use it later // Hack to enrich UserManager object userManager.authorizationCodeFlowEnabled = authorizationCodeFlowEnabled; @@ -218,45 +408,6 @@ function initializeAuthenticationProd( }); } -function computeMinExpiresIn( - expiresIn: number, - idToken: string, - maxExpiresIn: number | undefined -) { - const now = Date.now() / 1000; - const exp = jwtDecode(idToken).exp; - if (exp === undefined) { - return expiresIn; - } - const idTokenExpiresIn = exp - now; - let newExpiresIn = expiresIn; - let newExpiresInReplaceReason; - if (expiresIn === undefined || idTokenExpiresIn < newExpiresIn) { - newExpiresIn = idTokenExpiresIn; - newExpiresInReplaceReason = 'idtoken.exp is earlier'; - } - if (maxExpiresIn && maxExpiresIn < newExpiresIn) { - newExpiresIn = maxExpiresIn; - newExpiresInReplaceReason = 'idpSettings.maxExpiresIn is smaller'; - } - if (newExpiresInReplaceReason) { - console.debug( - 'Replacing expiresIn in user to ' + - newExpiresIn + - ' because ' + - newExpiresInReplaceReason + - '. ', - 'debug:', - 'original expires_in: ' + expiresIn + ', ', - 'idTokenExpiresIn: ' + - idTokenExpiresIn + - ', idpSettings maxExpiresIn: ' + - maxExpiresIn - ); - } - return newExpiresIn; -} - function login(location: Location, userManagerInstance: UserManager) { sessionStorage.setItem(pathKey, location.pathname + location.search); return userManagerInstance @@ -265,7 +416,7 @@ function login(location: Location, userManagerInstance: UserManager) { } function logout(dispatch: Dispatch, userManagerInstance: UserManager) { - sessionStorage.removeItem(hackAuthorityKey); //To remove when hack is removed + sessionStorage.removeItem(hackAuthorityKey); // To remove when hack is removed return userManagerInstance.getUser().then((user) => { if (user) { // We don't need to check if token is valid at this point @@ -286,80 +437,9 @@ function logout(dispatch: Dispatch, userManagerInstance: UserManager) { dispatch(setLoggedUser(null)); dispatch(setLogoutError(user?.profile?.name, { error: e })); }); - } else { - console.log('Error nobody to logout '); - } - }); -} - -function getIdTokenExpiresIn(user: User) { - const now = Date.now() / 1000; - const exp = jwtDecode(user.id_token).exp; - if (exp === undefined) { - return 0; - } - return exp - now; -} - -function dispatchUser( - dispatch: Dispatch, - userManagerInstance: CustomUserManager, - validateUser: UserValidationFunc -) { - return userManagerInstance.getUser().then((user) => { - if (user) { - // If session storage contains a expired token at initialization - // We do not dispatch the user - // Our explicit SigninSilent will attempt to connect once - if (getIdTokenExpiresIn(user) < 0) { - console.debug( - 'User token is expired and will not be dispatched' - ); - return; - } - // without validateUser defined, valid user by default - let validateUserPromise = - (validateUser && validateUser(user)) || Promise.resolve(true); - return validateUserPromise - .then((valid) => { - if (!valid) { - console.debug( - "User isn't authorized to log in and will not be dispatched" - ); - return dispatch( - setUnauthorizedUserInfo(user?.profile?.name, '') - ); - } - console.debug( - 'User has been successfully loaded from store.' - ); - - // In authorization code flow we have to make the oidc-client lib re-evaluate the date of the token renewal timers - // because it is not hacked at page loading on the fragment before oidc-client lib initialization - if (userManagerInstance.authorizationCodeFlowEnabled) { - reloadTimerOnExpiresIn( - user, - userManagerInstance, - computeMinExpiresIn( - user.expires_in, - user.id_token, - userManagerInstance.idpSettings?.maxExpiresIn - ) - ); - } - return dispatch(setLoggedUser(user)); - }) - .catch((e) => { - console.log('Error in dispatchUser', e); - return dispatch( - setUserValidationError(user?.profile?.name, { - error: e, - }) - ); - }); - } else { - console.debug('You are not logged in.'); } + console.log('Error nobody to logout '); + return null; }); } @@ -382,7 +462,7 @@ function handleSigninCallback( let reloadAfterNavigate = false; userManagerInstance .signinRedirectCallback() - .catch(function (e) { + .catch((e) => { if (isIssuerErrorForCodeFlow(e)) { // Replacing authority for code flow only because it's done in the hash for implicit flow // TODO: Can we also do it here for the implicit flow ? To make it common here for both flows @@ -396,14 +476,14 @@ function handleSigninCallback( throw e; } }) - .then(function () { + .then(() => { dispatch(setSignInCallbackError(null)); navigateToPreLoginPath(navigate); if (reloadAfterNavigate) { reload(); } }) - .catch(function (e) { + .catch((e) => { dispatch(setSignInCallbackError(e)); console.error(e); }); @@ -413,101 +493,6 @@ function handleSilentRenewCallback(userManagerInstance: UserManager) { userManagerInstance.signinSilentCallback(); } -function handleUser( - dispatch: Dispatch, - userManager: CustomUserManager, - validateUser: UserValidationFunc -) { - userManager.events.addUserLoaded((user) => { - console.debug('user loaded', user); - dispatchUser(dispatch, userManager, validateUser); - }); - - userManager.events.addSilentRenewError((error) => { - console.debug(error); - // Wait for accessTokenExpiringNotificationTime so that the user is expired and not between expiring and expired - // otherwise the library will fire AccessTokenExpiring everytime we do getUser() - // Indeed, getUSer() => loadUser() => load() on events => if it's already expiring it will be init and triggerred again - window.setTimeout(() => { - userManager.getUser().then((user) => { - if (!user) { - console.error( - "user is null at silent renew error, it shouldn't happen." - ); - return; - } - const idTokenExpiresIn = getIdTokenExpiresIn(user); - if (idTokenExpiresIn < 0) { - console.log( - 'Error in silent renew, idtoken expired: ' + - idTokenExpiresIn + - ' => Logging out.', - error - ); - // remove the user from our app, but don't sso logout on all other apps - dispatch(setShowAuthenticationRouterLogin(true)); - // logout during token expiration, show login without errors - dispatch(resetAuthenticationRouterError()); - return dispatch(setLoggedUser(null)); - } else if (userManager.idpSettings?.maxExpiresIn) { - if ( - idTokenExpiresIn < userManager.idpSettings.maxExpiresIn - ) { - // TODO here attempt last chance login ? snackbar to notify the user ? Popup ? - // for now we do the same thing as in the else block - console.log( - 'Error in silent renew, but idtoken ALMOST expiring (expiring in' + - idTokenExpiresIn + - ') => last chance, next error will logout', - 'maxExpiresIn = ' + - userManager.idpSettings.maxExpiresIn, - 'last renew attempt in ' + - (idTokenExpiresIn - - accessTokenExpiringNotificationTime) + - 'seconds', - error - ); - reloadTimerOnExpiresIn( - user, - userManager, - idTokenExpiresIn - ); - } else { - console.log( - 'Error in silent renew, but idtoken NOT expiring (expiring in' + - idTokenExpiresIn + - ') => postponing expiration to' + - userManager.idpSettings.maxExpiresIn, - error - ); - reloadTimerOnExpiresIn( - user, - userManager, - userManager.idpSettings.maxExpiresIn - ); - } - } else { - console.log( - 'Error in silent renew, unsupported configuration: token still valid for ' + - idTokenExpiresIn + - ' but maxExpiresIn is not configured:' + - userManager.idpSettings?.maxExpiresIn, - error - ); - } - }); - }, accessTokenExpiringNotificationTime * 1000); - // Should be min(accessTokenExpiringNotificationTime * 1000, idTokenExpiresIn) to avoid rare case - // when user connection is dying and you refresh the page between expiring and expired. - // but gateway has a DEFAULT_MAX_CLOCK_SKEW = 60s then the token is still valid for this time - // even if expired - // We accept to not manage this case further - }); - - console.debug('dispatch user'); - dispatchUser(dispatch, userManager, validateUser); -} - export { initializeAuthenticationDev, initializeAuthenticationProd, diff --git a/src/utils/ElementIcon.tsx b/src/utils/ElementIcon.tsx index e9ce6ab5..3645b283 100644 --- a/src/utils/ElementIcon.tsx +++ b/src/utils/ElementIcon.tsx @@ -12,10 +12,10 @@ import { PhotoLibrary as PhotoLibraryIcon, Settings as SettingsIcon, } from '@mui/icons-material'; -import { ElementType } from './ElementType'; import { SxProps } from '@mui/material'; +import { ElementType } from './ElementType'; -export function getFileIcon(type: ElementType, style: SxProps) { +function getFileIcon(type: ElementType, style: SxProps) { switch (type) { case ElementType.STUDY: return ; @@ -34,8 +34,11 @@ export function getFileIcon(type: ElementType, style: SxProps) { return ; case ElementType.DIRECTORY: // to easily use in TreeView we do not give icons for directories - return; + return undefined; default: - console.warn('unknown type [' + type + ']'); + console.warn(`unknown type [${type}]`); } + return undefined; } + +export default getFileIcon; diff --git a/src/utils/ElementType.ts b/src/utils/ElementType.ts index f88b3f66..b3e60b1b 100644 --- a/src/utils/ElementType.ts +++ b/src/utils/ElementType.ts @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { UUID } from 'crypto'; + export enum ElementType { DIRECTORY = 'DIRECTORY', STUDY = 'STUDY', @@ -17,3 +19,9 @@ export enum ElementType { LOADFLOW_PARAMETERS = 'LOADFLOW_PARAMETERS', SENSITIVITY_PARAMETERS = 'SENSITIVITY_PARAMETERS', } + +export type ElementExistsType = ( + directory: UUID, + value: string, + elementType: ElementType +) => Promise; diff --git a/src/utils/EquipmentType.ts b/src/utils/EquipmentType.ts index 90a82251..cea40c43 100644 --- a/src/utils/EquipmentType.ts +++ b/src/utils/EquipmentType.ts @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { LIGHT_THEME } from '../components/TopBar/TopBar'; import { Theme } from '@mui/material'; +import { LIGHT_THEME } from '../components/TopBar/TopBar'; export const TYPE_TAG_MAX_SIZE = '90px'; export const VL_TAG_MAX_SIZE = '100px'; @@ -27,7 +27,7 @@ export const equipmentStyles = { fontSize: 'x-small', textAlign: 'center', color: - //TODO remove first condition when gridstudy is updated + // TODO remove first condition when gridstudy is updated theme === LIGHT_THEME || (typeof theme !== 'string' && theme?.palette?.mode === 'light') ? 'inherit' @@ -178,11 +178,11 @@ export const getEquipmentsInfosForSearchBar = ( getNameOrId: (e: Identifiable) => string ) => { return equipmentsInfos.flatMap((e): EquipmentInfos[] => { - let label = getNameOrId(e); + const label = getNameOrId(e); return e.type === EquipmentType.SUBSTATION ? [ { - label: label, + label, id: e.id, key: e.id, type: e.type, @@ -190,9 +190,9 @@ export const getEquipmentsInfosForSearchBar = ( ] : e.voltageLevels?.map((vli) => { return { - label: label, + label, id: e.id, - key: e.id + '_' + vli.id, + key: `${e.id}_${vli.id}`, type: e.type, voltageLevelLabel: getNameOrId(vli), voltageLevelId: vli.id, diff --git a/src/utils/Events.ts b/src/utils/Events.ts new file mode 100644 index 00000000..fcc64b9c --- /dev/null +++ b/src/utils/Events.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +export default class Events { + userLoadedCallbacks: ((data: any) => void)[] = []; + + addUserLoaded(callback: (data: any) => void) { + this.userLoadedCallbacks.push(callback); + } + + static addSilentRenewError() { + // Nothing to do + } +} diff --git a/src/utils/FetchStatus.ts b/src/utils/FetchStatus.ts index 7337391f..0d7a9ce7 100644 --- a/src/utils/FetchStatus.ts +++ b/src/utils/FetchStatus.ts @@ -5,9 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export const FetchStatus = { +const FetchStatus = { IDLE: 'IDLE', FETCHING: 'FETCHING', FETCH_SUCCESS: 'FETCH_SUCCESS', FETCH_ERROR: 'FETCH_ERROR', }; + +export default FetchStatus; diff --git a/src/utils/MetaData.ts b/src/utils/MetaData.ts new file mode 100644 index 00000000..bf65b37f --- /dev/null +++ b/src/utils/MetaData.ts @@ -0,0 +1,16 @@ +import { PredefinedProperties } from './types'; + +export interface Metadata { + name: string; + url: string; + appColor: string; + hiddenInAppsMenu: boolean; + resources: unknown; +} + +export interface StudyMetadata extends Metadata { + name: 'Study'; + predefinedEquipmentProperties: { + [networkElementType: string]: PredefinedProperties; + }; +} diff --git a/src/utils/UserManagerMock.ts b/src/utils/UserManagerMock.ts index d9a33c82..1abe35d5 100644 --- a/src/utils/UserManagerMock.ts +++ b/src/utils/UserManagerMock.ts @@ -5,20 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -class Events { - userLoadedCallbacks: ((data: any) => void)[] = []; - addUserLoaded(callback: (data: any) => void) { - this.userLoadedCallbacks.push(callback); - } - - addSilentRenewError(callback: (data: any) => void) { - // Nothing to do - } -} +import Events from './Events'; -export class UserManagerMock { +export default class UserManagerMock { settings; + events; + user = { profile: { name: 'John Doe', email: 'Jhon.Doe@rte-france.com' }, id_token: @@ -52,7 +45,7 @@ export class UserManagerMock { this.events = new Events(); } - getUser() { + static getUser() { return Promise.resolve( JSON.parse( sessionStorage.getItem('powsybl-gridsuite-mock-user') ?? 'null' @@ -78,7 +71,7 @@ export class UserManagerMock { return Promise.resolve(localStorageUser); } - signinSilentCallback() { + static signinSilentCallback() { console.error( 'Unsupported, iframe signinSilentCallback in UserManagerMock (dev mode)' ); @@ -94,12 +87,13 @@ export class UserManagerMock { return Promise.resolve(null); } - signoutRedirect() { + static signoutRedirect() { sessionStorage.removeItem('powsybl-gridsuite-mock-user'); localStorage.removeItem('powsybl-gridsuite-mock-user'); window.location.href = '.'; return Promise.resolve(null); } + signinRedirectCallback() { sessionStorage.setItem( 'powsybl-gridsuite-mock-user', diff --git a/src/utils/algos.ts b/src/utils/algos.ts index bc966e88..9292edac 100644 --- a/src/utils/algos.ts +++ b/src/utils/algos.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export function equalsArray(a: Array, b: Array) { +function equalsArray(a: Array, b: Array) { if (b === a) { return true; } @@ -16,7 +16,7 @@ export function equalsArray(a: Array, b: Array) { return false; } - for (var i = 0, l = a.length; i < l; i++) { + for (let i = 0, l = a.length; i < l; i += 1) { if (a[i] instanceof Array && b[i] instanceof Array) { if (!equalsArray(a[i], b[i])) { return false; @@ -28,3 +28,5 @@ export function equalsArray(a: Array, b: Array) { } return true; } + +export default equalsArray; diff --git a/src/utils/equipment-types-for-predefined-properties-mapper.ts b/src/utils/equipment-types-for-predefined-properties-mapper.ts index 9974b6f2..f352419d 100644 --- a/src/utils/equipment-types-for-predefined-properties-mapper.ts +++ b/src/utils/equipment-types-for-predefined-properties-mapper.ts @@ -6,7 +6,7 @@ */ import { EquipmentType } from './types'; -export const mapEquipmentTypeForPredefinedProperties = ( +const mapEquipmentTypeForPredefinedProperties = ( type: EquipmentType ): string | undefined => { switch (type) { @@ -34,5 +34,9 @@ export const mapEquipmentTypeForPredefinedProperties = ( case 'STATIC_VAR_COMPENSATOR': case 'VSC_CONVERTER_STATION': return undefined; + default: + return undefined; } }; + +export default mapEquipmentTypeForPredefinedProperties; diff --git a/src/utils/field-constants.ts b/src/utils/field-constants.ts index 85ad7f3c..8639b9e0 100644 --- a/src/utils/field-constants.ts +++ b/src/utils/field-constants.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export enum FieldConstants { +enum FieldConstants { ID = 'id', NAME = 'name', FILTER_TYPE = 'filterType', @@ -44,3 +44,5 @@ export enum FieldConstants { PROPERTY_VALUES = 'propertyValues', PROPERTY_OPERATOR = 'propertyOperator', } + +export default FieldConstants; diff --git a/src/utils/functions.ts b/src/utils/functions.ts index a69f668b..b7e997fd 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -11,11 +11,14 @@ */ export function keyGenerator() { let key = 1; - return () => key++; + return () => { + key += 1; + return key; + }; } export const areArrayElementsUnique = (array: unknown[]) => { - let uniqueValues = [...new Set(array)]; + const uniqueValues = [...new Set(array)]; return uniqueValues.length === array.length; }; diff --git a/src/utils/styles.ts b/src/utils/styles.ts index ca877f21..83c10467 100644 --- a/src/utils/styles.ts +++ b/src/utils/styles.ts @@ -6,8 +6,7 @@ */ import { SxProps } from '@mui/material'; -//TODO do we need to export this to clients (index.ts) ? - +// TODO do we need to export this to clients (index.ts) ? // like mui sx(slot)/class merging but simpler with less features // TODO use their system ? But it's named unstable_composeClasses so not supported? export const makeComposeClasses = diff --git a/src/utils/types.ts b/src/utils/types.ts index a71bc677..70f3e0ff 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -57,8 +57,7 @@ export type Equipment = | typeof TwoWindingTransfo | typeof ThreeWindingTransfo | typeof ShuntCompensator - | typeof VoltageLevel - | typeof Substation; + | typeof VoltageLevel; export type EquipmentType = { [Type in Equipment['type']]: Type; diff --git a/src/utils/yup-config.ts b/src/utils/yup-config.ts index 4209b421..66d8ed3a 100644 --- a/src/utils/yup-config.ts +++ b/src/utils/yup-config.ts @@ -13,9 +13,8 @@ yup.setLocale({ notType: ({ type }) => { if (type === 'number') { return 'YupNotTypeNumber'; - } else { - return 'YupNotTypeDefault'; } + return 'YupNotTypeDefault'; }, }, }); From d2274eedaad5d1bed99842a39c4bc71698532928 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Fri, 7 Jun 2024 10:45:19 +0200 Subject: [PATCH 02/16] Fix license headers Signed-off-by: Ayoub LABIDI --- demo/src/app.jsx | 13 +- src/components/Login/Login.tsx | 3 +- .../KeyedColumnsRowIndexer.tsx | 5 +- .../MuiVirtualizedTable.tsx | 675 +++++++++--------- src/components/OverflowableText/index.ts | 6 + src/utils/MetaData.ts | 6 + 6 files changed, 359 insertions(+), 349 deletions(-) diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 070f9750..0455067f 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2020, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ /* eslint-disable func-names */ /* eslint-disable no-nested-ternary */ /* eslint-disable no-return-assign */ @@ -9,13 +15,6 @@ /* eslint-disable @typescript-eslint/no-shadow */ /* eslint-disable react/jsx-no-bind */ /* eslint-disable react/prop-types */ -/** - * Copyright (c) 2020, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - import { useCallback, useEffect, useRef, useState } from 'react'; import { diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index 44f32ec0..c4219db8 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -1,11 +1,10 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ /** * Copyright (c) 2020, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - +/* eslint-disable jsx-a11y/anchor-is-valid */ import { Avatar, Box, diff --git a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx index 1ffbeb71..ff0fb4a6 100644 --- a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx +++ b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx @@ -1,12 +1,11 @@ -/* eslint-disable no-param-reassign */ -/* eslint-disable @typescript-eslint/no-unused-vars */ /** * Copyright (c) 2022, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - +/* eslint-disable no-param-reassign */ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { ReactElement } from 'react'; import { ColumnProps } from 'react-virtualized'; import equalsArray from '../../utils/algos'; diff --git a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx index a310553a..96cb030e 100644 --- a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx +++ b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx @@ -4,13 +4,39 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - +/* eslint-disable react/jsx-curly-brace-presence */ +/* eslint-disable react/jsx-boolean-value */ +/* eslint-disable @typescript-eslint/no-shadow */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable no-restricted-globals */ +/* eslint-disable no-else-return */ +/* eslint-disable react/no-unstable-nested-components */ +/* eslint-disable react/function-component-definition */ +/* eslint-disable react/no-this-in-sfc */ +/* eslint-disable react/require-default-props */ +/* eslint-disable react/no-access-state-in-setstate */ +/* eslint-disable spaced-comment */ +/* eslint-disable object-shorthand */ +/* eslint-disable prefer-destructuring */ +/* eslint-disable prefer-template */ +/* eslint-disable no-return-assign */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable react/static-property-placement */ +/* eslint-disable @typescript-eslint/lines-between-class-members */ +/* eslint-disable react/default-props-match-prop-types */ +/* eslint-disable react/destructuring-assignment */ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable class-methods-use-this */ +/* eslint-disable react/sort-comp */ +/* eslint-disable prefer-const */ +/* eslint-disable no-plusplus */ /** * This class has been taken from 'Virtualized Table' example at https://material-ui.com/components/tables/ */ import { createRef, PureComponent, + ReactElement, ReactNode, MouseEvent, KeyboardEvent, @@ -48,25 +74,22 @@ import { import { ChangeWays, collectibleHelper, - CustomColumnProps, getHelper, KeyedColumnsRowIndexer, - RowProps, } from './KeyedColumnsRowIndexer'; import { ColumnHeader } from './ColumnHeader'; -import { on } from 'events'; function getTextWidth(text: any): number { // re-use canvas object for better performance - const canvas = - // @ts-ignore this is questioning + let canvas = + //@ts-ignore this is questioning getTextWidth.canvas || - // @ts-ignore this is questioning + //@ts-ignore this is questioning (getTextWidth.canvas = document.createElement('canvas')); - const context = canvas.getContext('2d'); + let context = canvas.getContext('2d'); // TODO find a better way to find Material UI style context.font = '14px "Roboto", "Helvetica", "Arial", sans-serif'; - const metrics = context.measureText(text); + let metrics = context.measureText(text); return metrics.width; } @@ -107,7 +130,7 @@ const defaultStyles = { [cssTableRowHover]: {}, [cssTableCell]: { flex: 1, - padding: `${DEFAULT_CELL_PADDING}px`, + padding: DEFAULT_CELL_PADDING + 'px', }, [cssTableCellColor]: {}, [cssNoClick]: { @@ -128,7 +151,7 @@ const defaultTooltipSx = { fontSize: '0.9rem', }; -// TODO do we need to export this to clients (index.js) ? +//TODO do we need to export this to clients (index.js) ? export const generateMuiVirtualizedTableClass = (className: string) => `MuiVirtualizedTable-${className}`; const composeClasses = makeComposeClasses(generateMuiVirtualizedTableClass); @@ -141,9 +164,7 @@ interface AmongChooserProps { id: string; } -function AmongChooser( - props: Readonly> -) { +const AmongChooser = (props: AmongChooserProps) => { const { options, value, setValue, id, onDropDownVisibility } = props; return ( @@ -151,22 +172,20 @@ function AmongChooser( { setValue(newVal); }} onClose={() => onDropDownVisibility(false)} onOpen={() => onDropDownVisibility(true)} options={options} - renderInput={(inputProps) => ( - - )} + renderInput={(props) => } renderTags={(val, getTagsProps) => { return val.map((code, index) => { return ( @@ -176,7 +195,7 @@ function AmongChooser( /> ); -} +}; function makeIndexRecord( viewIndexToModel: number[] | null, @@ -199,6 +218,22 @@ function makeIndexRecord( }; } +export interface CustomColumnProps extends ColumnProps { + sortable?: boolean; + numeric?: boolean; + indexer?: KeyedColumnsRowIndexer; + label: string; + clickable?: boolean; + fractionDigits?: number; + unit?: number; + extra?: ReactElement; + nostat?: boolean; +} + +export interface RowProps { + notClickable?: boolean; +} + const initIndexer = ( props: CustomColumnProps, versionSetter: (version: number) => void @@ -220,7 +255,6 @@ const preFilterData = memoize( rows, filterFromProps, indexer, - // eslint-disable-next-line @typescript-eslint/no-unused-vars filterVersion // filterVersion is unused directly, used only as a workaround just to reset the memoization ) => { return indexer.preFilterRowMapping(columns, rows, filterFromProps); @@ -251,7 +285,7 @@ const reorderIndex = memoize( : indexer.highestCodedColumn(columns); if (sortFromProps && highestCodedColumn) { const colIdx = Math.abs(highestCodedColumn) - 1; - const reorderedIndex = sortFromProps( + let reorderedIndex = sortFromProps( columns[colIdx].dataKey, highestCodedColumn > 0, !!columns[colIdx].numeric @@ -263,8 +297,8 @@ const reorderIndex = memoize( const viewIndexToModel = sortFromProps(null, false, false); return makeIndexRecord(viewIndexToModel, rows); } catch (e) { - // some external sort functions may expect to only be called - // when the user has select a column. Catch their errors and ignore + //some external sort functions may expect to only be called + //when the user has select a column. Catch their errors and ignore console.warn( 'error in external sort. consider adding support for datakey=null in your external sort function' ); @@ -287,8 +321,8 @@ const reorderIndex = memoize( if (filterFromProps) { const viewIndexToModel = rows .map((r: unknown, i: number) => [r, i]) - .filter(([r]: [unknown, number]) => filterFromProps(r)) - .map(([j]: [unknown, number]) => j); + .filter(([r, _]: [unknown, number]) => filterFromProps(r)) + .map(([_, j]: [unknown, number]) => j); return makeIndexRecord(viewIndexToModel, rows); } @@ -297,20 +331,20 @@ const reorderIndex = memoize( ); export interface MuiVirtualizedTableProps extends CustomColumnProps { - headerHeight?: number; + headerHeight: number; columns: CustomColumnProps[]; defersFilterChanges: (() => void) | null; rows: RowProps[]; filter: unknown; sort: unknown; - classes?: Record; + classes: Record; onRowClick?: (event: RowMouseEventHandlerParams) => void; - rowHeight?: number; + rowHeight: number; onCellClick: (row: RowProps, column: ColumnProps) => void; tooltipSx: SxProps; name: string; exportCSVDataKeys: unknown[]; - enableExportCSV?: boolean; + enableExportCSV: boolean; } export interface MuiVirtualizedTableState { @@ -329,98 +363,39 @@ class MuiVirtualizedTable extends PureComponent< MuiVirtualizedTableProps, MuiVirtualizedTableState > { - // eslint-disable-next-line react/static-property-placement static defaultProps = { headerHeight: DEFAULT_HEADER_HEIGHT, rowHeight: DEFAULT_ROW_HEIGHT, enableExportCSV: false, classes: {}, - onRowClick: undefined, - }; - - static readonly computeDataWidth = (text: string) => { - return getTextWidth(text || '') + 2 * DEFAULT_CELL_PADDING; }; headers: MutableRefObject; - observer: IntersectionObserver; - dropDownVisible: boolean; - sizes = memoize((columns, rows, rowGetter) => { - const sizes: Record = {}; - columns.forEach((col: CustomColumnProps) => { - if (col.width) { - sizes[col.dataKey] = col.width; - } else { - /* calculate the header (and min size if exists) - * NB : ignores the possible icons - */ - let size = Math.max( - col.minWidth ?? 0, - MuiVirtualizedTable.computeDataWidth(col.label) - ); - /* calculate for each row the width, and keep the max */ - for (let i = 0; i < rows.length; i += 1) { - const gotRow = rowGetter(i); - const text = MuiVirtualizedTable.getDisplayValue( - col, - gotRow[col.dataKey] - ); - size = Math.max( - size, - MuiVirtualizedTable.computeDataWidth(text) - ); - } - if (col.maxWidth) { - size = Math.min(col.maxWidth, size); - } - sizes[col.dataKey] = Math.ceil(size); - } - }); - return sizes; - }); - - csvHeaders = memoize((columns, exportCSVDataKeys) => { - const tempHeaders: { displayName: string; id: string }[] = []; - columns.forEach((col: CustomColumnProps) => { - if ( - exportCSVDataKeys !== undefined && - exportCSVDataKeys.find((el: string) => el === col.dataKey) - ) { - tempHeaders.push({ - displayName: col.label, - id: col.dataKey, - }); - } - }); - return tempHeaders; - }); - constructor(props: MuiVirtualizedTableProps, context: any) { super(props, context); - this.computeHeaderSize = this.computeHeaderSize.bind(this); - this.registerHeader = this.registerHeader.bind(this); - this.registerObserver = this.registerObserver.bind(this); + this._computeHeaderSize = this._computeHeaderSize.bind(this); + this._registerHeader = this._registerHeader.bind(this); + this._registerObserver = this._registerObserver.bind(this); // we shouldn't use createRef here, just defining an object would be enough // We have to type RefObject to MutableRefObject to enable mutability, and TS enables that... this.headers = createRef(); this.headers.current = {}; - const options = { + let options = { root: null, rootMargin: '0px', threshold: 0.1, }; this.observer = new IntersectionObserver( - this.computeHeaderSize, + this._computeHeaderSize, options ); this.dropDownVisible = false; - const { headerHeight } = this.props; this.state = { - headerHeight, + headerHeight: this.props.headerHeight, indexer: initIndexer(props, this.setVersion), indirectionVersion: 0, popoverAnchorEl: null, @@ -429,13 +404,15 @@ class MuiVirtualizedTable extends PureComponent< }; } - componentDidMount() { - window.addEventListener('resize', this.computeHeaderSize); - } + setVersion = (v: number) => { + this.setState({ indirectionVersion: v }); + }; componentDidUpdate(oldProps: MuiVirtualizedTableProps) { - const { indexer, sortable, headerHeight } = this.props; - if (oldProps.indexer !== indexer || oldProps.sortable !== sortable) { + if ( + oldProps.indexer !== this.props.indexer || + oldProps.sortable !== this.props.sortable + ) { this.setState((state) => { return { indexer: initIndexer(this.props, this.setVersion), @@ -443,158 +420,66 @@ class MuiVirtualizedTable extends PureComponent< }; }); } - if (oldProps.headerHeight !== headerHeight) { - this.computeHeaderSize(); + if (oldProps.headerHeight !== this.props.headerHeight) { + this._computeHeaderSize(); } } + componentDidMount() { + window.addEventListener('resize', this._computeHeaderSize); + } + componentWillUnmount() { - window.removeEventListener('resize', this.computeHeaderSize); + window.removeEventListener('resize', this._computeHeaderSize); this.observer.disconnect(); } - handleKeyDownOnPopover = (evt: KeyboardEvent) => { - if (evt.key === 'Enter' && !this.dropDownVisible) { - this.closePopover(evt, 'enterKeyDown'); - } - }; - - onFilterParamsChange(newVal: unknown[] | null, colKey: string | null) { - const { indexer, indirectionVersion } = this.state; - const { defersFilterChanges } = this.props; - const nonEmpty = newVal?.length === 0 ? null : newVal; - if (defersFilterChanges) { - this.setState({ - deferredFilterChange: { newVal, colKey }, - }); - } else if (indexer?.setColFilterUserParams(colKey, nonEmpty)) { - this.setState({ - indirectionVersion: indirectionVersion + 1, - }); + _registerHeader(label: string, header: unknown) { + if (this.headers.current) { + this.headers.current[label] = header; } } - getCSVData = () => { - const { rows, columns, filter, sort, exportCSVDataKeys } = this.props; - const { indexer, indirectionVersion } = this.state; - const reorderedIndex = reorderIndex( - indexer, - indirectionVersion, - rows, - columns, - filter, - sort - ); - const rowsCount = - reorderedIndex.viewIndexToModel?.length ?? rows.length; - - const csvData = []; - for (let index = 0; index < rowsCount; index += 1) { - const myobj: Record = {}; - const sortedRow: any = reorderedIndex.rowGetter(index); - const exportedKeys = exportCSVDataKeys; - columns.forEach((col) => { - if (exportedKeys?.find((el: any) => el === col.dataKey)) { - myobj[col.dataKey] = sortedRow[col.dataKey]; - } - }); - csvData.push(myobj); + _registerObserver(element: Element) { + if (element !== null) { + this.observer.observe(element); } + } - return Promise.resolve(csvData); - }; - - getCSVFilename = () => { - const { name, columns } = this.props; - if (name?.length > 0) { - return name.replace(/\s/g, '_'); - } - const filename = Object.entries(columns) - .map((p) => p[1].label) - .join('_'); - return filename; + computeDataWidth = (text: string) => { + return getTextWidth(text || '') + 2 * DEFAULT_CELL_PADDING; }; - // type check should be increased here - static getDisplayValue(column: CustomColumnProps, cellData: any) { - let displayedValue: any; - if (!column.numeric) { - displayedValue = cellData; - } else if (Number.isNaN(cellData)) { - displayedValue = ''; - } else if ( - column.fractionDigits === undefined || - column.fractionDigits === 0 - ) { - displayedValue = Math.round(cellData); - } else { - displayedValue = Number(cellData).toFixed(column.fractionDigits); - } - - if (column.unit !== undefined) { - displayedValue += ' '; - displayedValue += column.unit; - } - return displayedValue; - } - - makeSizedTable = ( - height: number, - width: number, - sizes: Record, - reorderedIndex: number[] | null, - rowGetter: ((index: number) => RowProps) | ((index: number) => number) - ) => { - const { sort, onRowClick, ...otherProps } = this.props; - const { headerHeight } = this.state; - return ( - { + let sizes: Record = {}; + columns.forEach((col: CustomColumnProps) => { + if (col.width) { + sizes[col.dataKey] = col.width; + } else { + /* calculate the header (and min size if exists) + * NB : ignores the possible icons + */ + let size = Math.max( + col.minWidth || 0, + this.computeDataWidth(col.label) + ); + /* calculate for each row the width, and keep the max */ + for (let i = 0; i < rows.length; ++i) { + const gotRow = rowGetter(i); + let text = this.getDisplayValue(col, gotRow[col.dataKey]); + size = Math.max(size, this.computeDataWidth(text)); } - rowCount={reorderedIndex?.length ?? otherProps.rows.length} - rowClassName={({ index }) => - this.getRowClassName({ index, rowGetter }) + if (col.maxWidth) { + size = Math.min(col.maxWidth, size); } - rowGetter={({ index }) => rowGetter(index)} - > - {otherProps.columns.map(({ dataKey, ...other }, index) => { - return ( - - ); - })} -
- ); - }; + sizes[col.dataKey] = Math.ceil(size); + } + }); + return sizes; + }); openPopover = (popoverTarget: Element, colKey: string) => { - const { columns } = this.props; - const col = columns.find((c) => c.dataKey === colKey); + const col = this.props.columns.find((c) => c.dataKey === colKey); if (getHelper(col) !== collectibleHelper) { return; } @@ -606,12 +491,18 @@ class MuiVirtualizedTable extends PureComponent< }); }; + handleKeyDownOnPopover = (evt: KeyboardEvent) => { + if (evt.key === 'Enter' && !this.dropDownVisible) { + this.closePopover(evt, 'enterKeyDown'); + } + }; + closePopover = (_: KeyboardEvent, reason: string) => { let bumpsVersion = false; if (reason === 'backdropClick' || reason === 'enterKeyDown') { - bumpsVersion = this.commitFilterChange(); + bumpsVersion = this._commitFilterChange(); } - this.setState((state) => { + this.setState((state, _) => { return { popoverAnchorEl: null, popoverColKey: null, @@ -623,39 +514,32 @@ class MuiVirtualizedTable extends PureComponent< }; makeColumnFilterEditor = () => { - const { columns, rows, filter, defersFilterChanges } = this.props; - const { - popoverColKey, - indexer, - deferredFilterChange, - indirectionVersion, - } = this.state; - const colKey = popoverColKey; - const outerParams = indexer?.getColFilterOuterParams(colKey); + const colKey = this.state.popoverColKey; + const outerParams = this.state.indexer?.getColFilterOuterParams(colKey); const userParams = - !defersFilterChanges || !deferredFilterChange - ? indexer?.getColFilterUserParams(colKey) - : deferredFilterChange.newVal ?? undefined; + !this.props.defersFilterChanges || !this.state.deferredFilterChange + ? this.state.indexer?.getColFilterUserParams(colKey) + : this.state.deferredFilterChange.newVal ?? undefined; const prefiltered = preFilterData( - columns, - rows, - filter, - indexer, - indirectionVersion + this.props.columns, + this.props.rows, + this.props.filter, + this.state.indexer, + this.state.indirectionVersion ); - const options: any[] = []; + let options = []; if (outerParams) { options.push(...outerParams); } // @ts-ignore colKey could be null, how to handle this ? const colStat = prefiltered?.colsStats?.[colKey]; if (colStat?.seen) { - Object.keys(colStat.seen).forEach((key) => { + for (const key of Object.getOwnPropertyNames(colStat.seen)) { if (options.findIndex((o) => o === key) < 0) { options.push(key); } - }); + } } options.sort(); @@ -663,26 +547,25 @@ class MuiVirtualizedTable extends PureComponent< { this.onFilterParamsChange(newVal, colKey); }} - onDropDownVisibility={(visible) => { - this.dropDownVisible = visible; - }} + onDropDownVisibility={(visible) => + (this.dropDownVisible = visible) + } /> ); }; - commitFilterChange = () => { - const { deferredFilterChange, indexer } = this.state; - if (deferredFilterChange) { - const { colKey } = deferredFilterChange; - let { newVal } = deferredFilterChange; + _commitFilterChange = () => { + if (this.state.deferredFilterChange) { + const colKey = this.state.deferredFilterChange.colKey; + let newVal = this.state.deferredFilterChange.newVal; if (newVal?.length === 0) { newVal = null; } - if (indexer?.setColFilterUserParams(colKey, newVal)) { + if (this.state.indexer?.setColFilterUserParams(colKey, newVal)) { return true; } } @@ -690,18 +573,26 @@ class MuiVirtualizedTable extends PureComponent< return false; }; - setVersion = (v: number) => { - this.setState({ indirectionVersion: v }); - }; + onFilterParamsChange(newVal: unknown[] | null, colKey: string | null) { + const nonEmpty = newVal?.length === 0 ? null : newVal; + if (this.props.defersFilterChanges) { + this.setState({ + deferredFilterChange: { newVal: newVal, colKey }, + }); + } else if ( + this.state.indexer?.setColFilterUserParams(colKey, nonEmpty) + ) { + this.setState({ + indirectionVersion: this.state.indirectionVersion + 1, + }); + } + } sortClickHandler = (evt: MouseEvent, _: unknown, columnIndex: number) => { - const { columns } = this.props; - const colKey = columns[columnIndex].dataKey; - - const { indexer, indirectionVersion } = this.state; + const colKey = this.props.columns[columnIndex].dataKey; if (evt.altKey) { - // @ts-ignore should be currentTarget maybe ? + //@ts-ignore should be currentTarget maybe ? this.openPopover(evt.target, colKey); return; } @@ -715,9 +606,9 @@ class MuiVirtualizedTable extends PureComponent< way = ChangeWays.TAIL; } - if (indexer?.updateSortingFromUser(colKey, way)) { + if (this.state.indexer?.updateSortingFromUser(colKey, way)) { this.setState({ - indirectionVersion: indirectionVersion + 1, + indirectionVersion: this.state.indirectionVersion + 1, }); } }; @@ -727,12 +618,11 @@ class MuiVirtualizedTable extends PureComponent< target: Element | undefined, columnIndex: number ) => { - const { columns } = this.props; // ColumnHeader to (header) TableCell const retargeted = target?.parentNode ?? target; - const colKey = columns[columnIndex].dataKey; - // @ts-ignore still not the good types + const colKey = this.props.columns[columnIndex].dataKey; + //@ts-ignore still not the good types this.openPopover(retargeted, colKey); }; @@ -743,17 +633,17 @@ class MuiVirtualizedTable extends PureComponent< label: string; columnIndex: number; }) => { - const { columns, rows, filter, sort } = this.props; - const { indexer } = this.state; + const { columns } = this.props; + const indexer = this.state.indexer; const colKey = columns[columnIndex].dataKey; const signedRank = indexer?.columnSortingSignedRank(colKey); const userParams = indexer?.getColFilterUserParams(colKey); - const { numeric } = columns[columnIndex]; + const numeric = columns[columnIndex].numeric; const prefiltered = preFilterData( columns, - rows, - filter, + this.props.rows, + this.props.filter, indexer, indexer?.filterVersion ); @@ -775,7 +665,7 @@ class MuiVirtualizedTable extends PureComponent< // - a cellRenderer is defined, as we have no simple way to match for chosen value(s) // - using an external sort, because it would hardly know about the indexer filtering const onFilterClick = - numeric || sort || columns[columnIndex].cellRenderer + numeric || this.props.sort || columns[columnIndex].cellRenderer ? undefined : (ev: MouseEvent, retargeted?: Element) => { this.filterClickHandler(ev, retargeted, columnIndex); @@ -783,7 +673,7 @@ class MuiVirtualizedTable extends PureComponent< return ( this.registerHeader(label, e)} + ref={(e) => this._registerHeader(label, e)} sortSignedRank={signedRank} filterLevel={filterLevel} numeric={numeric ?? false} @@ -802,7 +692,7 @@ class MuiVirtualizedTable extends PureComponent< return (
{ - this.registerHeader(label, element); + this._registerHeader(label, element); }} > {label} @@ -833,14 +723,13 @@ class MuiVirtualizedTable extends PureComponent< }; onClickableRowClick = (event: RowMouseEventHandlerParams) => { - const { onRowClick } = this.props; if ( event.rowData?.notClickable !== true || event.event?.shiftKey || event.event?.ctrlKey ) { - // @ts-ignore onRowClick is possibly undefined - onRowClick(event); + //@ts-ignore onRowClick is possibly undefined + this.props.onRowClick(event); } }; @@ -848,7 +737,7 @@ class MuiVirtualizedTable extends PureComponent< const { columns, classes, rowHeight, onCellClick, rows, tooltipSx } = this.props; - const displayedValue = MuiVirtualizedTable.getDisplayValue( + let displayedValue = this.getDisplayValue( columns[columnIndex], cellData ); @@ -895,29 +784,37 @@ class MuiVirtualizedTable extends PureComponent< > ); }; - - registerHeader(label: string, header: unknown) { - if (this.headers.current) { - this.headers.current[label] = header; + // type check should be increased here + getDisplayValue(column: CustomColumnProps, cellData: any) { + let displayedValue: any; + if (!column.numeric) { + displayedValue = cellData; + } else if (isNaN(cellData)) { + displayedValue = ''; + } else if ( + column.fractionDigits === undefined || + column.fractionDigits === 0 + ) { + displayedValue = Math.round(cellData); + } else { + displayedValue = Number(cellData).toFixed(column.fractionDigits); } - } - registerObserver(element: Element) { - if (element !== null) { - this.observer.observe(element); + if (column.unit !== undefined) { + displayedValue += ' '; + displayedValue += column.unit; } + return displayedValue; } - computeHeaderSize() { - const { headerHeight: stateHeaderHeight } = this.state; - const { headerHeight: propsHeaderHeight } = this.props; + _computeHeaderSize() { const headers = Object.values(this.headers.current); if (headers.length === 0) { return; @@ -926,21 +823,20 @@ class MuiVirtualizedTable extends PureComponent< // though it can not make a difference from clientHeight, // as overflow-y as no scroll value const scrollHeights = headers.map((header: any) => header.scrollHeight); - const computedHeaderHeight = Math.max( + let headerHeight = Math.max( Math.max(...scrollHeights) + DEFAULT_CELL_PADDING, - propsHeaderHeight + this.props.headerHeight // hides (most often) padding override by forcing height ); - if (computedHeaderHeight !== stateHeaderHeight) { + if (headerHeight !== this.state.headerHeight) { this.setState({ - headerHeight: computedHeaderHeight, + headerHeight: headerHeight, }); } } makeHeaderRenderer(dataKey: string, columnIndex: number) { - const { columns, classes, sortable } = this.props; - const { indexer, headerHeight } = this.state; + const { columns, classes } = this.props; return (headerProps: any) => { return ( this.registerObserver(e)} + ref={(e: Element) => this._registerObserver(e)} > - {sortable && indexer + {this.props.sortable && this.state.indexer ? this.sortableHeader({ ...headerProps, columnIndex, @@ -973,29 +869,134 @@ class MuiVirtualizedTable extends PureComponent< }; } + makeSizedTable = ( + height: number, + width: number, + sizes: Record, + reorderedIndex: number[] | null, + rowGetter: ((index: number) => RowProps) | ((index: number) => number) + ) => { + const { sort, ...otherProps } = this.props; + + return ( + + this.getRowClassName({ index, rowGetter }) + } + rowGetter={({ index }) => rowGetter(index)} + > + {otherProps.columns.map(({ dataKey, ...other }, index) => { + return ( + + ); + })} +
+ ); + }; + + getCSVFilename = () => { + if (this.props.name?.length > 0) { + return this.props.name.replace(/\s/g, '_'); + } else { + let filename = Object.entries(this.props.columns) + .map((p) => p[1].label) + .join('_'); + return filename; + } + }; + + getCSVData = () => { + let reorderedIndex = reorderIndex( + this.state.indexer, + this.state.indirectionVersion, + this.props.rows, + this.props.columns, + this.props.filter, + this.props.sort + ); + let rowsCount = + reorderedIndex.viewIndexToModel?.length ?? this.props.rows.length; + + const csvData = []; + for (let index = 0; index < rowsCount; ++index) { + const myobj: Record = {}; + const sortedRow: any = reorderedIndex.rowGetter(index); + const exportedKeys = this.props.exportCSVDataKeys; + this.props.columns.forEach((col) => { + if (exportedKeys?.find((el: any) => el === col.dataKey)) { + myobj[col.dataKey] = sortedRow[col.dataKey]; + } + }); + csvData.push(myobj); + } + + return Promise.resolve(csvData); + }; + + csvHeaders = memoize((columns, exportCSVDataKeys) => { + let tempHeaders: { displayName: string; id: string }[] = []; + columns.forEach((col: CustomColumnProps) => { + if ( + exportCSVDataKeys !== undefined && + exportCSVDataKeys.find((el: string) => el === col.dataKey) + ) { + tempHeaders.push({ + displayName: col.label, + id: col.dataKey, + }); + } + }); + return tempHeaders; + }); + render() { - const { - rows, - columns, - filter, - sort, - exportCSVDataKeys, - className, - enableExportCSV, - } = this.props; - - const { indexer, indirectionVersion, popoverAnchorEl } = this.state; const { viewIndexToModel, rowGetter } = reorderIndex( - indexer, - indirectionVersion, - rows, - columns, - filter, - sort + this.state.indexer, + this.state.indirectionVersion, + this.props.rows, + this.props.columns, + this.props.filter, + this.props.sort ); - const sizes = this.sizes(columns, rows, rowGetter); - const csvHeaders = this.csvHeaders(columns, exportCSVDataKeys); + const sizes = this.sizes( + this.props.columns, + this.props.rows, + rowGetter + ); + const csvHeaders = this.csvHeaders( + this.props.columns, + this.props.exportCSVDataKeys + ); return (
- {enableExportCSV && ( + {this.props.enableExportCSV && (
- {popoverAnchorEl && ( + {this.state.popoverAnchorEl && ( {this.makeColumnFilterEditor()} diff --git a/src/components/OverflowableText/index.ts b/src/components/OverflowableText/index.ts index 72518eb0..603c4325 100644 --- a/src/components/OverflowableText/index.ts +++ b/src/components/OverflowableText/index.ts @@ -1,2 +1,8 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ // eslint-disable-next-line import/prefer-default-export export { default as OverflowableText } from './overflowable-text'; diff --git a/src/utils/MetaData.ts b/src/utils/MetaData.ts index bf65b37f..796b9875 100644 --- a/src/utils/MetaData.ts +++ b/src/utils/MetaData.ts @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ import { PredefinedProperties } from './types'; export interface Metadata { From 5ca8d7989f48289cd0501a3bd65ac79cc42e5ed2 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Fri, 7 Jun 2024 11:16:21 +0200 Subject: [PATCH 03/16] Fix CI & add airbnb hooks rules Signed-off-by: Ayoub LABIDI --- .eslintrc.json | 6 ++++-- .../inputs/react-hook-form/error-management/error-input.tsx | 2 +- .../inputs/react-query-builder/combinator-selector.tsx | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index fe70018e..524a7eb2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,6 +3,7 @@ "extends": [ "airbnb", "airbnb-typescript", + "airbnb/hooks", "plugin:prettier/recommended" ], "parserOptions": { @@ -11,8 +12,9 @@ "ignorePatterns": [ // node_modules is implicitly always ignored "dist", - "jest.setup.ts", - "jest.config.ts" + "vite.config.mts", + "jest.config.ts", + "jest.setup.ts" ], "rules": { "prettier/prettier": "warn", diff --git a/src/components/inputs/react-hook-form/error-management/error-input.tsx b/src/components/inputs/react-hook-form/error-management/error-input.tsx index efbc6784..72bea3f2 100644 --- a/src/components/inputs/react-hook-form/error-management/error-input.tsx +++ b/src/components/inputs/react-hook-form/error-management/error-input.tsx @@ -57,7 +57,7 @@ function ErrorInput({ name, InputField }: Readonly) { if (error && errorRef.current) { errorRef.current.scrollIntoView({ behavior: 'smooth' }); } - }, [isSubmitting]); + }, [error, isSubmitting]); if (error?.message) { return ( diff --git a/src/components/inputs/react-query-builder/combinator-selector.tsx b/src/components/inputs/react-query-builder/combinator-selector.tsx index 75bc5332..5599489a 100644 --- a/src/components/inputs/react-query-builder/combinator-selector.tsx +++ b/src/components/inputs/react-query-builder/combinator-selector.tsx @@ -18,7 +18,7 @@ function CombinatorSelector(props: Readonly) { const handlePopupConfirmation = useCallback(() => { handleOnChange(tempCombinator); setOpenPopup(false); - }, [props, tempCombinator]); + }, [handleOnChange, tempCombinator]); return ( <> From 13832e6a81a95f848b1960faf7316c5f2ee11299 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Fri, 7 Jun 2024 11:50:34 +0200 Subject: [PATCH 04/16] Fix tests Signed-off-by: Ayoub LABIDI --- babel.config.json | 3 + package-lock.json | 377 ++++++++++++++++++++++++++-------------------- package.json | 1 + 3 files changed, 215 insertions(+), 166 deletions(-) diff --git a/babel.config.json b/babel.config.json index 988e8d4b..23889e4e 100644 --- a/babel.config.json +++ b/babel.config.json @@ -5,5 +5,8 @@ ["@babel/preset-react", { "runtime": "automatic" }], "@babel/preset-typescript", "babel-preset-vite" + ], + "plugins": [ + "@babel/plugin-transform-runtime" ] } diff --git a/package-lock.json b/package-lock.json index 738c3192..c6c9d9d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,15 +53,18 @@ "@types/react-virtualized": "^9.21.29", "@types/utf-8-validate": "^5.0.2", "@types/uuid": "^9.0.8", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-react": "^4.2.1", "ag-grid-community": "^31.0.0", "ag-grid-react": "^31.2.0", "babel-eslint": "^10.1.0", + "babel-preset-airbnb": "^5.0.0", "babel-preset-vite": "^1.1.3", "bufferutil": "^4.0.8", "eslint": "^8.57.0", + "eslint-config-airbnb": "^19.0.0", + "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-prettier": "^9.1.0", "eslint-config-react-app": "^7.0.1", "eslint-plugin-flowtype": "^8.0.3", @@ -431,9 +434,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -720,6 +723,43 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-optional-chaining": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", @@ -1698,6 +1738,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-property-mutators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-mutators/-/plugin-transform-property-mutators-7.24.7.tgz", + "integrity": "sha512-5sNoWzdv4AHhbMNCx8Pm8q3gYx4vCjKJnGAZRIm7aymyBHPKI7Nm+nlo23OBOzHs6ORVJfCpoXrEDC5pipBfhQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-react-display-name": { "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", @@ -5520,33 +5575,31 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz", + "integrity": "sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/type-utils": "7.12.0", + "@typescript-eslint/utils": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5554,39 +5607,6 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/experimental-utils": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", @@ -5762,26 +5782,26 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz", + "integrity": "sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/typescript-estree": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5790,16 +5810,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz", + "integrity": "sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -5807,25 +5827,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz", + "integrity": "sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/typescript-estree": "7.12.0", + "@typescript-eslint/utils": "7.12.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -5834,12 +5854,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", + "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -5847,22 +5867,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz", + "integrity": "sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -5874,26 +5894,11 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -5901,81 +5906,39 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz", + "integrity": "sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/typescript-estree": "7.12.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "eslint": "^8.56.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz", + "integrity": "sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.12.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -6765,6 +6728,34 @@ "@types/babel__core": "^7.1.12" } }, + "node_modules/babel-preset-airbnb": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-airbnb/-/babel-preset-airbnb-5.0.0.tgz", + "integrity": "sha512-Y5nqHhnhu4RpwbmQj4H+srdk1kb413pX81PfJsT1IZQOuEuRzUDXmgN4Ut1GgpQJnfRpjjEuQy0/uzcLMMP1cQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-numeric-separator": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.9.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.9.0", + "@babel/plugin-transform-classes": "^7.9.2", + "@babel/plugin-transform-exponentiation-operator": "^7.8.3", + "@babel/plugin-transform-member-expression-literals": "^7.8.3", + "@babel/plugin-transform-property-literals": "^7.8.3", + "@babel/plugin-transform-property-mutators": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.9.0", + "@babel/plugin-transform-template-literals": "^7.8.3", + "@babel/preset-env": "^7.9.0", + "@babel/preset-react": "^7.9.4", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@babel/runtime": "^7.0.0" + } + }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", @@ -8050,6 +8041,60 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-18.0.0.tgz", + "integrity": "sha512-oc+Lxzgzsu8FQyFVa4QFaVKiitTYiiW3frB9KYW5OWdPrqFc7FzxgB20hP4cHMlr+MBzGcLl3jnCOVOydL9mIg==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + } + }, "node_modules/eslint-config-prettier": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", @@ -12577,9 +12622,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" diff --git a/package.json b/package.json index 5efc2524..b793d0f8 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "ag-grid-community": "^31.0.0", "ag-grid-react": "^31.2.0", "babel-eslint": "^10.1.0", + "babel-preset-airbnb": "^5.0.0", "babel-preset-vite": "^1.1.3", "bufferutil": "^4.0.8", "eslint": "^8.57.0", From 43ff74c89e7c55360071084555d245d6186e2cb9 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Mon, 10 Jun 2024 10:27:29 +0200 Subject: [PATCH 05/16] Fix Signed-off-by: Ayoub LABIDI --- .../KeyedColumnsRowIndexer.tsx | 46 ++++++++----------- src/components/ReportViewer/filter-button.tsx | 2 +- src/components/ReportViewer/log-table.tsx | 2 +- .../ReportViewer/multi-select-list.tsx | 2 +- 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx index ff0fb4a6..8399d129 100644 --- a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx +++ b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx @@ -174,7 +174,7 @@ const codedColumnsFromKeyAndDirection = ( let ret = []; const columIndexByKey: Record = {}; - for (let colIdx = 0; colIdx < columns.length; colIdx + 1) { + for (let colIdx = 0; colIdx < columns.length; colIdx += 1) { const col = columns[colIdx]; const colKey = col.dataKey; columIndexByKey[colKey] = colIdx; @@ -495,11 +495,11 @@ export class KeyedColumnsRowIndexer { } }); - for (let rowIdx = 0; rowIdx < rows.length; rowIdx + 1) { + for (let rowIdx = 0; rowIdx < rows.length; rowIdx += 1) { const row = rows[rowIdx]; let acceptsRow = true; const acceptedOnRow: Record = {}; - for (let colIdx = 0; colIdx < columns.length; colIdx + 1) { + for (let colIdx = 0; colIdx < columns.length; colIdx += 1) { const col = columns[colIdx]; const helper = getHelper(col); const colKey = col.dataKey; @@ -636,30 +636,24 @@ export class KeyedColumnsRowIndexer { } else { this.sortingState?.splice(wasAtIdx, 1); } - } else { - // AMEND - // eslint-disable-next-line no-lonely-if - if (wasAtIdx < 0) { - if ( - this.lastUsedRank - 1 > - // @ts-ignore could be undefined, how to handle this case ? - this.sortingState.length - ) { - return false; - } - this.sortingState?.splice(this.lastUsedRank - 1, 0, [ - colKey, - canonicalForSign(1), - ]); - } else if (!(this.isThreeState && wasSignDir === -1)) { - // @ts-ignore could be null but hard to handle with such accesses - this.sortingState[wasAtIdx][1] = canonicalForSign( - -wasSignDir - ); - } else { - this.lastUsedRank = wasAtIdx + 1; - this.sortingState?.splice(wasAtIdx, 1); + } else if (wasAtIdx < 0) { + if ( + this.lastUsedRank - 1 > + // @ts-ignore could be undefined, how to handle this case ? + this.sortingState.length + ) { + return false; } + this.sortingState?.splice(this.lastUsedRank - 1, 0, [ + colKey, + canonicalForSign(1), + ]); + } else if (!(this.isThreeState && wasSignDir === -1)) { + // @ts-ignore could be null but hard to handle with such accesses + this.sortingState[wasAtIdx][1] = canonicalForSign(-wasSignDir); + } else { + this.lastUsedRank = wasAtIdx + 1; + this.sortingState?.splice(wasAtIdx, 1); } } this.bumpVersion(); diff --git a/src/components/ReportViewer/filter-button.tsx b/src/components/ReportViewer/filter-button.tsx index 14613766..dd24d0be 100644 --- a/src/components/ReportViewer/filter-button.tsx +++ b/src/components/ReportViewer/filter-button.tsx @@ -45,7 +45,7 @@ export interface FilterButtonProps { export function FilterButton({ selectedItems, setSelectedItems, -}: FilterButtonProps) { +}: Readonly) { const [initialState] = useState(selectedItems); const [anchorEl, setAnchorEl] = useState(null); diff --git a/src/components/ReportViewer/log-table.tsx b/src/components/ReportViewer/log-table.tsx index cd4b0c22..1b0e686a 100644 --- a/src/components/ReportViewer/log-table.tsx +++ b/src/components/ReportViewer/log-table.tsx @@ -50,7 +50,7 @@ function LogTable({ onRowClick, selectedSeverity, setSelectedSeverity, -}: LogTableProps) { +}: Readonly) { const intl = useIntl(); const theme = useTheme(); diff --git a/src/components/ReportViewer/multi-select-list.tsx b/src/components/ReportViewer/multi-select-list.tsx index 2522cc3a..dae99486 100644 --- a/src/components/ReportViewer/multi-select-list.tsx +++ b/src/components/ReportViewer/multi-select-list.tsx @@ -45,7 +45,7 @@ export function MultiSelectList({ handleChange, handleClose, anchor, -}: MultiSelectListProps) { +}: Readonly) { const open = Boolean(anchor); return ( From f3422bcfd6a9bf28ba501965be877bb5c8a8fd98 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Tue, 11 Jun 2024 08:58:04 +0200 Subject: [PATCH 06/16] Requested changes Signed-off-by: Ayoub LABIDI --- .eslintrc.json | 1 - demo/src/TableTab.jsx | 1 - demo/src/app.jsx | 2 - demo/src/right-resizable-box.jsx | 3 - package-lock.json | 64 ++++++++++++------- package.json | 9 ++- .../directory-item-selector.tsx | 8 ++- .../MuiVirtualizedTable/ColumnHeader.tsx | 1 - .../MuiVirtualizedTable.tsx | 1 - src/components/ReportViewer/log-table.tsx | 1 - src/components/ReportViewer/report-item.tsx | 1 - src/components/ReportViewer/report-viewer.tsx | 7 +- src/components/TopBar/TopBar.test.tsx | 1 - src/components/TopBar/TopBar.tsx | 1 - .../TreeViewFinder/TreeViewFinder.tsx | 2 - .../criteria-based/filter-properties.tsx | 2 +- .../react-hook-form/unique-name-input.tsx | 4 +- .../react-hook-form/utils/field-label.tsx | 5 -- .../custom-react-query-builder.tsx | 5 +- .../react-query-builder/value-editor.tsx | 3 +- src/hooks/useSnackMessage.ts | 5 +- 21 files changed, 60 insertions(+), 67 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 524a7eb2..8f1b32d1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,7 +22,6 @@ "no-console": "off", "react/jsx-props-no-spreading": "off", "react/react-in-jsx-scope": "off", - "no-shadow": "off", "@typescript-eslint/no-shadow": "error" } } diff --git a/demo/src/TableTab.jsx b/demo/src/TableTab.jsx index 518c3c04..012ff40d 100644 --- a/demo/src/TableTab.jsx +++ b/demo/src/TableTab.jsx @@ -6,7 +6,6 @@ */ import { useCallback, useMemo, useState } from 'react'; -// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { Box, diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 0455067f..3460607d 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -33,9 +33,7 @@ import { ThemeProvider, Typography, } from '@mui/material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; -// eslint-disable-next-line import/no-extraneous-dependencies import { useMatch } from 'react-router'; import { IntlProvider, useIntl } from 'react-intl'; import { BrowserRouter, useLocation, useNavigate } from 'react-router-dom'; diff --git a/demo/src/right-resizable-box.jsx b/demo/src/right-resizable-box.jsx index 369a89cd..933466dd 100644 --- a/demo/src/right-resizable-box.jsx +++ b/demo/src/right-resizable-box.jsx @@ -7,13 +7,10 @@ import { useState } from 'react'; import { MoreVert as ResizePanelHandleIcon } from '@mui/icons-material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { ResizableBox } from 'react-resizable'; -// eslint-disable-next-line import/no-extraneous-dependencies import { useWindowWidth } from '@react-hook/window-size'; import PropTypes from 'prop-types'; import { Box } from '@mui/material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { mergeSx } from '../../src/utils/styles'; diff --git a/package-lock.json b/package-lock.json index c6c9d9d1..fdabe055 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "0.59.0", "license": "MPL-2.0", "dependencies": { + "@mui/system": "^5.15.15", + "@mui/x-tree-view": "^6.17.0", + "@react-hook/window-size": "^3.1.1", "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", "autosuggest-highlight": "^3.3.4", @@ -22,6 +25,8 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-querybuilder": "^7.2.0", + "react-resizable": "^3.0.5", + "react-router": "^6.23.1", "react-virtualized": "^9.22.5", "uuid": "^9.0.1" }, @@ -34,11 +39,10 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@hookform/resolvers": "^3.3.4", + "@jest/globals": "^29.7.0", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", "@mui/material": "^5.15.14", - "@mui/x-tree-view": "^6.17.0", - "@react-hook/window-size": "^3.1.1", "@types/autosuggest-highlight": "^3.2.3", "@types/eslint": "^8.56.7", "@types/eslint-config-prettier": "^6.11.3", @@ -85,7 +89,6 @@ "react-hook-form": "^7.51.2", "react-intl": "^6.6.4", "react-papaparse": "^4.1.0", - "react-resizable": "^3.0.5", "react-router-dom": "^6.22.3", "ts-node": "^10.9.2", "type-fest": "^4.14.0", @@ -4415,7 +4418,6 @@ "version": "6.17.0", "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.17.0.tgz", "integrity": "sha512-09dc2D+Rjg2z8KOaxbUXyPi0aw7fm2jurEtV8Xw48xJ00joLWd5QJm1/v4CarEvaiyhTQzHImNqdgeJW8ZQB6g==", - "dev": true, "dependencies": { "@babel/runtime": "^7.23.2", "@mui/base": "^5.0.0-beta.20", @@ -4545,7 +4547,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@react-hook/debounce/-/debounce-3.0.0.tgz", "integrity": "sha512-ir/kPrSfAzY12Gre0sOHkZ2rkEmM4fS5M5zFxCi4BnCeXh2nvx9Ujd+U4IGpKCuPA+EQD0pg1eK2NGLvfWejag==", - "dev": true, "dependencies": { "@react-hook/latest": "^1.0.2" }, @@ -4557,7 +4558,6 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@react-hook/event/-/event-1.2.6.tgz", "integrity": "sha512-JUL5IluaOdn5w5Afpe/puPa1rj8X6udMlQ9dt4hvMuKmTrBS1Ya6sb4sVgvfe2eU4yDuOfAhik8xhbcCekbg9Q==", - "dev": true, "peerDependencies": { "react": ">=16.8" } @@ -4566,7 +4566,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz", "integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==", - "dev": true, "peerDependencies": { "react": ">=16.8" } @@ -4575,7 +4574,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@react-hook/throttle/-/throttle-2.2.0.tgz", "integrity": "sha512-LJ5eg+yMV8lXtqK3lR+OtOZ2WH/EfWvuiEEu0M3bhR7dZRfTyEJKxH1oK9uyBxiXPtWXiQggWbZirMCXam51tg==", - "dev": true, "dependencies": { "@react-hook/latest": "^1.0.2" }, @@ -4587,7 +4585,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@react-hook/window-size/-/window-size-3.1.1.tgz", "integrity": "sha512-yWnVS5LKnOUIrEsI44oz3bIIUYqflamPL27n+k/PC//PsX/YeWBky09oPeAoc9As6jSH16Wgo8plI+ECZaHk3g==", - "dev": true, "dependencies": { "@react-hook/debounce": "^3.0.0", "@react-hook/event": "^1.2.1", @@ -6867,12 +6864,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -9245,9 +9242,9 @@ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -13585,7 +13582,6 @@ "version": "4.4.6", "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", - "dev": true, "dependencies": { "clsx": "^1.1.1", "prop-types": "^15.8.1" @@ -13599,7 +13595,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "dev": true, "engines": { "node": ">=6" } @@ -13761,7 +13756,6 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", - "dev": true, "dependencies": { "prop-types": "15.x", "react-draggable": "^4.0.3" @@ -13771,12 +13765,11 @@ } }, "node_modules/react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", - "dev": true, + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", + "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==", "dependencies": { - "@remix-run/router": "1.15.3" + "@remix-run/router": "1.16.1" }, "engines": { "node": ">=14.0.0" @@ -13802,6 +13795,29 @@ "react-dom": ">=16.8" } }, + "node_modules/react-router-dom/node_modules/react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dev": true, + "dependencies": { + "@remix-run/router": "1.15.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router/node_modules/@remix-run/router": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", + "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/package.json b/package.json index b793d0f8..911abcdd 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "licenses-check": "license-checker --summary --excludePrivatePackages --production --onlyAllow \"$( jq -r .onlyAllow[] license-checker-config.json | tr '\n' ';')\" --excludePackages \"$( jq -r .excludePackages[] license-checker-config.json | tr '\n' ';')\"" }, "dependencies": { + "@mui/system": "^5.15.15", + "@mui/x-tree-view": "^6.17.0", "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", "autosuggest-highlight": "^3.3.4", @@ -41,6 +43,9 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-querybuilder": "^7.2.0", + "react-router": "^6.23.1", + "react-resizable": "^3.0.5", + "@react-hook/window-size": "^3.1.1", "react-virtualized": "^9.22.5", "uuid": "^9.0.1" }, @@ -74,8 +79,6 @@ "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", "@mui/material": "^5.15.14", - "@mui/x-tree-view": "^6.17.0", - "@react-hook/window-size": "^3.1.1", "@types/autosuggest-highlight": "^3.2.3", "@types/eslint": "^8.56.7", "@types/eslint-config-prettier": "^6.11.3", @@ -122,7 +125,6 @@ "react-hook-form": "^7.51.2", "react-intl": "^6.6.4", "react-papaparse": "^4.1.0", - "react-resizable": "^3.0.5", "react-router-dom": "^6.22.3", "ts-node": "^10.9.2", "type-fest": "^4.14.0", @@ -133,6 +135,7 @@ "vite-plugin-eslint": "^1.8.1", "vite-plugin-lib-inject-css": "^2.0.1", "vite-plugin-svgr": "^4.2.0", + "@jest/globals": "^29.7.0", "yup": "^1.4.0" }, "author": "gridsuite team", diff --git a/src/components/DirectoryItemSelector/directory-item-selector.tsx b/src/components/DirectoryItemSelector/directory-item-selector.tsx index e32bfe58..ba85f46e 100644 --- a/src/components/DirectoryItemSelector/directory-item-selector.tsx +++ b/src/components/DirectoryItemSelector/directory-item-selector.tsx @@ -332,7 +332,10 @@ function DirectoryItemSelector({ } }, [open, updateRootDirectories, expanded, fetchDirectory]); - function sortHandlingDirectories(a: any, b: any): number { + function sortHandlingDirectories( + a: TreeViewFinderNodeProps, + b: TreeViewFinderNodeProps + ): number { // If children property is set it means it's a directory, they are handled differently in order to keep them at the top of the list if (a.children && !b.children) { return -1; @@ -346,8 +349,7 @@ function DirectoryItemSelector({ return ( void} - // eslint-disable-next-line react/jsx-no-bind - sortMethod={sortHandlingDirectories} + sortMethod={(a, b) => sortHandlingDirectories(a, b)} multiSelect // defaulted to true open={open} expanded={expanded as string[]} diff --git a/src/components/MuiVirtualizedTable/ColumnHeader.tsx b/src/components/MuiVirtualizedTable/ColumnHeader.tsx index dccaf056..907a7669 100644 --- a/src/components/MuiVirtualizedTable/ColumnHeader.tsx +++ b/src/components/MuiVirtualizedTable/ColumnHeader.tsx @@ -22,7 +22,6 @@ import { FilterAltOutlined as FilterAltOutlinedIcon, } from '@mui/icons-material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { Box, BoxProps, Theme } from '@mui/material'; import { mergeSx } from '../../utils/styles'; diff --git a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx index 96cb030e..73ace525 100644 --- a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx +++ b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx @@ -54,7 +54,6 @@ import { TableCell, TextField, } from '@mui/material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { GetApp as GetAppIcon } from '@mui/icons-material'; import { diff --git a/src/components/ReportViewer/log-table.tsx b/src/components/ReportViewer/log-table.tsx index 1b0e686a..290162f1 100644 --- a/src/components/ReportViewer/log-table.tsx +++ b/src/components/ReportViewer/log-table.tsx @@ -7,7 +7,6 @@ import { memo, useCallback, useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; import { TableCell, Theme, useTheme } from '@mui/material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { MuiVirtualizedTable } from '../MuiVirtualizedTable'; import { FilterButton } from './filter-button'; diff --git a/src/components/ReportViewer/report-item.tsx b/src/components/ReportViewer/report-item.tsx index a1c5fec7..1f5a4814 100644 --- a/src/components/ReportViewer/report-item.tsx +++ b/src/components/ReportViewer/report-item.tsx @@ -7,7 +7,6 @@ import { PropsWithChildren, ReactNode, useContext } from 'react'; import { Box, Theme, Typography } from '@mui/material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { alpha, styled } from '@mui/system'; import { TreeItem, TreeItemProps } from '@mui/lab'; import { Label } from '@mui/icons-material'; diff --git a/src/components/ReportViewer/report-viewer.tsx b/src/components/ReportViewer/report-viewer.tsx index 76d600b6..6e4f670c 100644 --- a/src/components/ReportViewer/report-viewer.tsx +++ b/src/components/ReportViewer/report-viewer.tsx @@ -19,7 +19,6 @@ import { ArrowRight as ArrowRightIcon, } from '@mui/icons-material'; import { Grid } from '@mui/material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { TreeView } from '@mui/x-tree-view'; import ReportItem from './report-item'; import LogReport from './log-report'; @@ -49,7 +48,7 @@ export interface ReportViewerProps { export default function ReportViewer({ jsonReport, maxSubReports = MAX_SUB_REPORTS, -}: ReportViewerProps) { +}: Readonly) { const [selectedNode, setSelectedNode] = useState(null); const [expandedNodes, setExpandedNodes] = useState([]); const [logs, setLogs] = useState([]); @@ -211,7 +210,3 @@ export default function ReportViewer({ ) ); } - -ReportViewer.defaultProps = { - maxSubReports: MAX_SUB_REPORTS, -}; diff --git a/src/components/TopBar/TopBar.test.tsx b/src/components/TopBar/TopBar.test.tsx index 9596aec2..6859ed06 100644 --- a/src/components/TopBar/TopBar.test.tsx +++ b/src/components/TopBar/TopBar.test.tsx @@ -12,7 +12,6 @@ import { IntlProvider } from 'react-intl'; import { red } from '@mui/material/colors'; import { createTheme, ThemeProvider } from '@mui/material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { beforeEach, afterEach, it, expect } from '@jest/globals'; import TopBar, { LANG_ENGLISH } from './TopBar'; import { top_bar_en } from '../..'; diff --git a/src/components/TopBar/TopBar.tsx b/src/components/TopBar/TopBar.tsx index 8cbe7ee2..23a8e35b 100644 --- a/src/components/TopBar/TopBar.tsx +++ b/src/components/TopBar/TopBar.tsx @@ -41,7 +41,6 @@ import { Settings as SettingsIcon, WbSunny as WbSunnyIcon, } from '@mui/icons-material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { User } from 'oidc-client'; diff --git a/src/components/TreeViewFinder/TreeViewFinder.tsx b/src/components/TreeViewFinder/TreeViewFinder.tsx index a426aac4..52c30177 100644 --- a/src/components/TreeViewFinder/TreeViewFinder.tsx +++ b/src/components/TreeViewFinder/TreeViewFinder.tsx @@ -14,7 +14,6 @@ import React, { } from 'react'; import { useIntl } from 'react-intl'; -// eslint-disable-next-line import/no-extraneous-dependencies import { styled } from '@mui/system'; import { @@ -29,7 +28,6 @@ import { ModalProps, } from '@mui/material'; -// eslint-disable-next-line import/no-extraneous-dependencies import { TreeItem, TreeView, TreeViewClasses } from '@mui/x-tree-view'; import { Check as CheckIcon, diff --git a/src/components/filter/criteria-based/filter-properties.tsx b/src/components/filter/criteria-based/filter-properties.tsx index 83db6cb9..7bcf089d 100644 --- a/src/components/filter/criteria-based/filter-properties.tsx +++ b/src/components/filter/criteria-based/filter-properties.tsx @@ -52,7 +52,7 @@ function propertyValuesTest( if (doublePropertyValues) { return isForLineOrHvdcLine ? values?.length! > 0 : true; } - return isForLineOrHvdcLine ? true : values?.length! > 0; + return isForLineOrHvdcLine || values?.length! > 0; } export const filterPropertiesYupSchema = { diff --git a/src/components/inputs/react-hook-form/unique-name-input.tsx b/src/components/inputs/react-hook-form/unique-name-input.tsx index 7ea823a0..5b914dee 100644 --- a/src/components/inputs/react-hook-form/unique-name-input.tsx +++ b/src/components/inputs/react-hook-form/unique-name-input.tsx @@ -145,9 +145,7 @@ function UniqueNameInput({ e: ChangeEvent ) => { onChange(e.target.value); - if (onManualChangeCallback) { - onManualChangeCallback(); - } + onManualChangeCallback?.(); }; const translatedLabel = ; diff --git a/src/components/inputs/react-hook-form/utils/field-label.tsx b/src/components/inputs/react-hook-form/utils/field-label.tsx index 749b4cad..261c6486 100644 --- a/src/components/inputs/react-hook-form/utils/field-label.tsx +++ b/src/components/inputs/react-hook-form/utils/field-label.tsx @@ -25,9 +25,4 @@ function FieldLabel({ ); } -FieldLabel.defaultProps = { - optional: false, - values: undefined, -}; - export default FieldLabel; diff --git a/src/components/inputs/react-query-builder/custom-react-query-builder.tsx b/src/components/inputs/react-query-builder/custom-react-query-builder.tsx index 71351063..938b9b41 100644 --- a/src/components/inputs/react-query-builder/custom-react-query-builder.tsx +++ b/src/components/inputs/react-query-builder/custom-react-query-builder.tsx @@ -11,6 +11,7 @@ import * as ReactDnD from 'react-dnd'; import * as ReactDndHtml5Backend from 'react-dnd-html5-backend'; import { QueryBuilderMaterial } from '@react-querybuilder/material'; import { + ActionWithRulesAndAddersProps, Field, formatQuery, QueryBuilder, @@ -39,11 +40,11 @@ interface CustomReactQueryBuilderProps { fields: Field[]; } -function RuleAddButton(props: any) { +function RuleAddButton(props: Readonly) { return ; } -function GroupAddButton(props: any) { +function GroupAddButton(props: Readonly) { return ; } diff --git a/src/components/inputs/react-query-builder/value-editor.tsx b/src/components/inputs/react-query-builder/value-editor.tsx index 08a7266b..305b15fe 100644 --- a/src/components/inputs/react-query-builder/value-editor.tsx +++ b/src/components/inputs/react-query-builder/value-editor.tsx @@ -39,10 +39,9 @@ const styles = { }; function ValueEditor(props: Readonly) { - const { field, operator, value } = props; + const { field, operator, value, rule, handleOnChange, inputType } = props; const formContext = useFormContext(); const { getValues } = formContext; - const { rule, handleOnChange, inputType } = props; const itemFilter = useCallback( (filterValue: any) => { if (filterValue?.type === ElementType.FILTER) { diff --git a/src/hooks/useSnackMessage.ts b/src/hooks/useSnackMessage.ts index 2973733d..c920c430 100644 --- a/src/hooks/useSnackMessage.ts +++ b/src/hooks/useSnackMessage.ts @@ -9,7 +9,7 @@ import { MutableRefObject, useCallback } from 'react'; import { BaseVariant, OptionsObject, - closeSnackbar, + closeSnackbar as closeSnackbarFromNotistack, useSnackbar, } from 'notistack'; import { IntlShape } from 'react-intl'; @@ -28,7 +28,7 @@ export interface UseSnackMessageReturn { snackError: (snackInputs: SnackInputs) => void; snackWarning: (snackInputs: SnackInputs) => void; snackInfo: (snackInputs: SnackInputs) => void; - closeSnackbar: typeof closeSnackbar; + closeSnackbar: typeof closeSnackbarFromNotistack; } function checkInputs(txt?: string, id?: string, values?: any) { @@ -89,7 +89,6 @@ function makeMessage( export function useSnackMessage(): UseSnackMessageReturn { const intlRef = useIntlRef(); - // eslint-disable-next-line @typescript-eslint/no-shadow const { enqueueSnackbar, closeSnackbar } = useSnackbar(); const enqueue = useCallback( From 2b560f6da93b7136103e1b5e4208b38ae0ac7623 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Tue, 11 Jun 2024 10:58:30 +0200 Subject: [PATCH 07/16] requested changes second batch Signed-off-by: Ayoub LABIDI --- .../AuthenticationRouter.tsx | 6 +++--- src/components/AuthenticationRouter/index.ts | 5 +++-- src/components/CardErrorBoundary/index.ts | 5 +++-- .../ElementSearchDialog/equipment-item.tsx | 2 +- .../ElementSearchDialog/tag-renderer.tsx | 2 +- src/components/FlatParameters/index.ts | 4 ++-- src/components/Login/index.ts | 5 +++-- src/components/MultipleSelectionDialog/index.ts | 4 ++-- src/components/OverflowableText/index.ts | 5 +++-- src/components/ReportViewer/index.ts | 5 +++-- src/components/ReportViewerDialog/index.ts | 5 +++-- .../ReportViewerDialog/report-viewer-dialog.tsx | 2 +- src/components/SignInCallbackHandler/index.ts | 5 +++-- .../SilentRenewCallbackHandler/index.ts | 5 +++-- src/components/SnackbarProvider/index.ts | 5 +++-- .../react-hook-form/directory-items-input.tsx | 2 +- src/index.ts | 16 ++++++++-------- 17 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/components/AuthenticationRouter/AuthenticationRouter.tsx b/src/components/AuthenticationRouter/AuthenticationRouter.tsx index b9f0e897..8bab1f4a 100644 --- a/src/components/AuthenticationRouter/AuthenticationRouter.tsx +++ b/src/components/AuthenticationRouter/AuthenticationRouter.tsx @@ -10,15 +10,15 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import { Alert, AlertTitle, Grid } from '@mui/material'; import { FormattedMessage } from 'react-intl'; import { UserManager } from 'oidc-client'; -import { SignInCallbackHandler } from '../SignInCallbackHandler'; +import SignInCallbackHandler from '../SignInCallbackHandler'; import { handleSigninCallback, handleSilentRenewCallback, login, logout, } from '../../utils/AuthService'; -import { SilentRenewCallbackHandler } from '../SilentRenewCallbackHandler'; -import { Login } from '../Login'; +import SilentRenewCallbackHandler from '../SilentRenewCallbackHandler'; +import Login from '../Login'; import Logout from '../Login/Logout'; export interface AuthenticationRouterProps { diff --git a/src/components/AuthenticationRouter/index.ts b/src/components/AuthenticationRouter/index.ts index c4e7b24b..c1d7640f 100644 --- a/src/components/AuthenticationRouter/index.ts +++ b/src/components/AuthenticationRouter/index.ts @@ -4,5 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// eslint-disable-next-line import/prefer-default-export -export { default as AuthenticationRouter } from './AuthenticationRouter'; +import defaultAuthenticationRouter from './AuthenticationRouter'; + +export default defaultAuthenticationRouter; diff --git a/src/components/CardErrorBoundary/index.ts b/src/components/CardErrorBoundary/index.ts index 0b3d64a5..8b9f6938 100644 --- a/src/components/CardErrorBoundary/index.ts +++ b/src/components/CardErrorBoundary/index.ts @@ -4,5 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// eslint-disable-next-line import/prefer-default-export -export { default as CardErrorBoundary } from './card-error-boundary'; +import defaultCardErrorBoundary from './card-error-boundary'; + +export default defaultCardErrorBoundary; diff --git a/src/components/ElementSearchDialog/equipment-item.tsx b/src/components/ElementSearchDialog/equipment-item.tsx index 6a5bcde6..ec04dc33 100644 --- a/src/components/ElementSearchDialog/equipment-item.tsx +++ b/src/components/ElementSearchDialog/equipment-item.tsx @@ -11,7 +11,7 @@ import { FormattedMessage } from 'react-intl'; import { Box, SxProps } from '@mui/material'; import { EQUIPMENT_TYPE, EquipmentInfos } from '../../utils/EquipmentType'; import { TagRenderer } from './index'; -import { OverflowableText } from '../OverflowableText'; +import OverflowableText from '../OverflowableText'; import { mergeSx } from '../../utils/styles'; export interface EquipmentItemProps { diff --git a/src/components/ElementSearchDialog/tag-renderer.tsx b/src/components/ElementSearchDialog/tag-renderer.tsx index d7ef5cde..20adfcdb 100644 --- a/src/components/ElementSearchDialog/tag-renderer.tsx +++ b/src/components/ElementSearchDialog/tag-renderer.tsx @@ -6,7 +6,7 @@ */ import clsx from 'clsx'; import { SxProps } from '@mui/material'; -import { OverflowableText } from '../OverflowableText'; +import OverflowableText from '../OverflowableText'; import { EQUIPMENT_TYPE, EquipmentType } from '../../utils/EquipmentType'; import { mergeSx } from '../../utils/styles'; diff --git a/src/components/FlatParameters/index.ts b/src/components/FlatParameters/index.ts index 527c4ffc..01205196 100644 --- a/src/components/FlatParameters/index.ts +++ b/src/components/FlatParameters/index.ts @@ -4,6 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import defaultFlatParameters from './FlatParameters'; -// eslint-disable-next-line import/prefer-default-export -export { default as FlatParameters } from './FlatParameters'; +export default defaultFlatParameters; diff --git a/src/components/Login/index.ts b/src/components/Login/index.ts index ee8cad59..66d5b0fe 100644 --- a/src/components/Login/index.ts +++ b/src/components/Login/index.ts @@ -4,5 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// eslint-disable-next-line import/prefer-default-export -export { default as Login } from './Login'; +import defaultLogin from './Login'; + +export default defaultLogin; diff --git a/src/components/MultipleSelectionDialog/index.ts b/src/components/MultipleSelectionDialog/index.ts index df3b37ab..cc1e7ab8 100644 --- a/src/components/MultipleSelectionDialog/index.ts +++ b/src/components/MultipleSelectionDialog/index.ts @@ -4,6 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import defaultMultipleSelectionDialog from './MultipleSelectionDialog'; -// eslint-disable-next-line import/prefer-default-export -export { default as MultipleSelectionDialog } from './MultipleSelectionDialog'; +export default defaultMultipleSelectionDialog; diff --git a/src/components/OverflowableText/index.ts b/src/components/OverflowableText/index.ts index 603c4325..c09365bf 100644 --- a/src/components/OverflowableText/index.ts +++ b/src/components/OverflowableText/index.ts @@ -4,5 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// eslint-disable-next-line import/prefer-default-export -export { default as OverflowableText } from './overflowable-text'; +import defaultOverflowableText from './overflowable-text'; + +export default defaultOverflowableText; diff --git a/src/components/ReportViewer/index.ts b/src/components/ReportViewer/index.ts index b1ec5e3d..38f9231b 100644 --- a/src/components/ReportViewer/index.ts +++ b/src/components/ReportViewer/index.ts @@ -4,5 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// eslint-disable-next-line import/prefer-default-export -export { default as ReportViewer } from './report-viewer'; +import defaultReportViewer from './report-viewer'; + +export default defaultReportViewer; diff --git a/src/components/ReportViewerDialog/index.ts b/src/components/ReportViewerDialog/index.ts index c2efc106..9e72f831 100644 --- a/src/components/ReportViewerDialog/index.ts +++ b/src/components/ReportViewerDialog/index.ts @@ -4,5 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// eslint-disable-next-line import/prefer-default-export -export { default as ReportViewerDialog } from './report-viewer-dialog'; +import defaultReportViewerDialog from './report-viewer-dialog'; + +export default defaultReportViewerDialog; diff --git a/src/components/ReportViewerDialog/report-viewer-dialog.tsx b/src/components/ReportViewerDialog/report-viewer-dialog.tsx index 7aee2daf..08a8a0d7 100644 --- a/src/components/ReportViewerDialog/report-viewer-dialog.tsx +++ b/src/components/ReportViewerDialog/report-viewer-dialog.tsx @@ -18,7 +18,7 @@ import { FullscreenExit as FullscreenExitIcon, } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; -import { ReportViewer } from '../ReportViewer'; +import ReportViewer from '../ReportViewer'; import { Report } from '../ReportViewer/report.type'; const styles = { diff --git a/src/components/SignInCallbackHandler/index.ts b/src/components/SignInCallbackHandler/index.ts index c34dd93e..f6748a9a 100644 --- a/src/components/SignInCallbackHandler/index.ts +++ b/src/components/SignInCallbackHandler/index.ts @@ -4,5 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// eslint-disable-next-line import/prefer-default-export -export { default as SignInCallbackHandler } from './SignInCallbackHandler'; +import defaultSignInCallbackHandler from './SignInCallbackHandler'; + +export default defaultSignInCallbackHandler; diff --git a/src/components/SilentRenewCallbackHandler/index.ts b/src/components/SilentRenewCallbackHandler/index.ts index e6fad105..8d55a409 100644 --- a/src/components/SilentRenewCallbackHandler/index.ts +++ b/src/components/SilentRenewCallbackHandler/index.ts @@ -4,5 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// eslint-disable-next-line import/prefer-default-export -export { default as SilentRenewCallbackHandler } from './SilentRenewCallbackHandler'; +import defaultSilentRenewCallbackHandler from './SilentRenewCallbackHandler'; + +export default defaultSilentRenewCallbackHandler; diff --git a/src/components/SnackbarProvider/index.ts b/src/components/SnackbarProvider/index.ts index 384d936d..e531a58b 100644 --- a/src/components/SnackbarProvider/index.ts +++ b/src/components/SnackbarProvider/index.ts @@ -4,5 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// eslint-disable-next-line import/prefer-default-export -export { default as SnackbarProvider } from './SnackbarProvider'; +import defaultSnackbarProvider from './SnackbarProvider'; + +export default defaultSnackbarProvider; diff --git a/src/components/inputs/react-hook-form/directory-items-input.tsx b/src/components/inputs/react-hook-form/directory-items-input.tsx index 7801855f..f598edc6 100644 --- a/src/components/inputs/react-hook-form/directory-items-input.tsx +++ b/src/components/inputs/react-hook-form/directory-items-input.tsx @@ -26,7 +26,7 @@ import ErrorInput from './error-management/error-input'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { TreeViewFinderNodeProps } from '../../TreeViewFinder'; import { mergeSx } from '../../../utils/styles'; -import { OverflowableText } from '../../OverflowableText'; +import OverflowableText from '../../OverflowableText'; import MidFormError from './error-management/mid-form-error'; import DirectoryItemSelector from '../../DirectoryItemSelector/directory-item-selector'; import { fetchDirectoryElementPath } from '../../../services'; diff --git a/src/index.ts b/src/index.ts index bdd9b0a4..5bf55322 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,19 +8,19 @@ export { TreeViewFinder } from './components/TreeViewFinder'; export { TopBar } from './components/TopBar'; export { default as AboutDialog } from './components/TopBar/AboutDialog'; -export { SnackbarProvider } from './components/SnackbarProvider'; -export { AuthenticationRouter } from './components/AuthenticationRouter'; +export { default as SnackbarProvider } from './components/SnackbarProvider'; +export { default as AuthenticationRouter } from './components/AuthenticationRouter'; export { MuiVirtualizedTable } from './components/MuiVirtualizedTable'; export { KeyedColumnsRowIndexer, ChangeWays, } from './components/MuiVirtualizedTable'; -export { ReportViewer } from './components/ReportViewer'; -export { ReportViewerDialog } from './components/ReportViewerDialog'; -export { OverflowableText } from './components/OverflowableText'; +export { default as ReportViewer } from './components/ReportViewer'; +export { default as ReportViewerDialog } from './components/ReportViewerDialog'; +export { default as OverflowableText } from './components/OverflowableText'; export { ElementSearchDialog } from './components/ElementSearchDialog'; -export { FlatParameters } from './components/FlatParameters'; -export { MultipleSelectionDialog } from './components/MultipleSelectionDialog'; +export { default as FlatParameters } from './components/FlatParameters'; +export { default as MultipleSelectionDialog } from './components/MultipleSelectionDialog'; export { default as CustomMuiDialog } from './components/dialogs/custom-mui-dialog'; export { default as DescriptionModificationDialog } from './components/dialogs/description-modification-dialog'; export { default as ModifyElementSelection } from './components/dialogs/modify-element-selection'; @@ -138,7 +138,7 @@ export { default as directory_items_input_fr } from './components/translations/d export { TagRenderer } from './components/ElementSearchDialog'; export { EquipmentItem } from './components/ElementSearchDialog/equipment-item'; -export { CardErrorBoundary } from './components/CardErrorBoundary'; +export { default as CardErrorBoundary } from './components/CardErrorBoundary'; export { default as useIntlRef } from './hooks/useIntlRef'; export { useSnackMessage } from './hooks/useSnackMessage'; export { default as useDebounce } from './hooks/useDebounce'; From 194b03080d852654e9737a5990dd0141c3006eea Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Wed, 19 Jun 2024 15:10:22 +0200 Subject: [PATCH 08/16] Requested changes Signed-off-by: Ayoub LABIDI --- .eslintrc.json | 7 +++---- demo/src/app.jsx | 13 +------------ demo/src/index.jsx | 1 - src/components/ReportViewer/report-item.tsx | 6 +----- src/components/TopBar/TopBar.test.tsx | 1 - src/components/TopBar/TopBar.tsx | 11 +---------- src/components/dialogs/modify-element-selection.tsx | 2 +- .../dialogs/popup-confirmation-dialog.tsx | 4 ---- .../explicit-naming/explicit-naming-filter-form.tsx | 2 +- .../provider/custom-form-provider.tsx | 7 +------ .../utils/text-field-with-adornment.tsx | 6 +----- .../react-query-builder/element-value-editor.tsx | 2 +- .../inputs/react-query-builder/value-editor.tsx | 2 +- .../inputs/react-query-builder/value-selector.tsx | 3 +-- src/components/inputs/select-clearable.tsx | 4 ---- src/utils/{MetaData.ts => Metadata.ts} | 0 16 files changed, 13 insertions(+), 58 deletions(-) rename src/utils/{MetaData.ts => Metadata.ts} (100%) diff --git a/.eslintrc.json b/.eslintrc.json index 8f1b32d1..6923f04e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,8 @@ "airbnb", "airbnb-typescript", "airbnb/hooks", - "plugin:prettier/recommended" + "plugin:prettier/recommended", + "plugin:react/jsx-runtime" ], "parserOptions": { "project": "./tsconfig.json" @@ -20,8 +21,6 @@ "prettier/prettier": "warn", "curly": "error", "no-console": "off", - "react/jsx-props-no-spreading": "off", - "react/react-in-jsx-scope": "off", - "@typescript-eslint/no-shadow": "error" + "react/jsx-props-no-spreading": "off" } } diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 3460607d..47e91cfc 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -4,18 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* eslint-disable func-names */ -/* eslint-disable no-nested-ternary */ -/* eslint-disable no-return-assign */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable no-promise-executor-return */ -/* eslint-disable @typescript-eslint/no-unused-expressions */ -/* eslint-disable no-alert */ -/* eslint-disable no-undef */ -/* eslint-disable @typescript-eslint/no-shadow */ -/* eslint-disable react/jsx-no-bind */ -/* eslint-disable react/prop-types */ -import { useCallback, useEffect, useRef, useState } from 'react'; +/* eslint-disable func-names, no-nested-ternary, no-return-assign, @typescript-eslint/no-unused-vars, no-promise-executor-return, @typescript-eslint/no-unused-expressions, no-alert, no-undef, @typescript-eslint/no-shadow, react/jsx-no-bind, react/prop-types */ import { Box, diff --git a/demo/src/index.jsx b/demo/src/index.jsx index 40e79ac0..e10d4ae0 100644 --- a/demo/src/index.jsx +++ b/demo/src/index.jsx @@ -5,7 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React from 'react'; import { createRoot } from 'react-dom/client'; import App from './app'; diff --git a/src/components/ReportViewer/report-item.tsx b/src/components/ReportViewer/report-item.tsx index 1f5a4814..e4d1ac32 100644 --- a/src/components/ReportViewer/report-item.tsx +++ b/src/components/ReportViewer/report-item.tsx @@ -87,7 +87,7 @@ export interface ReportItemProps extends TreeItemProps { className?: any; } -function ReportItem(props: PropsWithChildren) { +function ReportItem(props: Readonly>) { const { nodeId } = props; // using a context because TreeItem uses useMemo on this. See report-viewer.js for the provider const { isHighlighted } = useContext(ReportTreeViewContext); @@ -126,8 +126,4 @@ function ReportItem(props: PropsWithChildren) { ); } -ReportItem.defaultProps = { - className: undefined, -}; - export default styled(ReportItem)({}); diff --git a/src/components/TopBar/TopBar.test.tsx b/src/components/TopBar/TopBar.test.tsx index 6859ed06..c508019c 100644 --- a/src/components/TopBar/TopBar.test.tsx +++ b/src/components/TopBar/TopBar.test.tsx @@ -5,7 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React from 'react'; import { createRoot } from 'react-dom/client'; import { act } from 'react-dom/test-utils'; import { IntlProvider } from 'react-intl'; diff --git a/src/components/TopBar/TopBar.tsx b/src/components/TopBar/TopBar.tsx index 23a8e35b..ba218645 100644 --- a/src/components/TopBar/TopBar.tsx +++ b/src/components/TopBar/TopBar.tsx @@ -196,7 +196,7 @@ function TopBar({ equipmentLabelling, onLanguageClick, language, -}: PropsWithChildren) { +}: Readonly>) { const [anchorElSettingsMenu, setAnchorElSettingsMenu] = useState(null); const [anchorElAppsMenu, setAnchorElAppsMenu] = useState( @@ -645,13 +645,4 @@ function TopBar({ ); } -TopBar.defaultProps = { - onParametersClick: undefined, - onAboutClick: undefined, - onThemeClick: undefined, - theme: undefined, - onEquipmentLabellingClick: undefined, - equipmentLabelling: undefined, -}; - export default TopBar; diff --git a/src/components/dialogs/modify-element-selection.tsx b/src/components/dialogs/modify-element-selection.tsx index ddf02b4c..66c94d4a 100644 --- a/src/components/dialogs/modify-element-selection.tsx +++ b/src/components/dialogs/modify-element-selection.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { Button, Grid, Typography } from '@mui/material'; import { FormattedMessage, useIntl } from 'react-intl'; import { useController } from 'react-hook-form'; diff --git a/src/components/dialogs/popup-confirmation-dialog.tsx b/src/components/dialogs/popup-confirmation-dialog.tsx index 79ccab07..ee4e7b75 100644 --- a/src/components/dialogs/popup-confirmation-dialog.tsx +++ b/src/components/dialogs/popup-confirmation-dialog.tsx @@ -52,8 +52,4 @@ function PopupConfirmationDialog({ ); } -PopupConfirmationDialog.defaultProps = { - validateButtonLabel: undefined, -}; - export default PopupConfirmationDialog; diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx index 74c0f5f3..d93ff49e 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx @@ -205,7 +205,7 @@ function ExplicitNamingFilterForm({ const openConfirmationPopup = () => { return getValues(FILTER_EQUIPMENTS_ATTRIBUTES).some( (row: FilterTableRow) => - row[DISTRIBUTION_KEY] ?? row[FieldConstants.EQUIPMENT_ID] + row[DISTRIBUTION_KEY] || row[FieldConstants.EQUIPMENT_ID] ); }; diff --git a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx index 9eb31a66..3be307d0 100644 --- a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx +++ b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx @@ -27,7 +27,7 @@ export const CustomFormContext = createContext({ language: getSystemLanguage(), }); -function CustomFormProvider(props: CustomFormProviderProps) { +function CustomFormProvider(props: Readonly) { const { validationSchema, removeOptional, @@ -54,9 +54,4 @@ function CustomFormProvider(props: CustomFormProviderProps) { ); } -CustomFormProvider.defaultProps = { - removeOptional: false, - language: undefined, -}; - export default CustomFormProvider; diff --git a/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx b/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx index c350b0bb..8f714357 100644 --- a/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx +++ b/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx @@ -24,7 +24,7 @@ export type TextFieldWithAdornmentProps = TextFieldProps & { handleClearValue?: () => void; }; -function TextFieldWithAdornment(props: TextFieldWithAdornmentProps) { +function TextFieldWithAdornment(props: Readonly) { const { adornmentPosition, adornmentText, @@ -133,8 +133,4 @@ function TextFieldWithAdornment(props: TextFieldWithAdornmentProps) { ); } -TextFieldWithAdornment.defaultProps = { - handleClearValue: undefined, -}; - export default TextFieldWithAdornment; diff --git a/src/components/inputs/react-query-builder/element-value-editor.tsx b/src/components/inputs/react-query-builder/element-value-editor.tsx index 9299974b..e2fcd810 100644 --- a/src/components/inputs/react-query-builder/element-value-editor.tsx +++ b/src/components/inputs/react-query-builder/element-value-editor.tsx @@ -5,8 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { useEffect } from 'react'; import { validate as uuidValidate } from 'uuid'; +import { useEffect } from 'react'; import useCustomFormContext from '../react-hook-form/provider/use-custom-form-context'; import { fetchElementsInfos } from '../../../services'; import DirectoryItemsInput from '../react-hook-form/directory-items-input'; diff --git a/src/components/inputs/react-query-builder/value-editor.tsx b/src/components/inputs/react-query-builder/value-editor.tsx index 305b15fe..453e967d 100644 --- a/src/components/inputs/react-query-builder/value-editor.tsx +++ b/src/components/inputs/react-query-builder/value-editor.tsx @@ -6,10 +6,10 @@ */ import { ValueEditorProps } from 'react-querybuilder'; -import React, { useCallback } from 'react'; import { MaterialValueEditor } from '@react-querybuilder/material'; import Box from '@mui/material/Box'; import { useFormContext } from 'react-hook-form'; +import { useCallback } from 'react'; import CountryValueEditor from './country-value-editor'; import TranslatedValueEditor from './translated-value-editor'; import TextValueEditor from './text-value-editor'; diff --git a/src/components/inputs/react-query-builder/value-selector.tsx b/src/components/inputs/react-query-builder/value-selector.tsx index 05649118..34d21867 100644 --- a/src/components/inputs/react-query-builder/value-selector.tsx +++ b/src/components/inputs/react-query-builder/value-selector.tsx @@ -6,10 +6,9 @@ */ import { ValueSelectorProps } from 'react-querybuilder'; -import React from 'react'; import { MaterialValueSelector } from '@react-querybuilder/material'; -function ValueSelector(props: ValueSelectorProps) { +function ValueSelector(props: Readonly) { return ( ) { ); } -SelectClearable.defaultProps = { - label: undefined, -}; - export default SelectClearable; diff --git a/src/utils/MetaData.ts b/src/utils/Metadata.ts similarity index 100% rename from src/utils/MetaData.ts rename to src/utils/Metadata.ts From f8a60b240733b58b76034746163b903d8eee7958 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Thu, 20 Jun 2024 16:53:45 +0200 Subject: [PATCH 09/16] reorganize dependencies Signed-off-by: Ayoub LABIDI --- demo/src/app.jsx | 2 +- demo/src/right-resizable-box.jsx | 1 + package-lock.json | 139 ++++++++++++++----------------- package.json | 5 +- 4 files changed, 66 insertions(+), 81 deletions(-) diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 47e91cfc..56d1db7d 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -4,7 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* eslint-disable func-names, no-nested-ternary, no-return-assign, @typescript-eslint/no-unused-vars, no-promise-executor-return, @typescript-eslint/no-unused-expressions, no-alert, no-undef, @typescript-eslint/no-shadow, react/jsx-no-bind, react/prop-types */ +/* eslint-disable func-names, no-nested-ternary, no-return-assign, @typescript-eslint/no-unused-vars, no-promise-executor-return, @typescript-eslint/no-unused-expressions, no-alert, no-undef, @typescript-eslint/no-shadow, react/jsx-no-bind, react/prop-types, import/no-extraneous-dependencies */ import { Box, diff --git a/demo/src/right-resizable-box.jsx b/demo/src/right-resizable-box.jsx index 933466dd..cee3677e 100644 --- a/demo/src/right-resizable-box.jsx +++ b/demo/src/right-resizable-box.jsx @@ -4,6 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* eslint-disable import/no-extraneous-dependencies */ import { useState } from 'react'; import { MoreVert as ResizePanelHandleIcon } from '@mui/icons-material'; diff --git a/package-lock.json b/package-lock.json index a480a49d..46bde43c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,6 @@ "version": "0.59.2", "license": "MPL-2.0", "dependencies": { - "@mui/system": "^5.15.15", - "@mui/x-tree-view": "^6.17.0", - "@react-hook/window-size": "^3.1.1", "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", "autosuggest-highlight": "^3.3.4", @@ -25,8 +22,6 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-querybuilder": "^7.2.0", - "react-resizable": "^3.0.5", - "react-router": "^6.23.1", "react-virtualized": "^9.22.5", "uuid": "^9.0.1" }, @@ -43,6 +38,8 @@ "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", "@mui/material": "^5.15.14", + "@mui/x-tree-view": "^6.17.0", + "@react-hook/window-size": "^3.1.1", "@types/autosuggest-highlight": "^3.2.3", "@types/eslint": "^8.56.7", "@types/eslint-config-prettier": "^6.11.3", @@ -89,6 +86,7 @@ "react-hook-form": "^7.51.2", "react-intl": "^6.6.4", "react-papaparse": "^4.1.0", + "react-resizable": "^3.0.5", "react-router-dom": "^6.22.3", "ts-node": "^10.9.2", "type-fest": "^4.14.0", @@ -4418,6 +4416,7 @@ "version": "6.17.0", "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.17.0.tgz", "integrity": "sha512-09dc2D+Rjg2z8KOaxbUXyPi0aw7fm2jurEtV8Xw48xJ00joLWd5QJm1/v4CarEvaiyhTQzHImNqdgeJW8ZQB6g==", + "dev": true, "dependencies": { "@babel/runtime": "^7.23.2", "@mui/base": "^5.0.0-beta.20", @@ -4547,6 +4546,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@react-hook/debounce/-/debounce-3.0.0.tgz", "integrity": "sha512-ir/kPrSfAzY12Gre0sOHkZ2rkEmM4fS5M5zFxCi4BnCeXh2nvx9Ujd+U4IGpKCuPA+EQD0pg1eK2NGLvfWejag==", + "dev": true, "dependencies": { "@react-hook/latest": "^1.0.2" }, @@ -4558,6 +4558,7 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@react-hook/event/-/event-1.2.6.tgz", "integrity": "sha512-JUL5IluaOdn5w5Afpe/puPa1rj8X6udMlQ9dt4hvMuKmTrBS1Ya6sb4sVgvfe2eU4yDuOfAhik8xhbcCekbg9Q==", + "dev": true, "peerDependencies": { "react": ">=16.8" } @@ -4566,6 +4567,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz", "integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==", + "dev": true, "peerDependencies": { "react": ">=16.8" } @@ -4574,6 +4576,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@react-hook/throttle/-/throttle-2.2.0.tgz", "integrity": "sha512-LJ5eg+yMV8lXtqK3lR+OtOZ2WH/EfWvuiEEu0M3bhR7dZRfTyEJKxH1oK9uyBxiXPtWXiQggWbZirMCXam51tg==", + "dev": true, "dependencies": { "@react-hook/latest": "^1.0.2" }, @@ -4585,6 +4588,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@react-hook/window-size/-/window-size-3.1.1.tgz", "integrity": "sha512-yWnVS5LKnOUIrEsI44oz3bIIUYqflamPL27n+k/PC//PsX/YeWBky09oPeAoc9As6jSH16Wgo8plI+ECZaHk3g==", + "dev": true, "dependencies": { "@react-hook/debounce": "^3.0.0", "@react-hook/event": "^1.2.1", @@ -5572,16 +5576,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz", - "integrity": "sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.1.tgz", + "integrity": "sha512-kZqi+WZQaZfPKnsflLJQCz6Ze9FFSMfXrrIOcyargekQxG37ES7DJNpJUE9Q/X5n3yTIP/WPutVNzgknQ7biLg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/type-utils": "7.12.0", - "@typescript-eslint/utils": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/type-utils": "7.13.1", + "@typescript-eslint/utils": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -5779,15 +5783,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz", - "integrity": "sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.1.tgz", + "integrity": "sha512-1ELDPlnLvDQ5ybTSrMhRTFDfOQEOXNM+eP+3HT/Yq7ruWpciQw+Avi73pdEbA4SooCawEWo3dtYbF68gN7Ed1A==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/typescript-estree": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", "debug": "^4.3.4" }, "engines": { @@ -5807,13 +5811,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz", - "integrity": "sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.1.tgz", + "integrity": "sha512-adbXNVEs6GmbzaCpymHQ0MB6E4TqoiVbC0iqG3uijR8ZYfpAXMGttouQzF4Oat3P2GxDVIrg7bMI/P65LiQZdg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0" + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5824,13 +5828,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz", - "integrity": "sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.13.1.tgz", + "integrity": "sha512-aWDbLu1s9bmgPGXSzNCxELu+0+HQOapV/y+60gPXafR8e2g1Bifxzevaa+4L2ytCWm+CHqpELq4CSoN9ELiwCg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.12.0", - "@typescript-eslint/utils": "7.12.0", + "@typescript-eslint/typescript-estree": "7.13.1", + "@typescript-eslint/utils": "7.13.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -5851,9 +5855,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", - "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.1.tgz", + "integrity": "sha512-7K7HMcSQIAND6RBL4kDl24sG/xKM13cA85dc7JnmQXw2cBDngg7c19B++JzvJHRG3zG36n9j1i451GBzRuHchw==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5864,13 +5868,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz", - "integrity": "sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.1.tgz", + "integrity": "sha512-uxNr51CMV7npU1BxZzYjoVz9iyjckBduFBP0S5sLlh1tXYzHzgZ3BR9SVsNed+LmwKrmnqN3Kdl5t7eZ5TS1Yw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/visitor-keys": "7.13.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -5904,15 +5908,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz", - "integrity": "sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.1.tgz", + "integrity": "sha512-h5MzFBD5a/Gh/fvNdp9pTfqJAbuQC4sCN2WzuXme71lqFJsZtLbjxfSk4r3p02WIArOF9N94pdsLiGutpDbrXQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/typescript-estree": "7.12.0" + "@typescript-eslint/scope-manager": "7.13.1", + "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/typescript-estree": "7.13.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5926,12 +5930,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz", - "integrity": "sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.1.tgz", + "integrity": "sha512-k/Bfne7lrP7hcb7m9zSsgcBmo+8eicqqfNAJ7uUY+jkTFpKeH2FSkWpFRtimBxgkyvqfu9jTPRbYOvud6isdXA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/types": "7.13.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -13582,6 +13586,7 @@ "version": "4.4.6", "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "dev": true, "dependencies": { "clsx": "^1.1.1", "prop-types": "^15.8.1" @@ -13595,6 +13600,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "dev": true, "engines": { "node": ">=6" } @@ -13756,6 +13762,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", + "dev": true, "dependencies": { "prop-types": "15.x", "react-draggable": "^4.0.3" @@ -13765,11 +13772,12 @@ } }, "node_modules/react-router": { - "version": "6.23.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", - "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dev": true, "dependencies": { - "@remix-run/router": "1.16.1" + "@remix-run/router": "1.15.3" }, "engines": { "node": ">=14.0.0" @@ -13795,29 +13803,6 @@ "react-dom": ">=16.8" } }, - "node_modules/react-router-dom/node_modules/react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", - "dev": true, - "dependencies": { - "@remix-run/router": "1.15.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router/node_modules/@remix-run/router": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", - "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -15960,9 +15945,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 02c854df..e2d75bd8 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,6 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-querybuilder": "^7.2.0", - "react-router": "^6.23.1", - "react-resizable": "^3.0.5", - "@react-hook/window-size": "^3.1.1", "react-virtualized": "^9.22.5", "uuid": "^9.0.1" }, @@ -79,6 +76,7 @@ "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", "@mui/material": "^5.15.14", + "@react-hook/window-size": "^3.1.1", "@types/autosuggest-highlight": "^3.2.3", "@types/eslint": "^8.56.7", "@types/eslint-config-prettier": "^6.11.3", @@ -125,6 +123,7 @@ "react-hook-form": "^7.51.2", "react-intl": "^6.6.4", "react-papaparse": "^4.1.0", + "react-resizable": "^3.0.5", "react-router-dom": "^6.22.3", "ts-node": "^10.9.2", "type-fest": "^4.14.0", From 33f9e8ee5549c7785a33f7061cb0ffd789852a00 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Tue, 25 Jun 2024 09:13:42 +0200 Subject: [PATCH 10/16] auto review Signed-off-by: Ayoub LABIDI --- demo/src/TableTab.jsx | 3 ++- demo/src/equipment-search.tsx | 3 ++- demo/src/right-resizable-box.jsx | 3 ++- src/components/TopBar/AboutDialog.tsx | 9 ++++++--- src/components/filter/expert/expert-filter-utils.ts | 8 +++----- .../react-hook-form/error-management/mid-form-error.tsx | 2 +- .../select-inputs/input-with-popup-confirmation.tsx | 4 +--- src/components/inputs/react-hook-form/slider-input.tsx | 7 ++++++- .../composite-rule-editor/group-value-editor.tsx | 2 +- 9 files changed, 24 insertions(+), 17 deletions(-) diff --git a/demo/src/TableTab.jsx b/demo/src/TableTab.jsx index 012ff40d..f5f62308 100644 --- a/demo/src/TableTab.jsx +++ b/demo/src/TableTab.jsx @@ -191,7 +191,8 @@ export function TableTab() { filtered = filtered.reverse(); } } - return filtered.map(([j]) => j); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + return filtered.map(([_, j]) => j); }, [rows, filter] ); diff --git a/demo/src/equipment-search.tsx b/demo/src/equipment-search.tsx index eca1e660..f0eb2830 100644 --- a/demo/src/equipment-search.tsx +++ b/demo/src/equipment-search.tsx @@ -38,7 +38,8 @@ const equipmentsToReturn: AnyElementInterface[] = [ }, ]; -const searchEquipmentPromise = () => { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const searchEquipmentPromise = (term: string) => { return new Promise((resolve) => { setTimeout(() => { resolve(equipmentsToReturn); diff --git a/demo/src/right-resizable-box.jsx b/demo/src/right-resizable-box.jsx index cee3677e..bc8c57f7 100644 --- a/demo/src/right-resizable-box.jsx +++ b/demo/src/right-resizable-box.jsx @@ -65,7 +65,8 @@ function RightResizableBox(props) { setResizedTreePercentage(newPercentage); } }; - const onResize = (event, { size }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const onResize = (event, { element, size }) => { updateResizedTreePercentage(size.width, windowWidth); }; diff --git a/src/components/TopBar/AboutDialog.tsx b/src/components/TopBar/AboutDialog.tsx index ec4fabd8..3c8c6c6d 100644 --- a/src/components/TopBar/AboutDialog.tsx +++ b/src/components/TopBar/AboutDialog.tsx @@ -366,7 +366,8 @@ function AboutDialog({ ) .then( (values) => (Array.isArray(values) ? values : []), - () => [] + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (reason) => [] ) .then((values) => { setModules([currentApp, ...values]); @@ -399,7 +400,8 @@ function AboutDialog({ aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" TransitionProps={{ - onExited: () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onExited: (node) => { setModules(null); setActualGlobalVersion(null); }, @@ -450,7 +452,8 @@ function AboutDialog({ in={loadingGlobalVersion} appear unmountOnExit - onExited={() => setShowGlobalVersion(true)} + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onExited={(node) => setShowGlobalVersion(true)} > diff --git a/src/components/filter/expert/expert-filter-utils.ts b/src/components/filter/expert/expert-filter-utils.ts index 58745ec1..7bb03d45 100644 --- a/src/components/filter/expert/expert-filter-utils.ts +++ b/src/components/filter/expert/expert-filter-utils.ts @@ -399,14 +399,12 @@ export function importExpertRules(query: RuleGroupTypeExport): RuleGroupType { if ('rules' in ruleOrGroup) { // a composite group => aggregate into a composite rule if ('field' in ruleOrGroup && 'operator' in ruleOrGroup) { - return transformCompositeGroup( - ruleOrGroup as RuleGroupTypeExport - ); + return transformCompositeGroup(ruleOrGroup); } // a normal group - return transformGroup(ruleOrGroup as RuleGroupTypeExport); + return transformGroup(ruleOrGroup); } - return transformRule(ruleOrGroup as RuleTypeExport); + return transformRule(ruleOrGroup); }); return { diff --git a/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx b/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx index a16e8ccb..40f63a33 100644 --- a/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx +++ b/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx @@ -9,7 +9,7 @@ import { Box } from '@mui/material'; import { ReactNode } from 'react'; // component to display error message in the middle of dialog -function MidFormError({ message }: { message: string | ReactNode }) { +function MidFormError({ message }: Readonly<{ message: string | ReactNode }>) { return ( ({ diff --git a/src/components/inputs/react-hook-form/select-inputs/input-with-popup-confirmation.tsx b/src/components/inputs/react-hook-form/select-inputs/input-with-popup-confirmation.tsx index c221b39f..cbff2b17 100644 --- a/src/components/inputs/react-hook-form/select-inputs/input-with-popup-confirmation.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/input-with-popup-confirmation.tsx @@ -35,9 +35,7 @@ function InputWithPopupConfirmation({ }; const handlePopupConfirmation = () => { - if (resetOnConfirmation) { - resetOnConfirmation(); - } + resetOnConfirmation?.(); onChange(newValue); setOpenPopup(false); }; diff --git a/src/components/inputs/react-hook-form/slider-input.tsx b/src/components/inputs/react-hook-form/slider-input.tsx index b316f452..b1542a27 100644 --- a/src/components/inputs/react-hook-form/slider-input.tsx +++ b/src/components/inputs/react-hook-form/slider-input.tsx @@ -26,7 +26,12 @@ function SliderInput({ field: { onChange, value }, } = useController({ name }); - const handleValueChange = (event: Event, newValue: number | number[]) => { + const handleValueChange = ( + event: Event, + newValue: number | number[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + activeThumb: number + ) => { onValueChanged(newValue); onChange(newValue); }; diff --git a/src/components/inputs/react-query-builder/composite-rule-editor/group-value-editor.tsx b/src/components/inputs/react-query-builder/composite-rule-editor/group-value-editor.tsx index ebdce3c7..14c0ff05 100644 --- a/src/components/inputs/react-query-builder/composite-rule-editor/group-value-editor.tsx +++ b/src/components/inputs/react-query-builder/composite-rule-editor/group-value-editor.tsx @@ -22,7 +22,7 @@ const styles = { }), }; -function GroupValueEditor(props: ValueEditorProps) { +function GroupValueEditor(props: Readonly>) { const { fieldData: { combinator, children }, value, From 057c068e2754dbee4f40ef3934dbe3cd10499ec6 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Mon, 1 Jul 2024 15:14:59 +0200 Subject: [PATCH 11/16] Remove Readonly on props & disable react/require-default-props rule Signed-off-by: Ayoub LABIDI --- .eslintrc.json | 3 ++- .../AuthenticationRouter.tsx | 2 +- .../CardErrorBoundary/card-error-boundary.tsx | 1 - src/components/CustomAGGrid/custom-aggrid.tsx | 2 +- .../directory-item-selector.tsx | 2 +- .../element-search-dialog.tsx | 4 +--- .../element-search-input.tsx | 4 +--- .../ElementSearchDialog/equipment-item.tsx | 4 ++-- .../ElementSearchDialog/tag-renderer.tsx | 20 +++++++++---------- .../FlatParameters/FlatParameters.tsx | 2 +- src/components/Login/Login.tsx | 6 +++--- src/components/Login/Logout.tsx | 2 +- .../MuiVirtualizedTable/ColumnHeader.tsx | 4 ++-- .../MultipleSelectionDialog.tsx | 2 +- src/components/ReportViewer/filter-button.tsx | 2 +- src/components/ReportViewer/log-table.tsx | 2 +- .../ReportViewer/multi-select-list.tsx | 2 +- src/components/ReportViewer/report-item.tsx | 2 +- src/components/ReportViewer/report-viewer.tsx | 2 +- .../report-viewer-dialog.tsx | 4 +--- .../SignInCallbackHandler.tsx | 2 +- .../SilentRenewCallbackHandler.tsx | 2 +- .../SnackbarProvider/SnackbarProvider.tsx | 2 +- src/components/TopBar/AboutDialog.tsx | 7 ++----- src/components/TopBar/GridLogo.tsx | 4 ++-- src/components/TopBar/TopBar.tsx | 2 +- .../TreeViewFinder/TreeViewFinder.tsx | 2 +- src/components/dialogs/custom-mui-dialog.tsx | 2 +- .../description-modification-dialog.tsx | 2 +- .../dialogs/modify-element-selection.tsx | 2 +- .../dialogs/popup-confirmation-dialog.tsx | 2 +- .../criteria-based-filter-edition-dialog.tsx | 2 +- .../criteria-based/criteria-based-form.tsx | 2 +- .../criteria-based/filter-free-properties.tsx | 2 +- .../filter/criteria-based/filter-property.tsx | 2 +- .../expert/expert-filter-edition-dialog.tsx | 2 +- .../explicit-naming-filter-edition-dialog.tsx | 2 +- .../explicit-naming-filter-form.tsx | 4 ++-- .../filter/filter-creation-dialog.tsx | 2 +- src/components/filter/filter-form.tsx | 2 +- .../react-hook-form/ExpandingTextField.tsx | 2 +- .../ag-grid-table/bottom-right-buttons.tsx | 2 +- .../csv-uploader/csv-uploader.tsx | 2 +- .../ag-grid-table/custom-ag-grid-table.tsx | 2 +- .../autocomplete-input.tsx | 2 +- .../booleans/boolean-input.tsx | 7 +------ .../booleans/checkbox-input.tsx | 6 +----- .../react-hook-form/booleans/switch-input.tsx | 2 +- .../react-hook-form/directory-items-input.tsx | 2 +- .../error-management/error-input.tsx | 2 +- .../error-management/field-error-alert.tsx | 2 +- .../error-management/mid-form-error.tsx | 2 +- .../react-hook-form/numbers/float-input.tsx | 2 +- .../react-hook-form/numbers/integer-input.tsx | 2 +- .../provider/custom-form-provider.tsx | 2 +- .../inputs/react-hook-form/radio-input.tsx | 8 +------- .../inputs/react-hook-form/range-input.tsx | 2 +- .../select-inputs/countries-input.tsx | 2 +- .../select-inputs/select-input.tsx | 2 +- .../inputs/react-hook-form/slider-input.tsx | 2 +- .../inputs/react-hook-form/text-input.tsx | 2 +- .../react-hook-form/unique-name-input.tsx | 2 +- .../react-hook-form/utils/field-label.tsx | 2 +- .../utils/text-field-with-adornment.tsx | 2 +- .../inputs/react-query-builder/add-button.tsx | 2 +- .../combinator-selector.tsx | 2 +- .../group-value-editor.tsx | 2 +- .../rule-value-editor.tsx | 2 +- .../country-value-editor.tsx | 2 +- .../custom-react-query-builder.tsx | 8 +++----- .../element-value-editor.tsx | 2 +- .../property-value-editor.tsx | 2 +- .../react-query-builder/remove-button.tsx | 2 +- .../react-query-builder/text-value-editor.tsx | 2 +- .../translated-value-editor.tsx | 2 +- .../react-query-builder/value-editor.tsx | 2 +- .../react-query-builder/value-selector.tsx | 2 +- src/components/inputs/select-clearable.tsx | 2 +- src/services/study.ts | 9 ++++++--- 79 files changed, 101 insertions(+), 126 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 6923f04e..07133157 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,6 +21,7 @@ "prettier/prettier": "warn", "curly": "error", "no-console": "off", - "react/jsx-props-no-spreading": "off" + "react/jsx-props-no-spreading": "off", + "react/require-default-props": "off" } } diff --git a/src/components/AuthenticationRouter/AuthenticationRouter.tsx b/src/components/AuthenticationRouter/AuthenticationRouter.tsx index 8bab1f4a..81daca3f 100644 --- a/src/components/AuthenticationRouter/AuthenticationRouter.tsx +++ b/src/components/AuthenticationRouter/AuthenticationRouter.tsx @@ -48,7 +48,7 @@ function AuthenticationRouter({ dispatch, navigate, location, -}: Readonly) { +}: AuthenticationRouterProps) { const handleSigninCallbackClosure = useCallback( () => handleSigninCallback(dispatch, navigate, userManager.instance), [dispatch, navigate, userManager.instance] diff --git a/src/components/CardErrorBoundary/card-error-boundary.tsx b/src/components/CardErrorBoundary/card-error-boundary.tsx index 5aa27b86..71494f9f 100644 --- a/src/components/CardErrorBoundary/card-error-boundary.tsx +++ b/src/components/CardErrorBoundary/card-error-boundary.tsx @@ -47,7 +47,6 @@ const ExpandMore = styled((props: ExpandMoreProps) => { // Extracted from https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/error_boundaries/ for types interface Props { - // eslint-disable-next-line react/require-default-props children?: ReactNode; } diff --git a/src/components/CustomAGGrid/custom-aggrid.tsx b/src/components/CustomAGGrid/custom-aggrid.tsx index baf3f52d..abb55ee5 100644 --- a/src/components/CustomAGGrid/custom-aggrid.tsx +++ b/src/components/CustomAGGrid/custom-aggrid.tsx @@ -36,7 +36,7 @@ const onColumnResized = (params: ColumnResizedEvent) => { } }; -const CustomAGGrid = React.forwardRef>( +const CustomAGGrid = React.forwardRef( (props, ref) => { const { shouldHidePinnedHeaderRightBorder = false, diff --git a/src/components/DirectoryItemSelector/directory-item-selector.tsx b/src/components/DirectoryItemSelector/directory-item-selector.tsx index ba85f46e..b2278e1d 100644 --- a/src/components/DirectoryItemSelector/directory-item-selector.tsx +++ b/src/components/DirectoryItemSelector/directory-item-selector.tsx @@ -182,7 +182,7 @@ function DirectoryItemSelector({ itemFilter, expanded, ...otherTreeViewFinderProps -}: Readonly) { +}: DirectoryItemSelectorProps) { const [data, setData] = useState([]); const [rootDirectories, setRootDirectories] = useState([]); const nodeMap = useRef({}); diff --git a/src/components/ElementSearchDialog/element-search-dialog.tsx b/src/components/ElementSearchDialog/element-search-dialog.tsx index d03268c2..c3bdbdde 100644 --- a/src/components/ElementSearchDialog/element-search-dialog.tsx +++ b/src/components/ElementSearchDialog/element-search-dialog.tsx @@ -18,9 +18,7 @@ export interface ElementSearchDialogProps open: boolean; } -export function ElementSearchDialog( - props: Readonly> -) { +export function ElementSearchDialog(props: ElementSearchDialogProps) { const { open, onClose, onSearchTermChange, ...rest } = props; const handleClose = useCallback(() => { diff --git a/src/components/ElementSearchDialog/element-search-input.tsx b/src/components/ElementSearchDialog/element-search-input.tsx index 493d8586..b8eaf101 100644 --- a/src/components/ElementSearchDialog/element-search-input.tsx +++ b/src/components/ElementSearchDialog/element-search-input.tsx @@ -38,9 +38,7 @@ export interface ElementSearchInputProps showResults?: boolean; } -export function ElementSearchInput( - props: Readonly> -) { +export function ElementSearchInput(props: ElementSearchInputProps) { const { elementsFound, loading, diff --git a/src/components/ElementSearchDialog/equipment-item.tsx b/src/components/ElementSearchDialog/equipment-item.tsx index 043f5455..249f4123 100644 --- a/src/components/ElementSearchDialog/equipment-item.tsx +++ b/src/components/ElementSearchDialog/equipment-item.tsx @@ -41,7 +41,7 @@ export function EquipmentItem({ element, showsJustText = false, ...props -}: Readonly) { +}: EquipmentItemProps) { const matches = match(element.label, inputValue, { insideWords: true, findAllOccurrences: true, @@ -87,7 +87,7 @@ export function EquipmentItem({ ))} - {!showsJustText && suffixRenderer({ props, element })} + {!showsJustText && suffixRenderer({ element, ...props })} ); diff --git a/src/components/ElementSearchDialog/tag-renderer.tsx b/src/components/ElementSearchDialog/tag-renderer.tsx index d4e599db..96ca3e69 100644 --- a/src/components/ElementSearchDialog/tag-renderer.tsx +++ b/src/components/ElementSearchDialog/tag-renderer.tsx @@ -11,23 +11,21 @@ import { EQUIPMENT_TYPE, EquipmentType } from '../../utils/EquipmentType'; import { mergeSx } from '../../utils/styles'; interface TagRendererProps { - props: { - classes?: { - equipmentTag?: string; - equipmentVlTag?: string; - }; - styles?: { - equipmentTag?: SxProps; - equipmentVlTag?: SxProps; - }; - }; element: { type: EquipmentType; voltageLevelLabel?: string; }; + classes?: { + equipmentTag?: string; + equipmentVlTag?: string; + }; + styles?: { + equipmentTag?: SxProps; + equipmentVlTag?: SxProps; + }; } -function TagRenderer({ props, element }: Readonly) { +function TagRenderer({ element, ...props }: TagRendererProps) { if ( element.type !== EQUIPMENT_TYPE.SUBSTATION?.name && element.type !== EQUIPMENT_TYPE.VOLTAGE_LEVEL?.name diff --git a/src/components/FlatParameters/FlatParameters.tsx b/src/components/FlatParameters/FlatParameters.tsx index b87ef4ee..77da2844 100644 --- a/src/components/FlatParameters/FlatParameters.tsx +++ b/src/components/FlatParameters/FlatParameters.tsx @@ -127,7 +127,7 @@ export function FlatParameters({ variant = 'outlined', showSeparator = false, selectionWithDialog = () => false, -}: Readonly) { +}: FlatParametersProps) { const intl = useIntl(); const longestPrefix = longestCommonPrefix(paramsAsArray.map((m) => m.name)); diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index c4219db8..e79380b5 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -4,7 +4,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* eslint-disable jsx-a11y/anchor-is-valid */ import { Avatar, Box, @@ -43,7 +42,7 @@ export interface LoginProps { disabled: boolean; } -function Login({ onLoginClick, disabled }: Readonly) { +function Login({ onLoginClick, disabled }: LoginProps) { return ( @@ -75,7 +74,8 @@ function Login({ onLoginClick, disabled }: Readonly) { align="center" > {'Copyright © '} - + {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + GridSuite {' '} {new Date().getFullYear()}. diff --git a/src/components/Login/Logout.tsx b/src/components/Login/Logout.tsx index 90cf1478..40e07427 100644 --- a/src/components/Login/Logout.tsx +++ b/src/components/Login/Logout.tsx @@ -51,7 +51,7 @@ function Copyright() { ); } -function Logout({ onLogoutClick, disabled }: Readonly) { +function Logout({ onLogoutClick, disabled }: LogoutProps) { return ( diff --git a/src/components/MuiVirtualizedTable/ColumnHeader.tsx b/src/components/MuiVirtualizedTable/ColumnHeader.tsx index 907a7669..c5b4f517 100644 --- a/src/components/MuiVirtualizedTable/ColumnHeader.tsx +++ b/src/components/MuiVirtualizedTable/ColumnHeader.tsx @@ -75,7 +75,7 @@ interface SortButtonProps { // in the same direction as it will get if clicked (once). // signedRank > 0 means sorted by ascending value from lower indices to higher indices // so lesser values are at top, so the upward arrow -function SortButton({ signedRank = 0, ...props }: Readonly) { +function SortButton({ signedRank = 0, ...props }: SortButtonProps) { const sortRank = Math.abs(signedRank); const visibilityStyle = (!signedRank || undefined) && @@ -98,7 +98,7 @@ interface FilterButtonProps { onClick: ComponentProps['onClick']; } -function FilterButton(props: Readonly) { +function FilterButton(props: FilterButtonProps) { const { filterLevel, headerHovered, onClick } = props; const visibilityStyle = !filterLevel && (headerHovered ? styles.hovered : styles.transparent); diff --git a/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx b/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx index 731111e6..f2dce50e 100644 --- a/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx +++ b/src/components/MultipleSelectionDialog/MultipleSelectionDialog.tsx @@ -37,7 +37,7 @@ function MultipleSelectionDialog({ handleClose, handleValidate, titleId, -}: Readonly) { +}: MultipleSelectionDialogProps) { const [selectedIds, setSelectedIds] = useState(selectedOptions ?? []); const handleSelectAll = () => { if (selectedIds.length !== options.length) { diff --git a/src/components/ReportViewer/filter-button.tsx b/src/components/ReportViewer/filter-button.tsx index dd24d0be..14613766 100644 --- a/src/components/ReportViewer/filter-button.tsx +++ b/src/components/ReportViewer/filter-button.tsx @@ -45,7 +45,7 @@ export interface FilterButtonProps { export function FilterButton({ selectedItems, setSelectedItems, -}: Readonly) { +}: FilterButtonProps) { const [initialState] = useState(selectedItems); const [anchorEl, setAnchorEl] = useState(null); diff --git a/src/components/ReportViewer/log-table.tsx b/src/components/ReportViewer/log-table.tsx index 290162f1..86945628 100644 --- a/src/components/ReportViewer/log-table.tsx +++ b/src/components/ReportViewer/log-table.tsx @@ -49,7 +49,7 @@ function LogTable({ onRowClick, selectedSeverity, setSelectedSeverity, -}: Readonly) { +}: LogTableProps) { const intl = useIntl(); const theme = useTheme(); diff --git a/src/components/ReportViewer/multi-select-list.tsx b/src/components/ReportViewer/multi-select-list.tsx index dae99486..2522cc3a 100644 --- a/src/components/ReportViewer/multi-select-list.tsx +++ b/src/components/ReportViewer/multi-select-list.tsx @@ -45,7 +45,7 @@ export function MultiSelectList({ handleChange, handleClose, anchor, -}: Readonly) { +}: MultiSelectListProps) { const open = Boolean(anchor); return ( diff --git a/src/components/ReportViewer/report-item.tsx b/src/components/ReportViewer/report-item.tsx index e4d1ac32..d65bbf3b 100644 --- a/src/components/ReportViewer/report-item.tsx +++ b/src/components/ReportViewer/report-item.tsx @@ -87,7 +87,7 @@ export interface ReportItemProps extends TreeItemProps { className?: any; } -function ReportItem(props: Readonly>) { +function ReportItem(props: PropsWithChildren) { const { nodeId } = props; // using a context because TreeItem uses useMemo on this. See report-viewer.js for the provider const { isHighlighted } = useContext(ReportTreeViewContext); diff --git a/src/components/ReportViewer/report-viewer.tsx b/src/components/ReportViewer/report-viewer.tsx index 6e4f670c..d8a7888a 100644 --- a/src/components/ReportViewer/report-viewer.tsx +++ b/src/components/ReportViewer/report-viewer.tsx @@ -48,7 +48,7 @@ export interface ReportViewerProps { export default function ReportViewer({ jsonReport, maxSubReports = MAX_SUB_REPORTS, -}: Readonly) { +}: ReportViewerProps) { const [selectedNode, setSelectedNode] = useState(null); const [expandedNodes, setExpandedNodes] = useState([]); const [logs, setLogs] = useState([]); diff --git a/src/components/ReportViewerDialog/report-viewer-dialog.tsx b/src/components/ReportViewerDialog/report-viewer-dialog.tsx index 08a8a0d7..58ed5f32 100644 --- a/src/components/ReportViewerDialog/report-viewer-dialog.tsx +++ b/src/components/ReportViewerDialog/report-viewer-dialog.tsx @@ -37,9 +37,7 @@ export interface ReportViewerDialogProps { jsonReport: Report; } -export default function ReportViewerDialog( - props: Readonly -) { +export default function ReportViewerDialog(props: ReportViewerDialogProps) { const { title, open, onClose, jsonReport } = props; const [fullScreen, setFullScreen] = useState(false); diff --git a/src/components/SignInCallbackHandler/SignInCallbackHandler.tsx b/src/components/SignInCallbackHandler/SignInCallbackHandler.tsx index 9ab178bc..e0c7f8dd 100644 --- a/src/components/SignInCallbackHandler/SignInCallbackHandler.tsx +++ b/src/components/SignInCallbackHandler/SignInCallbackHandler.tsx @@ -16,7 +16,7 @@ export interface SignInCallbackHandlerProps { function SignInCallbackHandler({ userManager, handleSignInCallback, -}: Readonly) { +}: SignInCallbackHandlerProps) { useEffect(() => { if (userManager !== null) { handleSignInCallback(); diff --git a/src/components/SilentRenewCallbackHandler/SilentRenewCallbackHandler.tsx b/src/components/SilentRenewCallbackHandler/SilentRenewCallbackHandler.tsx index 7fe93b41..8e4c05e8 100644 --- a/src/components/SilentRenewCallbackHandler/SilentRenewCallbackHandler.tsx +++ b/src/components/SilentRenewCallbackHandler/SilentRenewCallbackHandler.tsx @@ -16,7 +16,7 @@ export interface SilentRenewCallbackHandlerProps { function SilentRenewCallbackHandler({ userManager, handleSilentRenewCallback, -}: Readonly) { +}: SilentRenewCallbackHandlerProps) { useEffect(() => { if (userManager !== null) { handleSilentRenewCallback(); diff --git a/src/components/SnackbarProvider/SnackbarProvider.tsx b/src/components/SnackbarProvider/SnackbarProvider.tsx index ee6b7f2c..0c48e3cf 100644 --- a/src/components/SnackbarProvider/SnackbarProvider.tsx +++ b/src/components/SnackbarProvider/SnackbarProvider.tsx @@ -15,7 +15,7 @@ import { } from 'notistack'; /* A wrapper around notistack's SnackbarProvider that provides defaults props */ -function SnackbarProvider(props: Readonly) { +function SnackbarProvider(props: SnackbarProviderProps) { const ref = useRef(null); const action = (key: SnackbarKey) => ( diff --git a/src/components/TopBar/AboutDialog.tsx b/src/components/TopBar/AboutDialog.tsx index 3c8c6c6d..3e983ef3 100644 --- a/src/components/TopBar/AboutDialog.tsx +++ b/src/components/TopBar/AboutDialog.tsx @@ -139,7 +139,6 @@ type GridSuiteModule = { type: ModuleType; version?: string; gitTag?: string; - license?: string; }; export interface AboutDialogProps { @@ -221,7 +220,7 @@ function tooltipTypeLabel(type: string) { return 'about-dialog/module-tooltip-other'; } -function Module({ type, name, version, gitTag }: Readonly) { +function Module({ type, name, version, gitTag }: GridSuiteModule) { return ( ) { +}: AboutDialogProps) { const theme = useTheme(); const [isRefreshing, setIsRefreshing] = useState(false); const [loadingGlobalVersion, setLoadingGlobalVersion] = useState(false); @@ -356,7 +355,6 @@ function AboutDialog({ type: 'app', version: appVersion, gitTag: appGitTag, - license: appLicense, }; (additionalModulesPromise ? Promise.resolve(setLoadingAdditionalModules(true)).then(() => @@ -545,7 +543,6 @@ function AboutDialog({ name={module.name} version={module.version} gitTag={module.gitTag} - license={module.license} /> ))} diff --git a/src/components/TopBar/GridLogo.tsx b/src/components/TopBar/GridLogo.tsx index a4514c74..d76a0d05 100644 --- a/src/components/TopBar/GridLogo.tsx +++ b/src/components/TopBar/GridLogo.tsx @@ -35,7 +35,7 @@ export function LogoText({ appColor, style, onClick, -}: Readonly>) { +}: Partial) { return ( >) { +}: Partial) { return ( <> >) { +}: PropsWithChildren) { const [anchorElSettingsMenu, setAnchorElSettingsMenu] = useState(null); const [anchorElAppsMenu, setAnchorElAppsMenu] = useState( diff --git a/src/components/TreeViewFinder/TreeViewFinder.tsx b/src/components/TreeViewFinder/TreeViewFinder.tsx index 52c30177..8e350807 100644 --- a/src/components/TreeViewFinder/TreeViewFinder.tsx +++ b/src/components/TreeViewFinder/TreeViewFinder.tsx @@ -144,7 +144,7 @@ export interface TreeViewFinderProps { * @param {Object} [selected] - ids of selected items * @param {Array} [expanded] - ids of the expanded items */ -function TreeViewFinder(props: Readonly) { +function TreeViewFinder(props: TreeViewFinderProps) { const intl = useIntl(); const { classes = {}, diff --git a/src/components/dialogs/custom-mui-dialog.tsx b/src/components/dialogs/custom-mui-dialog.tsx index 2363a493..c67a573b 100644 --- a/src/components/dialogs/custom-mui-dialog.tsx +++ b/src/components/dialogs/custom-mui-dialog.tsx @@ -63,7 +63,7 @@ function CustomMuiDialog({ onCancel, children, language, -}: Readonly) { +}: ICustomMuiDialog) { const { handleSubmit } = formMethods; const handleCancel = (event: React.MouseEvent) => { diff --git a/src/components/dialogs/description-modification-dialog.tsx b/src/components/dialogs/description-modification-dialog.tsx index e709c31d..da796f36 100644 --- a/src/components/dialogs/description-modification-dialog.tsx +++ b/src/components/dialogs/description-modification-dialog.tsx @@ -38,7 +38,7 @@ function DescriptionModificationDialog({ open, onClose, updateElement, -}: Readonly) { +}: IDescriptionModificationDialog) { const { snackError } = useSnackMessage(); const emptyFormData = { diff --git a/src/components/dialogs/modify-element-selection.tsx b/src/components/dialogs/modify-element-selection.tsx index 6f4fcea0..dee56b09 100644 --- a/src/components/dialogs/modify-element-selection.tsx +++ b/src/components/dialogs/modify-element-selection.tsx @@ -25,7 +25,7 @@ export interface ModifyElementSelectionProps { onElementValidated?: (elementId: UUID) => void; } -function ModifyElementSelection(props: Readonly) { +function ModifyElementSelection(props: ModifyElementSelectionProps) { const intl = useIntl(); const { elementType, diff --git a/src/components/dialogs/popup-confirmation-dialog.tsx b/src/components/dialogs/popup-confirmation-dialog.tsx index ee4e7b75..26ed036d 100644 --- a/src/components/dialogs/popup-confirmation-dialog.tsx +++ b/src/components/dialogs/popup-confirmation-dialog.tsx @@ -28,7 +28,7 @@ function PopupConfirmationDialog({ openConfirmationPopup, setOpenConfirmationPopup, handlePopupConfirmation, -}: Readonly) { +}: PopupConfirmationDialogProps) { return ( ) { +}: CriteriaBasedFilterEditionDialogProps) { const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); diff --git a/src/components/filter/criteria-based/criteria-based-form.tsx b/src/components/filter/criteria-based/criteria-based-form.tsx index 23be967d..2bc90c3b 100644 --- a/src/components/filter/criteria-based/criteria-based-form.tsx +++ b/src/components/filter/criteria-based/criteria-based-form.tsx @@ -20,7 +20,7 @@ export interface CriteriaBasedFormProps { function CriteriaBasedForm({ equipments, defaultValues, -}: Readonly) { +}: CriteriaBasedFormProps) { const { getValues, setValue } = useFormContext(); const watchEquipmentType = useWatch({ diff --git a/src/components/filter/criteria-based/filter-free-properties.tsx b/src/components/filter/criteria-based/filter-free-properties.tsx index 6a5a2d21..d1c31f6d 100644 --- a/src/components/filter/criteria-based/filter-free-properties.tsx +++ b/src/components/filter/criteria-based/filter-free-properties.tsx @@ -36,7 +36,7 @@ interface FilterFreePropertiesProps { function FilterFreeProperties({ freePropertiesType, predefined, -}: Readonly) { +}: FilterFreePropertiesProps) { const watchEquipmentType = useWatch({ name: FieldConstants.EQUIPMENT_TYPE, }); diff --git a/src/components/filter/criteria-based/filter-property.tsx b/src/components/filter/criteria-based/filter-property.tsx index bdc93439..8fce312b 100644 --- a/src/components/filter/criteria-based/filter-property.tsx +++ b/src/components/filter/criteria-based/filter-property.tsx @@ -28,7 +28,7 @@ interface FilterPropertyProps { propertyType: string; } -function FilterProperty(props: Readonly) { +function FilterProperty(props: FilterPropertyProps) { const { propertyType, index, predefined, valuesFields, handleDelete } = props; const { setValue } = useFormContext(); diff --git a/src/components/filter/expert/expert-filter-edition-dialog.tsx b/src/components/filter/expert/expert-filter-edition-dialog.tsx index 3886399f..41a839b1 100644 --- a/src/components/filter/expert/expert-filter-edition-dialog.tsx +++ b/src/components/filter/expert/expert-filter-edition-dialog.tsx @@ -61,7 +61,7 @@ function ExpertFilterEditionDialog({ activeDirectory, elementExists, language, -}: Readonly) { +}: ExpertFilterEditionDialogProps) { const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx index 20208bfd..67220d88 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-edition-dialog.tsx @@ -65,7 +65,7 @@ function ExplicitNamingFilterEditionDialog({ activeDirectory, elementExists, language, -}: Readonly) { +}: ExplicitNamingFilterEditionDialogProps) { const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); diff --git a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx index d93ff49e..53ff62ff 100644 --- a/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx +++ b/src/components/filter/explicit-naming/explicit-naming-filter-form.tsx @@ -26,7 +26,7 @@ import { FILTER_EQUIPMENTS } from '../utils/filter-form-utils'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { ElementType } from '../../../utils/ElementType'; import ModifyElementSelection from '../../dialogs/modify-element-selection'; -import exportFilter from '../../../services/study'; +import { exportFilter } from '../../../services/study'; export const FILTER_EQUIPMENTS_ATTRIBUTES = 'filterEquipmentsAttributes'; @@ -122,7 +122,7 @@ interface ExplicitNamingFilterFormProps { function ExplicitNamingFilterForm({ sourceFilterForExplicitNamingConversion, -}: Readonly) { +}: ExplicitNamingFilterFormProps) { const intl = useIntl(); const { snackError } = useSnackMessage(); diff --git a/src/components/filter/filter-creation-dialog.tsx b/src/components/filter/filter-creation-dialog.tsx index 738b5ab3..61edd14b 100644 --- a/src/components/filter/filter-creation-dialog.tsx +++ b/src/components/filter/filter-creation-dialog.tsx @@ -81,7 +81,7 @@ function FilterCreationDialog({ elementExists, language, sourceFilterForExplicitNamingConversion = undefined, -}: Readonly) { +}: FilterCreationDialogProps) { const { snackError } = useSnackMessage(); const formMethods = useForm({ diff --git a/src/components/filter/filter-form.tsx b/src/components/filter/filter-form.tsx index 04a2dda6..6eb49e9d 100644 --- a/src/components/filter/filter-form.tsx +++ b/src/components/filter/filter-form.tsx @@ -29,7 +29,7 @@ interface FilterFormProps { }; } -function FilterForm(props: Readonly) { +function FilterForm(props: FilterFormProps) { const { sourceFilterForExplicitNamingConversion, creation, diff --git a/src/components/inputs/react-hook-form/ExpandingTextField.tsx b/src/components/inputs/react-hook-form/ExpandingTextField.tsx index bcbbd250..9022a05c 100644 --- a/src/components/inputs/react-hook-form/ExpandingTextField.tsx +++ b/src/components/inputs/react-hook-form/ExpandingTextField.tsx @@ -30,7 +30,7 @@ function ExpandingTextField({ label, textFieldFormProps, ...otherTexFieldProps -}: Readonly) { +}: ExpandingTextFieldProps) { const [isFocused, setIsFocused] = useState(false); const { control } = useCustomFormContext(); const descriptionWatch = useWatch({ diff --git a/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx b/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx index ea7617d7..d661d1f2 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/bottom-right-buttons.tsx @@ -47,7 +47,7 @@ function BottomRightButtons({ handleMoveRowDown, useFieldArrayOutput, csvProps, -}: Readonly) { +}: BottomRightButtonsProps) { const [uploaderOpen, setUploaderOpen] = useState(false); const intl = useIntl(); diff --git a/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx b/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx index 810ee7f1..9d9fb8fd 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx @@ -46,7 +46,7 @@ function CsvUploader({ validateData = () => true, getDataFromCsv, useFieldArrayOutput, -}: Readonly) { +}: CsvUploaderProps) { const watchTableValues = useWatch({ name }); const { append, replace } = useFieldArrayOutput; const [createError, setCreateError] = React.useState(''); diff --git a/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx b/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx index 924c3ec0..564f33ca 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/custom-ag-grid-table.tsx @@ -115,7 +115,7 @@ function CustomAgGridTable({ alwaysShowVerticalScroll, stopEditingWhenCellsLoseFocus, ...props -}: Readonly) { +}: CustomAgGridTableProps) { const theme: any = useTheme(); const [gridApi, setGridApi] = useState(null); const [selectedRows, setSelectedRows] = useState([]); diff --git a/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx b/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx index 65067d67..45d0f15a 100644 --- a/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx +++ b/src/components/inputs/react-hook-form/autocomplete-inputs/autocomplete-input.tsx @@ -60,7 +60,7 @@ function AutocompleteInput({ onChangeCallback, // method called when input value is changing formProps, ...props -}: Readonly) { +}: AutocompleteInputProps) { const { validationSchema, getValues, removeOptional } = useCustomFormContext(); const { diff --git a/src/components/inputs/react-hook-form/booleans/boolean-input.tsx b/src/components/inputs/react-hook-form/booleans/boolean-input.tsx index bea80ac1..060518f0 100644 --- a/src/components/inputs/react-hook-form/booleans/boolean-input.tsx +++ b/src/components/inputs/react-hook-form/booleans/boolean-input.tsx @@ -17,12 +17,7 @@ export interface BooleanInputProps { Input: typeof Switch | typeof Checkbox; } -function BooleanInput({ - name, - label, - formProps, - Input, -}: Readonly) { +function BooleanInput({ name, label, formProps, Input }: BooleanInputProps) { const { field: { onChange, value, ref }, } = useController>({ name }); diff --git a/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx b/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx index 87e81893..c5b7d56f 100644 --- a/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx +++ b/src/components/inputs/react-hook-form/booleans/checkbox-input.tsx @@ -14,11 +14,7 @@ export interface CheckboxInputProps { formProps?: CheckboxProps; } -function CheckboxInput({ - name, - label, - formProps, -}: Readonly) { +function CheckboxInput({ name, label, formProps }: CheckboxInputProps) { return ( ) { +function SwitchInput({ name, label, formProps }: SwitchInputProps) { return ( ) { +}: DirectoryItemsInputProps) { const { snackError } = useSnackMessage(); const intl = useIntl(); const [selected, setSelected] = useState([]); diff --git a/src/components/inputs/react-hook-form/error-management/error-input.tsx b/src/components/inputs/react-hook-form/error-management/error-input.tsx index cf0210bd..195137ea 100644 --- a/src/components/inputs/react-hook-form/error-management/error-input.tsx +++ b/src/components/inputs/react-hook-form/error-management/error-input.tsx @@ -25,7 +25,7 @@ export interface ErrorInputProps { }) => React.ReactNode; } -function ErrorInput({ name, InputField }: Readonly) { +function ErrorInput({ name, InputField }: ErrorInputProps) { const { fieldState: { error }, formState: { isSubmitting }, diff --git a/src/components/inputs/react-hook-form/error-management/field-error-alert.tsx b/src/components/inputs/react-hook-form/error-management/field-error-alert.tsx index 939b8e7d..68306601 100644 --- a/src/components/inputs/react-hook-form/error-management/field-error-alert.tsx +++ b/src/components/inputs/react-hook-form/error-management/field-error-alert.tsx @@ -14,7 +14,7 @@ interface FieldErrorAlertProps { // component to display alert when a specific rhf field is in error // this component needs to be isolated to avoid too many rerenders -function FieldErrorAlert({ message }: Readonly) { +function FieldErrorAlert({ message }: FieldErrorAlertProps) { return ( {message} diff --git a/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx b/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx index 40f63a33..a16e8ccb 100644 --- a/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx +++ b/src/components/inputs/react-hook-form/error-management/mid-form-error.tsx @@ -9,7 +9,7 @@ import { Box } from '@mui/material'; import { ReactNode } from 'react'; // component to display error message in the middle of dialog -function MidFormError({ message }: Readonly<{ message: string | ReactNode }>) { +function MidFormError({ message }: { message: string | ReactNode }) { return ( ({ diff --git a/src/components/inputs/react-hook-form/numbers/float-input.tsx b/src/components/inputs/react-hook-form/numbers/float-input.tsx index 58aacf14..24acc2b4 100644 --- a/src/components/inputs/react-hook-form/numbers/float-input.tsx +++ b/src/components/inputs/react-hook-form/numbers/float-input.tsx @@ -37,7 +37,7 @@ const normalizeFixed = (number: number) => { }); }; -function FloatInput(props: Readonly) { +function FloatInput(props: FloatInputProps) { const inputTransform = (value: Input) => { if (typeof value === 'number' && !Number.isNaN(value)) { // if we have a parsed real number, normalize like we do after each diff --git a/src/components/inputs/react-hook-form/numbers/integer-input.tsx b/src/components/inputs/react-hook-form/numbers/integer-input.tsx index 1e74f581..c6d93728 100644 --- a/src/components/inputs/react-hook-form/numbers/integer-input.tsx +++ b/src/components/inputs/react-hook-form/numbers/integer-input.tsx @@ -7,7 +7,7 @@ import TextInput, { TextInputProps } from '../text-input'; import { isIntegerNumber } from './utils'; -function IntegerInput(props: Readonly) { +function IntegerInput(props: TextInputProps) { const inputTransform = (value: string | number | null) => { if (value === '-') { return value; diff --git a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx index 3be307d0..a4b6beb8 100644 --- a/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx +++ b/src/components/inputs/react-hook-form/provider/custom-form-provider.tsx @@ -27,7 +27,7 @@ export const CustomFormContext = createContext({ language: getSystemLanguage(), }); -function CustomFormProvider(props: Readonly) { +function CustomFormProvider(props: CustomFormProviderProps) { const { validationSchema, removeOptional, diff --git a/src/components/inputs/react-hook-form/radio-input.tsx b/src/components/inputs/react-hook-form/radio-input.tsx index 1f0ce3bf..830abc13 100644 --- a/src/components/inputs/react-hook-form/radio-input.tsx +++ b/src/components/inputs/react-hook-form/radio-input.tsx @@ -30,13 +30,7 @@ interface RadioInputProps { formProps?: Omit; } -function RadioInput({ - name, - label, - id, - options, - formProps, -}: Readonly) { +function RadioInput({ name, label, id, options, formProps }: RadioInputProps) { const { field: { onChange, value }, } = useController({ name }); diff --git a/src/components/inputs/react-hook-form/range-input.tsx b/src/components/inputs/react-hook-form/range-input.tsx index 774fa8a5..67f91900 100644 --- a/src/components/inputs/react-hook-form/range-input.tsx +++ b/src/components/inputs/react-hook-form/range-input.tsx @@ -72,7 +72,7 @@ interface RangeInputProps { label: string; } -function RangeInput({ name, label }: Readonly) { +function RangeInput({ name, label }: RangeInputProps) { const watchOperationType = useWatch({ name: `${name}.${FieldConstants.OPERATION_TYPE}`, }); diff --git a/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx b/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx index 836ae713..33af576b 100644 --- a/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/countries-input.tsx @@ -16,7 +16,7 @@ interface CountryInputProps { label: string; } -function CountriesInput({ name, label }: Readonly) { +function CountriesInput({ name, label }: CountryInputProps) { const { language } = useCustomFormContext(); const { translate, countryCodes } = useLocalizedCountries(language); diff --git a/src/components/inputs/react-hook-form/select-inputs/select-input.tsx b/src/components/inputs/react-hook-form/select-inputs/select-input.tsx index c8ed7528..9b821887 100644 --- a/src/components/inputs/react-hook-form/select-inputs/select-input.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/select-input.tsx @@ -19,7 +19,7 @@ export interface SelectInputProps options: Option[]; } -function SelectInput(props: Readonly) { +function SelectInput(props: SelectInputProps) { const intl = useIntl(); const { options } = props; const inputTransform = (value: Option | null) => { diff --git a/src/components/inputs/react-hook-form/slider-input.tsx b/src/components/inputs/react-hook-form/slider-input.tsx index b1542a27..02e88b4d 100644 --- a/src/components/inputs/react-hook-form/slider-input.tsx +++ b/src/components/inputs/react-hook-form/slider-input.tsx @@ -21,7 +21,7 @@ function SliderInput({ step, size = 'small', onValueChanged = identity, -}: Readonly) { +}: SliderInputProps) { const { field: { onChange, value }, } = useController({ name }); diff --git a/src/components/inputs/react-hook-form/text-input.tsx b/src/components/inputs/react-hook-form/text-input.tsx index d0f68cdc..408aa649 100644 --- a/src/components/inputs/react-hook-form/text-input.tsx +++ b/src/components/inputs/react-hook-form/text-input.tsx @@ -62,7 +62,7 @@ function TextInput({ previousValue, clearable, formProps, -}: Readonly) { +}: TextInputProps) { const { validationSchema, getValues, removeOptional } = useCustomFormContext(); const { diff --git a/src/components/inputs/react-hook-form/unique-name-input.tsx b/src/components/inputs/react-hook-form/unique-name-input.tsx index 5b914dee..bf8434b9 100644 --- a/src/components/inputs/react-hook-form/unique-name-input.tsx +++ b/src/components/inputs/react-hook-form/unique-name-input.tsx @@ -49,7 +49,7 @@ function UniqueNameInput({ formProps, activeDirectory, elementExists, -}: Readonly) { +}: UniqueNameInputProps) { const { field: { onChange, onBlur, value, ref }, fieldState: { error, isDirty }, diff --git a/src/components/inputs/react-hook-form/utils/field-label.tsx b/src/components/inputs/react-hook-form/utils/field-label.tsx index 261c6486..c2810555 100644 --- a/src/components/inputs/react-hook-form/utils/field-label.tsx +++ b/src/components/inputs/react-hook-form/utils/field-label.tsx @@ -16,7 +16,7 @@ function FieldLabel({ label, optional = false, values = undefined, -}: Readonly) { +}: FieldLabelProps) { return ( <> diff --git a/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx b/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx index 8f714357..1095db1c 100644 --- a/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx +++ b/src/components/inputs/react-hook-form/utils/text-field-with-adornment.tsx @@ -24,7 +24,7 @@ export type TextFieldWithAdornmentProps = TextFieldProps & { handleClearValue?: () => void; }; -function TextFieldWithAdornment(props: Readonly) { +function TextFieldWithAdornment(props: TextFieldWithAdornmentProps) { const { adornmentPosition, adornmentText, diff --git a/src/components/inputs/react-query-builder/add-button.tsx b/src/components/inputs/react-query-builder/add-button.tsx index 8dd35ac8..4fd2ea13 100644 --- a/src/components/inputs/react-query-builder/add-button.tsx +++ b/src/components/inputs/react-query-builder/add-button.tsx @@ -15,7 +15,7 @@ interface ActionWithRulesAndAddersWithLabelProps label: string; } -function AddButton(props: Readonly) { +function AddButton(props: ActionWithRulesAndAddersWithLabelProps) { const { label, handleOnClick } = props; return ( diff --git a/src/components/inputs/react-query-builder/combinator-selector.tsx b/src/components/inputs/react-query-builder/combinator-selector.tsx index 5599489a..9dadef6e 100644 --- a/src/components/inputs/react-query-builder/combinator-selector.tsx +++ b/src/components/inputs/react-query-builder/combinator-selector.tsx @@ -10,7 +10,7 @@ import { useCallback, useState } from 'react'; import { MaterialValueSelector } from '@react-querybuilder/material'; import PopupConfirmationDialog from '../../dialogs/popup-confirmation-dialog'; -function CombinatorSelector(props: Readonly) { +function CombinatorSelector(props: CombinatorSelectorProps) { const { value, handleOnChange } = props; const [tempCombinator, setTempCombinator] = useState(value); const [openPopup, setOpenPopup] = useState(false); diff --git a/src/components/inputs/react-query-builder/composite-rule-editor/group-value-editor.tsx b/src/components/inputs/react-query-builder/composite-rule-editor/group-value-editor.tsx index 14c0ff05..ebdce3c7 100644 --- a/src/components/inputs/react-query-builder/composite-rule-editor/group-value-editor.tsx +++ b/src/components/inputs/react-query-builder/composite-rule-editor/group-value-editor.tsx @@ -22,7 +22,7 @@ const styles = { }), }; -function GroupValueEditor(props: Readonly>) { +function GroupValueEditor(props: ValueEditorProps) { const { fieldData: { combinator, children }, value, diff --git a/src/components/inputs/react-query-builder/composite-rule-editor/rule-value-editor.tsx b/src/components/inputs/react-query-builder/composite-rule-editor/rule-value-editor.tsx index f9bc3ec3..86a85570 100644 --- a/src/components/inputs/react-query-builder/composite-rule-editor/rule-value-editor.tsx +++ b/src/components/inputs/react-query-builder/composite-rule-editor/rule-value-editor.tsx @@ -25,7 +25,7 @@ type RuleValueEditorProps = ValueEditorProps & { handleOnChangeRule: (rule: CompositeRule) => void; }; -function RuleValueEditor(props: Readonly) { +function RuleValueEditor(props: RuleValueEditorProps) { const { schema: { controls: { valueEditor: ValueEditorControlElement }, diff --git a/src/components/inputs/react-query-builder/country-value-editor.tsx b/src/components/inputs/react-query-builder/country-value-editor.tsx index d1d97bc9..768d4331 100644 --- a/src/components/inputs/react-query-builder/country-value-editor.tsx +++ b/src/components/inputs/react-query-builder/country-value-editor.tsx @@ -14,7 +14,7 @@ import useValid from './use-valid'; import { useLocalizedCountries } from '../../../hooks/localized-countries-hook'; import useCustomFormContext from '../react-hook-form/provider/use-custom-form-context'; -function CountryValueEditor(props: Readonly) { +function CountryValueEditor(props: ValueEditorProps) { const { language } = useCustomFormContext(); const { translate, countryCodes } = useLocalizedCountries(language); const { value, handleOnChange } = props; diff --git a/src/components/inputs/react-query-builder/custom-react-query-builder.tsx b/src/components/inputs/react-query-builder/custom-react-query-builder.tsx index 938b9b41..ea98d503 100644 --- a/src/components/inputs/react-query-builder/custom-react-query-builder.tsx +++ b/src/components/inputs/react-query-builder/custom-react-query-builder.tsx @@ -40,17 +40,15 @@ interface CustomReactQueryBuilderProps { fields: Field[]; } -function RuleAddButton(props: Readonly) { +function RuleAddButton(props: ActionWithRulesAndAddersProps) { return ; } -function GroupAddButton(props: Readonly) { +function GroupAddButton(props: ActionWithRulesAndAddersProps) { return ; } -function CustomReactQueryBuilder( - props: Readonly -) { +function CustomReactQueryBuilder(props: CustomReactQueryBuilderProps) { const { name, fields } = props; const { getValues, diff --git a/src/components/inputs/react-query-builder/element-value-editor.tsx b/src/components/inputs/react-query-builder/element-value-editor.tsx index e2fcd810..9c249f99 100644 --- a/src/components/inputs/react-query-builder/element-value-editor.tsx +++ b/src/components/inputs/react-query-builder/element-value-editor.tsx @@ -22,7 +22,7 @@ interface ElementValueEditorProps { defaultValue?: any; } -function ElementValueEditor(props: Readonly) { +function ElementValueEditor(props: ElementValueEditorProps) { const { defaultValue, name, diff --git a/src/components/inputs/react-query-builder/property-value-editor.tsx b/src/components/inputs/react-query-builder/property-value-editor.tsx index 4c9b1e59..f4d37bf1 100644 --- a/src/components/inputs/react-query-builder/property-value-editor.tsx +++ b/src/components/inputs/react-query-builder/property-value-editor.tsx @@ -23,7 +23,7 @@ interface ExpertFilterPropertyProps { valueEditorProps: ValueEditorProps; } -function PropertyValueEditor(props: Readonly) { +function PropertyValueEditor(props: ExpertFilterPropertyProps) { const { equipmentType, valueEditorProps } = props; const valid = useValid(valueEditorProps); const intl = useIntl(); diff --git a/src/components/inputs/react-query-builder/remove-button.tsx b/src/components/inputs/react-query-builder/remove-button.tsx index 2d01f2aa..231c1472 100644 --- a/src/components/inputs/react-query-builder/remove-button.tsx +++ b/src/components/inputs/react-query-builder/remove-button.tsx @@ -17,7 +17,7 @@ import { const EXPERT_FILTER_QUERY = 'rules'; -function RemoveButton(props: Readonly) { +function RemoveButton(props: ActionWithRulesProps) { const { path, className } = props; const { field: { value: query, onChange }, diff --git a/src/components/inputs/react-query-builder/text-value-editor.tsx b/src/components/inputs/react-query-builder/text-value-editor.tsx index 67b9dd0f..3f21ef5a 100644 --- a/src/components/inputs/react-query-builder/text-value-editor.tsx +++ b/src/components/inputs/react-query-builder/text-value-editor.tsx @@ -11,7 +11,7 @@ import { Autocomplete, TextField } from '@mui/material'; import useConvertValue from './use-convert-value'; import useValid from './use-valid'; -function TextValueEditor(props: Readonly) { +function TextValueEditor(props: ValueEditorProps) { useConvertValue(props); const valid = useValid(props); diff --git a/src/components/inputs/react-query-builder/translated-value-editor.tsx b/src/components/inputs/react-query-builder/translated-value-editor.tsx index edfb4564..20e0eaa9 100644 --- a/src/components/inputs/react-query-builder/translated-value-editor.tsx +++ b/src/components/inputs/react-query-builder/translated-value-editor.tsx @@ -13,7 +13,7 @@ import { Autocomplete, TextField } from '@mui/material'; import useConvertValue from './use-convert-value'; import useValid from './use-valid'; -function TranslatedValueEditor(props: Readonly) { +function TranslatedValueEditor(props: ValueEditorProps) { const intl = useIntl(); const { values, value, handleOnChange } = props; diff --git a/src/components/inputs/react-query-builder/value-editor.tsx b/src/components/inputs/react-query-builder/value-editor.tsx index a50041ec..aa035715 100644 --- a/src/components/inputs/react-query-builder/value-editor.tsx +++ b/src/components/inputs/react-query-builder/value-editor.tsx @@ -36,7 +36,7 @@ const styles = { }, }; -function ValueEditor(props: Readonly) { +function ValueEditor(props: ValueEditorProps) { const { field, operator, diff --git a/src/components/inputs/react-query-builder/value-selector.tsx b/src/components/inputs/react-query-builder/value-selector.tsx index 34d21867..dea98541 100644 --- a/src/components/inputs/react-query-builder/value-selector.tsx +++ b/src/components/inputs/react-query-builder/value-selector.tsx @@ -8,7 +8,7 @@ import { ValueSelectorProps } from 'react-querybuilder'; import { MaterialValueSelector } from '@react-querybuilder/material'; -function ValueSelector(props: Readonly) { +function ValueSelector(props: ValueSelectorProps) { return ( ) { +function SelectClearable(props: SelectClearableProps) { const { value, onChange, label, options, ...otherProps } = props; const intl = useIntl(); diff --git a/src/services/study.ts b/src/services/study.ts index 7235fb58..b24371de 100644 --- a/src/services/study.ts +++ b/src/services/study.ts @@ -10,7 +10,12 @@ import { backendFetchJson } from './utils'; const PREFIX_STUDY_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/study`; -function exportFilter(studyUuid: UUID, filterUuid?: UUID, token?: string) { +// eslint-disable-next-line import/prefer-default-export +export function exportFilter( + studyUuid: UUID, + filterUuid?: UUID, + token?: string +) { console.info('get filter export on study root node'); return backendFetchJson( `${PREFIX_STUDY_QUERIES}/v1/studies/${studyUuid}/filters/${filterUuid}/elements`, @@ -21,5 +26,3 @@ function exportFilter(studyUuid: UUID, filterUuid?: UUID, token?: string) { token ); } - -export default exportFilter; From d6d58226b62c4a115ce3823c8afddf28f0e17a8e Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Mon, 1 Jul 2024 16:07:41 +0200 Subject: [PATCH 12/16] disable no-plusplus rule for for loops Signed-off-by: Ayoub LABIDI --- .eslintrc.json | 3 ++- .../MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx | 6 +++--- .../ag-grid-table/csv-uploader/csv-uploader.tsx | 2 +- src/utils/algos.ts | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 07133157..847a599f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,6 +22,7 @@ "curly": "error", "no-console": "off", "react/jsx-props-no-spreading": "off", - "react/require-default-props": "off" + "react/require-default-props": "off", + "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }] } } diff --git a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx index 8399d129..e37bade7 100644 --- a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx +++ b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx @@ -174,7 +174,7 @@ const codedColumnsFromKeyAndDirection = ( let ret = []; const columIndexByKey: Record = {}; - for (let colIdx = 0; colIdx < columns.length; colIdx += 1) { + for (let colIdx = 0; colIdx < columns.length; colIdx++) { const col = columns[colIdx]; const colKey = col.dataKey; columIndexByKey[colKey] = colIdx; @@ -495,11 +495,11 @@ export class KeyedColumnsRowIndexer { } }); - for (let rowIdx = 0; rowIdx < rows.length; rowIdx += 1) { + for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) { const row = rows[rowIdx]; let acceptsRow = true; const acceptedOnRow: Record = {}; - for (let colIdx = 0; colIdx < columns.length; colIdx += 1) { + for (let colIdx = 0; colIdx < columns.length; colIdx++) { const col = columns[colIdx]; const helper = getHelper(col); const colKey = col.dataKey; diff --git a/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx b/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx index 9d9fb8fd..7b668501 100644 --- a/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx +++ b/src/components/inputs/react-hook-form/ag-grid-table/csv-uploader/csv-uploader.tsx @@ -75,7 +75,7 @@ function CsvUploader({ } // validate the headers - for (let i = 0; i < fileHeaders.length; i += 1) { + for (let i = 0; i < fileHeaders.length; i++) { if (fileHeaders[i] !== '' && rows[0][i] !== fileHeaders[i]) { setCreateError( intl.formatMessage({ id: 'wrongCsvHeadersError' }) diff --git a/src/utils/algos.ts b/src/utils/algos.ts index 9292edac..32c0b3c0 100644 --- a/src/utils/algos.ts +++ b/src/utils/algos.ts @@ -16,7 +16,7 @@ function equalsArray(a: Array, b: Array) { return false; } - for (let i = 0, l = a.length; i < l; i += 1) { + for (let i = 0, l = a.length; i < l; i++) { if (a[i] instanceof Array && b[i] instanceof Array) { if (!equalsArray(a[i], b[i])) { return false; From 752c9f7650b0be8ff162424950f2e6874d1dd716 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Tue, 2 Jul 2024 15:57:56 +0200 Subject: [PATCH 13/16] Requested changes + fixes Signed-off-by: Ayoub LABIDI --- demo/src/TableTab.jsx | 5 ++-- demo/src/app.jsx | 8 +++--- demo/src/equipment-search.tsx | 3 +-- demo/src/right-resizable-box.jsx | 1 - package-lock.json | 7 ++--- package.json | 6 ++--- .../ExpandableGroup/expandable-group.tsx | 12 +++------ src/components/Login/Login.tsx | 2 +- src/components/Login/Logout.tsx | 3 ++- .../KeyedColumnsRowIndexer.tsx | 3 +-- .../MuiVirtualizedTable.tsx | 27 +------------------ src/components/TopBar/AboutDialog.tsx | 11 ++++---- .../TreeViewFinder/TreeViewFinder.tsx | 8 +++--- .../select-inputs/mui-select-input.tsx | 1 + .../inputs/react-hook-form/slider-input.tsx | 7 +---- .../inputs/react-hook-form/text-input.tsx | 4 +-- src/services/index.ts | 2 +- src/utils/AuthService.ts | 12 ++++----- src/utils/Events.ts | 3 ++- src/utils/UserManagerMock.ts | 9 ++++--- 20 files changed, 53 insertions(+), 81 deletions(-) diff --git a/demo/src/TableTab.jsx b/demo/src/TableTab.jsx index f5f62308..a623d3d6 100644 --- a/demo/src/TableTab.jsx +++ b/demo/src/TableTab.jsx @@ -16,10 +16,11 @@ import { TextField, } from '@mui/material'; import { DEFAULT_CELL_PADDING } from '../../src'; -import MuiVirtualizedTable, { +import { ChangeWays, generateMuiVirtualizedTableClass, KeyedColumnsRowIndexer, + MuiVirtualizedTable, } from '../../src/components/MuiVirtualizedTable'; import { toNestedGlobalSelectors } from '../../src/utils/styles'; @@ -93,7 +94,7 @@ const stylesEmotion = ({ theme }) => ); const StyledVirtualizedTable = styled(MuiVirtualizedTable)(stylesEmotion); -export function TableTab() { +function TableTab() { const [usesCustomStyles, setUsesCustomStyles] = useState(true); const VirtualizedTable = usesCustomStyles diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 35717e09..4b9843b5 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -26,7 +26,7 @@ import { styled } from '@mui/system'; import { useMatch } from 'react-router'; import { IntlProvider, useIntl } from 'react-intl'; import { BrowserRouter, useLocation, useNavigate } from 'react-router-dom'; -import TopBar from '../../src/components/TopBar'; +import { useCallback, useEffect, useRef, useState } from 'react'; import SnackbarProvider from '../../src/components/SnackbarProvider'; import AuthenticationRouter from '../../src/components/AuthenticationRouter'; import CardErrorBoundary from '../../src/components/CardErrorBoundary'; @@ -65,6 +65,7 @@ import { top_bar_fr, treeview_finder_en, treeview_finder_fr, + TopBar, } from '../../src'; import { useSnackMessage } from '../../src/hooks/useSnackMessage'; @@ -75,7 +76,8 @@ import PowsyblLogo from '../images/powsybl_logo.svg?react'; import AppPackage from '../../package.json'; import ReportViewerDialog from '../../src/components/ReportViewerDialog'; -import TreeViewFinder, { +import { + TreeViewFinder, generateTreeViewFinderClass, } from '../../src/components/TreeViewFinder'; import TreeViewFinderConfig from './TreeViewFinderConfig'; @@ -94,7 +96,7 @@ import { EquipmentItem } from '../../src/components/ElementSearchDialog/equipmen import OverflowableText from '../../src/components/OverflowableText'; import { setShowAuthenticationRouterLogin } from '../../src/redux/actions'; -import { TableTab } from './TableTab'; +import TableTab from './TableTab'; import FlatParametersTab from './FlatParametersTab'; import { toNestedGlobalSelectors } from '../../src/utils/styles'; diff --git a/demo/src/equipment-search.tsx b/demo/src/equipment-search.tsx index f0eb2830..eca1e660 100644 --- a/demo/src/equipment-search.tsx +++ b/demo/src/equipment-search.tsx @@ -38,8 +38,7 @@ const equipmentsToReturn: AnyElementInterface[] = [ }, ]; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const searchEquipmentPromise = (term: string) => { +const searchEquipmentPromise = () => { return new Promise((resolve) => { setTimeout(() => { resolve(equipmentsToReturn); diff --git a/demo/src/right-resizable-box.jsx b/demo/src/right-resizable-box.jsx index bc8c57f7..c12998ad 100644 --- a/demo/src/right-resizable-box.jsx +++ b/demo/src/right-resizable-box.jsx @@ -93,7 +93,6 @@ function RightResizableBox(props) { } RightResizableBox.defaultProps = { - children: null, disableResize: false, fullscreen: false, hide: false, diff --git a/package-lock.json b/package-lock.json index fa1660be..04fb4c7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,6 @@ "version": "0.61.1", "license": "MPL-2.0", "dependencies": { - "@mui/system": "^5.15.15", - "@mui/x-tree-view": "^6.17.0", "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", "autosuggest-highlight": "^3.3.4", @@ -19,7 +17,6 @@ "localized-countries": "^2.0.0", "memoize-one": "^6.0.0", "oidc-client": "^1.11.5", - "papaparse": "^5.4.1", "prop-types": "^15.8.1", "react-csv-downloader": "^3.1.0", "react-dnd": "^16.0.1", @@ -112,9 +109,12 @@ "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", "@mui/material": "^5.15.14", + "@mui/system": "^5.15.15", + "@mui/x-tree-view": "^6.17.0", "ag-grid-community": "^31.0.0", "ag-grid-react": "^31.2.0", "notistack": "^3.0.1", + "papaparse": "^5.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.51.2", @@ -4418,6 +4418,7 @@ "version": "6.17.0", "resolved": "https://registry.npmjs.org/@mui/x-tree-view/-/x-tree-view-6.17.0.tgz", "integrity": "sha512-09dc2D+Rjg2z8KOaxbUXyPi0aw7fm2jurEtV8Xw48xJ00joLWd5QJm1/v4CarEvaiyhTQzHImNqdgeJW8ZQB6g==", + "peer": true, "dependencies": { "@babel/runtime": "^7.23.2", "@mui/base": "^5.0.0-beta.20", diff --git a/package.json b/package.json index 59714cb9..36c8d65c 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,6 @@ "licenses-check": "license-checker --summary --excludePrivatePackages --production --onlyAllow \"$( jq -r .onlyAllow[] license-checker-config.json | tr '\n' ';')\" --excludePackages \"$( jq -r .excludePackages[] license-checker-config.json | tr '\n' ';')\"" }, "dependencies": { - "@mui/system": "^5.15.15", - "@mui/x-tree-view": "^6.17.0", "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", "autosuggest-highlight": "^3.3.4", @@ -38,7 +36,6 @@ "localized-countries": "^2.0.0", "memoize-one": "^6.0.0", "oidc-client": "^1.11.5", - "papaparse": "^5.4.1", "prop-types": "^15.8.1", "react-csv-downloader": "^3.1.0", "react-dnd": "^16.0.1", @@ -48,6 +45,9 @@ "uuid": "^9.0.1" }, "peerDependencies": { + "@mui/system": "^5.15.15", + "@mui/x-tree-view": "^6.17.0", + "papaparse": "^5.4.1", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@hookform/resolvers": "^3.3.4", diff --git a/src/components/ExpandableGroup/expandable-group.tsx b/src/components/ExpandableGroup/expandable-group.tsx index b8991730..320b1e9c 100644 --- a/src/components/ExpandableGroup/expandable-group.tsx +++ b/src/components/ExpandableGroup/expandable-group.tsx @@ -16,8 +16,7 @@ import { ExpandCircleDown, ExpandMore } from '@mui/icons-material'; import { FormattedMessage } from 'react-intl'; export const styles = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - accordion: (theme: Theme) => ({ + accordion: () => ({ '&:before': { display: 'none', }, @@ -42,8 +41,7 @@ export const styles = { transform: 'rotate(0deg)', }, }), - // eslint-disable-next-line @typescript-eslint/no-unused-vars - accordionDetails: (theme: Theme) => ({ + accordionDetails: () => ({ padding: 0, // reset default left right space in details }), }; @@ -60,10 +58,8 @@ function ExpandableGroup({ renderHeader, children }: ExpandableGroupProps) { : } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onMouseEnter={(event) => setMouseHover(true)} - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onMouseLeave={(event) => setMouseHover(false)} + onMouseEnter={() => setMouseHover(true)} + onMouseLeave={() => setMouseHover(false)} > {typeof renderHeader === 'string' ? ( diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index e79380b5..82df44a1 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -75,7 +75,7 @@ function Login({ onLoginClick, disabled }: LoginProps) { > {'Copyright © '} {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} - + GridSuite {' '} {new Date().getFullYear()}. diff --git a/src/components/Login/Logout.tsx b/src/components/Login/Logout.tsx index 40e07427..2a7c095b 100644 --- a/src/components/Login/Logout.tsx +++ b/src/components/Login/Logout.tsx @@ -43,7 +43,8 @@ function Copyright() { return ( {'Copyright © '} - + {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + GridSuite {' '} {new Date().getFullYear()}. diff --git a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx index e37bade7..ff9cb5c0 100644 --- a/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx +++ b/src/components/MuiVirtualizedTable/KeyedColumnsRowIndexer.tsx @@ -4,8 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* eslint-disable no-param-reassign */ -/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unused-vars, no-param-reassign */ import { ReactElement } from 'react'; import { ColumnProps } from 'react-virtualized'; import equalsArray from '../../utils/algos'; diff --git a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx index 73ace525..ed434e3a 100644 --- a/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx +++ b/src/components/MuiVirtualizedTable/MuiVirtualizedTable.tsx @@ -4,32 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* eslint-disable react/jsx-curly-brace-presence */ -/* eslint-disable react/jsx-boolean-value */ -/* eslint-disable @typescript-eslint/no-shadow */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable no-restricted-globals */ -/* eslint-disable no-else-return */ -/* eslint-disable react/no-unstable-nested-components */ -/* eslint-disable react/function-component-definition */ -/* eslint-disable react/no-this-in-sfc */ -/* eslint-disable react/require-default-props */ -/* eslint-disable react/no-access-state-in-setstate */ -/* eslint-disable spaced-comment */ -/* eslint-disable object-shorthand */ -/* eslint-disable prefer-destructuring */ -/* eslint-disable prefer-template */ -/* eslint-disable no-return-assign */ -/* eslint-disable no-restricted-syntax */ -/* eslint-disable react/static-property-placement */ -/* eslint-disable @typescript-eslint/lines-between-class-members */ -/* eslint-disable react/default-props-match-prop-types */ -/* eslint-disable react/destructuring-assignment */ -/* eslint-disable no-underscore-dangle */ -/* eslint-disable class-methods-use-this */ -/* eslint-disable react/sort-comp */ -/* eslint-disable prefer-const */ -/* eslint-disable no-plusplus */ +/* eslint-disable react/jsx-curly-brace-presence, react/jsx-boolean-value, @typescript-eslint/no-shadow, @typescript-eslint/no-unused-vars, no-restricted-globals, no-else-return, react/no-unstable-nested-components, react/function-component-definition, react/no-this-in-sfc, react/require-default-props, react/no-access-state-in-setstate, spaced-comment, object-shorthand, prefer-destructuring, prefer-template, no-return-assign, no-restricted-syntax, react/static-property-placement, @typescript-eslint/lines-between-class-members, react/default-props-match-prop-types, react/destructuring-assignment, no-underscore-dangle, class-methods-use-this, react/sort-comp, prefer-const, no-plusplus */ /** * This class has been taken from 'Virtualized Table' example at https://material-ui.com/components/tables/ */ diff --git a/src/components/TopBar/AboutDialog.tsx b/src/components/TopBar/AboutDialog.tsx index 98f24fca..2d31da6c 100644 --- a/src/components/TopBar/AboutDialog.tsx +++ b/src/components/TopBar/AboutDialog.tsx @@ -139,6 +139,7 @@ type GridSuiteModule = { type: ModuleType; version?: string; gitTag?: string; + // license?: string; }; export interface AboutDialogProps { @@ -370,8 +371,7 @@ function AboutDialog({ ) .then( (values) => (Array.isArray(values) ? values : []), - // eslint-disable-next-line @typescript-eslint/no-unused-vars - (reason) => [] + () => [] ) .then((values) => { setModules([currentApp, ...values]); @@ -397,8 +397,7 @@ function AboutDialog({ aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" TransitionProps={{ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onExited: (node) => { + onExited: () => { setModules(null); setActualGlobalVersion(null); }, @@ -451,8 +450,7 @@ function AboutDialog({ in={loadingGlobalVersion} appear unmountOnExit - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onExited={(node) => setShowGlobalVersion(true)} + onExited={() => setShowGlobalVersion(true)} > @@ -544,6 +542,7 @@ function AboutDialog({ name={module.name} version={module.version} gitTag={module.gitTag} + // license={module.license} /> ))} diff --git a/src/components/TreeViewFinder/TreeViewFinder.tsx b/src/components/TreeViewFinder/TreeViewFinder.tsx index 8e350807..08a26057 100644 --- a/src/components/TreeViewFinder/TreeViewFinder.tsx +++ b/src/components/TreeViewFinder/TreeViewFinder.tsx @@ -253,7 +253,7 @@ function TreeViewFinder(props: TreeViewFinderProps) { } if (selectedProp.length > 0) { setSelected((oldSelectedNodes) => [ - ...(oldSelectedNodes || []), + ...(oldSelectedNodes ?? []), ...selectedProp, ]); } @@ -265,7 +265,7 @@ function TreeViewFinder(props: TreeViewFinderProps) { } if (expandedProp.length > 0) { setExpanded((oldExpandedNodes) => [ - ...(oldExpandedNodes || []), + ...(oldExpandedNodes ?? []), ...expandedProp, ]); } @@ -459,7 +459,7 @@ function TreeViewFinder(props: TreeViewFinderProps) { }} > - {title || + {title ?? intl.formatMessage( { id: 'treeview_finder/finderTitle' }, { multiSelect } @@ -467,7 +467,7 @@ function TreeViewFinder(props: TreeViewFinderProps) { - {contentText || + {contentText ?? intl.formatMessage( { id: 'treeview_finder/contentText' }, { multiSelect } diff --git a/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx b/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx index 23da98c5..27f74a55 100644 --- a/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx +++ b/src/components/inputs/react-hook-form/select-inputs/mui-select-input.tsx @@ -14,6 +14,7 @@ interface MuiSelectInputProps { options: { id: string; label: string }[]; } +// This input use Mui select instead of Autocomplete which can be needed some time (like in FormControl) function MuiSelectInput({ name, options, diff --git a/src/components/inputs/react-hook-form/slider-input.tsx b/src/components/inputs/react-hook-form/slider-input.tsx index 02e88b4d..a7af18f3 100644 --- a/src/components/inputs/react-hook-form/slider-input.tsx +++ b/src/components/inputs/react-hook-form/slider-input.tsx @@ -26,12 +26,7 @@ function SliderInput({ field: { onChange, value }, } = useController({ name }); - const handleValueChange = ( - event: Event, - newValue: number | number[], - // eslint-disable-next-line @typescript-eslint/no-unused-vars - activeThumb: number - ) => { + const handleValueChange = (event: Event, newValue: number | number[]) => { onValueChanged(newValue); onChange(newValue); }; diff --git a/src/components/inputs/react-hook-form/text-input.tsx b/src/components/inputs/react-hook-form/text-input.tsx index 408aa649..88d69348 100644 --- a/src/components/inputs/react-hook-form/text-input.tsx +++ b/src/components/inputs/react-hook-form/text-input.tsx @@ -101,10 +101,10 @@ function TextInput({ return ( JSON.stringify(user), - }; - userManager.storeUser(updatedUser).then(() => { + // TODO: Can we stop doing it in the hash for implicit flow ? To make it common for both flows + // Not allowed by TS because expires_in is supposed to be readonly + // @ts-ignore + // eslint-disable-next-line no-param-reassign + user.expires_in = expiresIn; + userManager.storeUser(user).then(() => { userManager.getUser(); }); } diff --git a/src/utils/Events.ts b/src/utils/Events.ts index fcc64b9c..64e6e5e9 100644 --- a/src/utils/Events.ts +++ b/src/utils/Events.ts @@ -12,7 +12,8 @@ export default class Events { this.userLoadedCallbacks.push(callback); } - static addSilentRenewError() { + // eslint-disable-next-line class-methods-use-this + addSilentRenewError() { // Nothing to do } } diff --git a/src/utils/UserManagerMock.ts b/src/utils/UserManagerMock.ts index 1abe35d5..186e2c74 100644 --- a/src/utils/UserManagerMock.ts +++ b/src/utils/UserManagerMock.ts @@ -45,7 +45,8 @@ export default class UserManagerMock { this.events = new Events(); } - static getUser() { + // eslint-disable-next-line class-methods-use-this + getUser() { return Promise.resolve( JSON.parse( sessionStorage.getItem('powsybl-gridsuite-mock-user') ?? 'null' @@ -71,7 +72,8 @@ export default class UserManagerMock { return Promise.resolve(localStorageUser); } - static signinSilentCallback() { + // eslint-disable-next-line class-methods-use-this + signinSilentCallback() { console.error( 'Unsupported, iframe signinSilentCallback in UserManagerMock (dev mode)' ); @@ -87,7 +89,8 @@ export default class UserManagerMock { return Promise.resolve(null); } - static signoutRedirect() { + // eslint-disable-next-line class-methods-use-this + signoutRedirect() { sessionStorage.removeItem('powsybl-gridsuite-mock-user'); localStorage.removeItem('powsybl-gridsuite-mock-user'); window.location.href = '.'; From dd1a15bd41efd804c03f8d90af642c40941abe6f Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Tue, 2 Jul 2024 16:32:28 +0200 Subject: [PATCH 14/16] Fix Signed-off-by: Ayoub LABIDI --- src/components/TopBar/AboutDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TopBar/AboutDialog.tsx b/src/components/TopBar/AboutDialog.tsx index 5e666dfb..6f555d4e 100644 --- a/src/components/TopBar/AboutDialog.tsx +++ b/src/components/TopBar/AboutDialog.tsx @@ -358,7 +358,7 @@ function AboutDialog({ type: 'app', version: appVersion, gitTag: appGitTag, - license: appLicense, + // license: appLicense, }; (additionalModulesPromise ? Promise.resolve(setLoadingAdditionalModules(true)).then(() => From 97c6f765c63575d0b0a91858c0cda1545be20147 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Mon, 8 Jul 2024 16:44:16 +0200 Subject: [PATCH 15/16] requested changes Signed-off-by: Ayoub LABIDI --- .../directory-item-selector.tsx | 30 +++++++++---------- .../ElementSearchDialog/tag-renderer.tsx | 2 +- src/components/TopBar/AboutDialog.tsx | 5 ++-- .../inputs/react-hook-form/slider-input.tsx | 7 ++++- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/components/DirectoryItemSelector/directory-item-selector.tsx b/src/components/DirectoryItemSelector/directory-item-selector.tsx index b2278e1d..7416858b 100644 --- a/src/components/DirectoryItemSelector/directory-item-selector.tsx +++ b/src/components/DirectoryItemSelector/directory-item-selector.tsx @@ -175,6 +175,20 @@ interface DirectoryItemSelectorProps extends TreeViewFinderProps { expanded?: UUID[]; } +function sortHandlingDirectories( + a: TreeViewFinderNodeProps, + b: TreeViewFinderNodeProps +): number { + // If children property is set it means it's a directory, they are handled differently in order to keep them at the top of the list + if (a.children && !b.children) { + return -1; + } + if (b.children && !a.children) { + return 1; + } + return a.name.localeCompare(b.name); +} + function DirectoryItemSelector({ open, types, @@ -332,24 +346,10 @@ function DirectoryItemSelector({ } }, [open, updateRootDirectories, expanded, fetchDirectory]); - function sortHandlingDirectories( - a: TreeViewFinderNodeProps, - b: TreeViewFinderNodeProps - ): number { - // If children property is set it means it's a directory, they are handled differently in order to keep them at the top of the list - if (a.children && !b.children) { - return -1; - } - if (b.children && !a.children) { - return 1; - } - return a.name.localeCompare(b.name); - } - return ( void} - sortMethod={(a, b) => sortHandlingDirectories(a, b)} + sortMethod={sortHandlingDirectories} multiSelect // defaulted to true open={open} expanded={expanded as string[]} diff --git a/src/components/ElementSearchDialog/tag-renderer.tsx b/src/components/ElementSearchDialog/tag-renderer.tsx index f0de03a2..419390e3 100644 --- a/src/components/ElementSearchDialog/tag-renderer.tsx +++ b/src/components/ElementSearchDialog/tag-renderer.tsx @@ -41,7 +41,7 @@ function TagRenderer({ element, ...props }: TagRendererProps) { /> ); } - return null; + return undefined; } export default TagRenderer; diff --git a/src/components/TopBar/AboutDialog.tsx b/src/components/TopBar/AboutDialog.tsx index 6f555d4e..066de1ab 100644 --- a/src/components/TopBar/AboutDialog.tsx +++ b/src/components/TopBar/AboutDialog.tsx @@ -539,9 +539,10 @@ function AboutDialog({ <> {[...modules] .sort(compareModules) - .map((module) => ( + .map((module, idx) => ( { + const handleValueChange = ( + event: Event, + newValue: number | number[], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + activeThumb: number + ) => { onValueChanged(newValue); onChange(newValue); }; From 5a0549257788f64f557a136188fa02ddbdead047 Mon Sep 17 00:00:00 2001 From: Ayoub LABIDI Date: Tue, 9 Jul 2024 09:38:29 +0200 Subject: [PATCH 16/16] Requested changes Signed-off-by: Ayoub LABIDI --- src/components/ElementSearchDialog/tag-renderer.tsx | 1 - src/components/TopBar/AboutDialog.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ElementSearchDialog/tag-renderer.tsx b/src/components/ElementSearchDialog/tag-renderer.tsx index 419390e3..06b98005 100644 --- a/src/components/ElementSearchDialog/tag-renderer.tsx +++ b/src/components/ElementSearchDialog/tag-renderer.tsx @@ -41,7 +41,6 @@ function TagRenderer({ element, ...props }: TagRendererProps) { /> ); } - return undefined; } export default TagRenderer; diff --git a/src/components/TopBar/AboutDialog.tsx b/src/components/TopBar/AboutDialog.tsx index 066de1ab..6cefbf05 100644 --- a/src/components/TopBar/AboutDialog.tsx +++ b/src/components/TopBar/AboutDialog.tsx @@ -541,7 +541,7 @@ function AboutDialog({ .sort(compareModules) .map((module, idx) => (