diff --git a/demo/src/TableTab.jsx b/demo/src/TableTab.jsx deleted file mode 100644 index eef8b206..00000000 --- a/demo/src/TableTab.jsx +++ /dev/null @@ -1,272 +0,0 @@ -/** - * 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, useMemo, useState } from 'react'; -import { styled } from '@mui/system'; -import { Box, Button, FormControlLabel, Stack, Switch, TextField } from '@mui/material'; -import { DEFAULT_CELL_PADDING } from '../../src'; -import { - ChangeWays, - generateMuiVirtualizedTableClass, - KeyedColumnsRowIndexer, - MuiVirtualizedTable, -} from '../../src/components/muiVirtualizedTable'; -import { toNestedGlobalSelectors } from '../../src/utils/styles'; - -// For demo and fun... all even numbers first, then all ascending odd numbers, only positive numbers... -const evenThenOddOrderingKey = (n) => { - const remainder = Math.abs(n % 2); - if (n <= 0 && remainder < 1) { - // first negative even and zero ]...-3,-2,-1] - return n / 2 - 1; - } - if (n > 0 && remainder < 1) { - // then positive even [-1/2, -1/3 ..., 0[ - return -1 / (n / 2 + 1); - } - if (n < 0 && remainder >= 1) { - // then negative odds ]0, 1/3, 1/2... - return -1 / ((n - 1) / 2 - 1); - } - // positive odd [1,2,3,4...[ - return (n + 1) / 2; -}; - -/** - * @param {import('@mui/material/styles').Theme} theme Theme from ThemeProvider - */ -const stylesVirtualizedTable = (theme) => ({ - '& .table': { - // temporary right-to-left patch, waiting for - // https://github.com/bvaughn/react-virtualized/issues/454 - '& .ReactVirtualized__Table__headerRow': { - flip: false, - paddingRight: theme.direction === 'rtl' ? '0 !important' : undefined, - }, - }, - '& .tableRow': { - cursor: 'pointer', - }, - '& .tableRowHover': { - '&:hover': { - backgroundColor: theme.palette.info.light, - }, - }, - '& .tableCell': { - flex: 1, - padding: `${DEFAULT_CELL_PADDING}px`, - }, - '& .noClick': { - cursor: 'initial', - }, - '& .tableCellColor': { - color: theme.palette.primary.contrastText, - }, - '& .header': { - backgroundColor: theme.palette.info.light, - color: theme.palette.primary.contrastText, - fontWeight: 'bold', - }, - '& .rowBackgroundDark': { - backgroundColor: theme.palette.info.dark, - }, - '& .rowBackgroundLight': { - backgroundColor: theme.palette.info.main, - }, -}); - -const stylesEmotion = ({ theme }) => - toNestedGlobalSelectors(stylesVirtualizedTable(theme), generateMuiVirtualizedTableClass); -const StyledVirtualizedTable = styled(MuiVirtualizedTable)(stylesEmotion); - -function TableTab() { - const [usesCustomStyles, setUsesCustomStyles] = useState(true); - - const VirtualizedTable = usesCustomStyles ? StyledVirtualizedTable : MuiVirtualizedTable; - - const columns = useMemo( - () => [ - { - label: 'header1', - dataKey: 'key1', - }, - { - label: 'header2', - dataKey: 'key2', - }, - { - label: 'header3 and some integers, and more, to be sure many lines are shown', - dataKey: 'key3', - numeric: true, - }, - { - label: 'floats', - dataKey: 'key4', - numeric: true, - fractionDigits: 1, - }, - ], - [] - ); - - const rows = useMemo( - () => [ - { key1: 'group2', key2: 'val2', key3: 1, key4: 2.35 }, - { key1: 'group1', key2: 'val1', key3: 2, key4: 5.32 }, - { key1: 'group2', key2: 'val4', key3: 3, key4: 23.5 }, - { key1: 'group1', key2: 'val3', key3: 4, key4: 52.3 }, - { key1: 'group3', key2: 'val3', key3: 5, key4: 2.53 }, - { key1: 'group3', key2: 'val2', key3: 6, key4: 25.3 }, - { key1: 'group4', key2: 'val3', key3: 5, key4: 53.2 }, - { key1: 'group4', key2: 'val4', key3: 2, key4: 3.25 }, - { key1: 'group4', key2: 'val4', key3: 1, key4: 3.52 }, - ], - [] - ); - - function makeIndexer(prevIndexer) { - const prevCol = prevIndexer?.highestCodedColumn(columns); - const colKey = !prevCol ? 'key2' : `key${(Math.abs(prevCol) % 4) + 1}`; - const ret = new KeyedColumnsRowIndexer(true, false); - ret.setColFilterOuterParams(colKey, ['val9']); - - const changeWay = ChangeWays.SIMPLE; - // fake user click twice, to set descending order - ret.updateSortingFromUser(colKey, changeWay); - ret.updateSortingFromUser(colKey, changeWay); - return ret; - } - - const [indexer, setIndexer] = useState(() => makeIndexer()); - - const [isIndexerExternal, setIndexerIsExternal] = useState(true); - const [sortable, setSortable] = useState(true); - const [recreates, setRecreates] = useState(false); - - const [defersFilterChanges, setDefersFilterChanges] = useState(false); - - const [headerHeight, setHeaderHeight] = useState(''); - - const [filterValue, setFilterValue] = useState(''); - const [doesSort, setDoesSort] = useState(false); - const filter = useCallback( - (row) => { - return row.key2 && row.key2.includes(filterValue); - }, - [filterValue] - ); - const sort = useCallback( - (dataKey, reverse, isNumeric) => { - let filtered = rows.map((r, i) => [r, i]).filter(([r]) => !filter || filter(r)); - if (dataKey) { - filtered = filtered - .map(([r, j]) => [r[dataKey], j]) - .map(([r, j]) => [isNumeric ? r : Number(r.replace(/[^0-9.]/g, '')), j]); // for demo, extract any number from a string.. - filtered.sort(([a], [b]) => evenThenOddOrderingKey(b) - evenThenOddOrderingKey(a)); - if (reverse) { - filtered = filtered.reverse(); - } - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - return filtered.map(([_, j]) => j); - }, - [rows, filter] - ); - - const [key, setKey] = useState(); - - function updateKeyIfNeeded() { - if (recreates) { - setKey(crypto.randomUUID()); - } - } - - function mkSwitch(label, value, setter) { - return ( - { - updateKeyIfNeeded(); - setter((was) => !was); - }} - /> - } - label={label} - /> - ); - } - - function renderParams() { - return ( - - {mkSwitch('Custom theme (emotion)', usesCustomStyles, setUsesCustomStyles)} - {mkSwitch('Sortable', sortable, setSortable)} - {mkSwitch('Instance renewal', recreates, setRecreates)} - {mkSwitch('Uses external indexer', isIndexerExternal, setIndexerIsExternal)} - - {mkSwitch('External sort (even then odds)', doesSort, setDoesSort)} - { - updateKeyIfNeeded(); - setFilterValue(event.target.value); - }} - /> - {mkSwitch('Defer filter changes', defersFilterChanges, setDefersFilterChanges)} - { - // 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 (!Number.isNaN(newHeaderHeight)) { - setHeaderHeight(event.target.value); - } - }} - /> - - ); - } - - return ( - - {renderParams()} - - console.log('onRowClick', args)} - onClick={(...args) => console.log('onClick', args)} - onCellClick={(...args) => console.log('onCellClick', args)} - indexer={isIndexerExternal ? indexer : null} - {...(filterValue && { filter })} - {...(doesSort && { sort })} - /> - - - ); -} - -export default TableTab; diff --git a/demo/src/app.jsx b/demo/src/app.jsx index 3579c85b..55bc0fb2 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -44,11 +44,8 @@ import { testDataTree, } from '../data/TreeViewFinder'; -import LOGS_JSON from '../data/ReportViewer'; - import searchEquipments from '../data/EquipmentSearchBar'; -import TableTab from './TableTab'; import FlatParametersTab from './FlatParametersTab'; import InputsTab from './InputsTab'; @@ -876,12 +873,10 @@ function AppContent({ language, onLanguageClick }) {
setTabIndex(newTabIndex)}> - {tabIndex === 0 && defaultTab} - {tabIndex === 1 && } {tabIndex === 2 && } {tabIndex === 3 && }
diff --git a/package-lock.json b/package-lock.json index 5be76415..d298bd46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gridsuite/commons-ui", - "version": "0.77.2", + "version": "0.79.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gridsuite/commons-ui", - "version": "0.77.2", + "version": "0.79.0", "license": "MPL-2.0", "dependencies": { "@react-querybuilder/dnd": "^7.2.0", @@ -23,7 +23,6 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-querybuilder": "^7.2.0", - "react-virtualized": "^9.22.5", "reconnecting-websocket": "^4.4.0", "uuid": "^9.0.1" }, @@ -57,14 +56,13 @@ "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^18.2.24", "@types/react-resizable": "^3.0.7", - "@types/react-virtualized": "^9.21.29", "@types/utf-8-validate": "^5.0.2", "@types/uuid": "^9.0.8", "@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", + "ag-grid-community": "^33.0.3", + "ag-grid-react": "^33.0.3", "babel-eslint": "^10.1.0", "babel-preset-airbnb": "^5.0.0", "babel-preset-vite": "^1.1.3", @@ -119,8 +117,8 @@ "@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", + "ag-grid-community": "^33.0.3", + "ag-grid-react": "^33.0.3", "notistack": "^3.0.1", "papaparse": "^5.4.1", "react": "^18.2.0", @@ -5828,16 +5826,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-virtualized": { - "version": "9.21.29", - "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.29.tgz", - "integrity": "sha512-+ODVQ+AyKngenj4OPpg43Hz4B9Rdjuz1Naxu9ypNc3Cjo0WVZTYhqXfF/Nm38i8PV/YXECRIl4mTAZK5hq2B+g==", - "dev": true, - "dependencies": { - "@types/prop-types": "*", - "@types/react": "*" - } - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -6422,24 +6410,33 @@ "node": ">=0.4.0" } }, - "node_modules/ag-grid-community": { - "version": "31.2.0", - "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-31.2.0.tgz", - "integrity": "sha512-Ija6X171Iq3mFZASZlriQIIdEFqA71rZIsjQD6KHy5lMmxnoseZTX2neThBav1gvr6SA6n5B2PD6eUHdZnrUfw==", + "node_modules/ag-charts-types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-11.0.3.tgz", + "integrity": "sha512-q7O2viQXPyO014QVH7KjFCUmQ/NvMBx9ReZtramQ44u+/aAa0ttLi/coK+V0Hse4/MG1eB/2XhgymdooQ0Kalg==", "dev": true }, + "node_modules/ag-grid-community": { + "version": "33.0.3", + "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-33.0.3.tgz", + "integrity": "sha512-HZeVmVieZ5Gm9j09Itecqm+OIX8X6cU4TJToUbsXv3DxdO9SK5/s8aAJAwBHtNRXOhHrhqQYwrY2yc3OtzWwcQ==", + "dev": true, + "dependencies": { + "ag-charts-types": "11.0.3" + } + }, "node_modules/ag-grid-react": { - "version": "31.2.0", - "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-31.2.0.tgz", - "integrity": "sha512-ObFdPmF3EC7/xWZX8NjrZjURePyFa72MWjb1ZgUqDP7Wq09OSXXyKBN1qXmfUIT3h4o5+os6tCQEqoo7Op+3ZA==", + "version": "33.0.3", + "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-33.0.3.tgz", + "integrity": "sha512-6IcraSVqsUG/hzTeZ0D0dtddAcZKcWdN75ek/O+lCA6r22abJPe33nHBYVezkTV8k6D3JhA24mlTwduzn0GZLQ==", "dev": true, "dependencies": { - "ag-grid-community": "31.2.0", + "ag-grid-community": "33.0.3", "prop-types": "^15.8.1" }, "peerDependencies": { - "react": "^16.3.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/agent-base": { @@ -14052,11 +14049,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, "node_modules/react-papaparse": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/react-papaparse/-/react-papaparse-4.4.0.tgz", @@ -14246,31 +14238,6 @@ "react-dom": ">=16.6.0" } }, - "node_modules/react-virtualized": { - "version": "9.22.5", - "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.5.tgz", - "integrity": "sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "clsx": "^1.0.4", - "dom-helpers": "^5.1.3", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-lifecycles-compat": "^3.0.4" - }, - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", - "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-virtualized/node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "engines": { - "node": ">=6" - } - }, "node_modules/read-installed": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", diff --git a/package.json b/package.json index bafd18d0..cde79e67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gridsuite/commons-ui", - "version": "0.77.2", + "version": "0.79.0", "description": "common react components for gridsuite applications", "engines": { "npm": ">=9", @@ -42,7 +42,6 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-querybuilder": "^7.2.0", - "react-virtualized": "^9.22.5", "reconnecting-websocket": "^4.4.0", "uuid": "^9.0.1" }, @@ -55,8 +54,8 @@ "@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", + "ag-grid-community": "^33.0.3", + "ag-grid-react": "^33.0.3", "notistack": "^3.0.1", "papaparse": "^5.4.1", "react": "^18.2.0", @@ -97,14 +96,13 @@ "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "^18.2.24", "@types/react-resizable": "^3.0.7", - "@types/react-virtualized": "^9.21.29", "@types/utf-8-validate": "^5.0.2", "@types/uuid": "^9.0.8", "@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", + "ag-grid-community": "^33.0.3", + "ag-grid-react": "^33.0.3", "babel-eslint": "^10.1.0", "babel-preset-airbnb": "^5.0.0", "babel-preset-vite": "^1.1.3", diff --git a/src/components/authentication/utils/userManagerMock.ts b/src/components/authentication/utils/userManagerMock.ts index 3ff604af..47531a41 100644 --- a/src/components/authentication/utils/userManagerMock.ts +++ b/src/components/authentication/utils/userManagerMock.ts @@ -20,14 +20,13 @@ import { } from 'oidc-client'; class Events implements UserManagerEvents { - userLoadedCallbacks: ((data: any) => void)[] = []; + userLoadedCallbacks: ((data: User) => void)[] = []; - addUserLoaded(callback: (data: any) => void) { + addUserLoaded(callback: (data: User) => void) { this.userLoadedCallbacks.push(callback); } - // eslint-disable-next-line - addSilentRenewError(callback: (data: any) => void) { + addSilentRenewError() { // Nothing to do } diff --git a/src/components/customAGGrid/customAggrid.tsx b/src/components/customAGGrid/customAggrid.tsx index 7e2708f6..28f138f6 100644 --- a/src/components/customAGGrid/customAggrid.tsx +++ b/src/components/customAGGrid/customAggrid.tsx @@ -31,7 +31,7 @@ const onColumnResized = (params: ColumnResizedEvent) => { if (column && colDefinedMinWidth && finished) { const newWidth = column?.getActualWidth(); if (newWidth < colDefinedMinWidth) { - column?.setActualWidth(colDefinedMinWidth, params.source); + params.api.setColumnWidths([{ key: column, newWidth: colDefinedMinWidth }], finished, params.source); } } }; @@ -66,9 +66,9 @@ export const CustomAGGrid = React.forwardRef((pr diff --git a/src/components/directoryItemSelector/DirectoryItemSelector.tsx b/src/components/directoryItemSelector/DirectoryItemSelector.tsx index 0b55d74f..a9f50045 100644 --- a/src/components/directoryItemSelector/DirectoryItemSelector.tsx +++ b/src/components/directoryItemSelector/DirectoryItemSelector.tsx @@ -65,7 +65,7 @@ function refreshedUpNodes( const parent = nodeMap[newElement.parentUuid]; const nextChildren = parent.children.map((c) => (c.elementUuid === newElement.elementUuid ? newElement : c)); const nextParent = { ...parent, children: nextChildren }; - return [newElement, ...refreshedUpNodes(nodeMap, nextParent)]; + return [newElement, ...refreshedUpNodes(nodeMap, nextParent as ElementAttributesBase)]; } /** @@ -148,19 +148,9 @@ function updatedTree( } export interface DirectoryItemSelectorProps extends TreeViewFinderProps { - open: boolean; types: string[]; equipmentTypes?: string[]; - itemFilter?: any; - classes?: any; - contentText?: string; - defaultExpanded?: string[]; - defaultSelected?: string[]; - validationButtonText?: string; - className?: string; - cancelButtonProps?: any; - onlyLeaves?: boolean; - multiselect?: boolean; + itemFilter?: (val: ElementAttributes) => boolean; expanded?: UUID[]; selected?: UUID[]; } @@ -196,7 +186,7 @@ export function DirectoryItemSelector({ const { snackError } = useSnackMessage(); const contentFilter = useCallback(() => new Set([ElementType.DIRECTORY, ...types]), [types]); - const convertChildren = useCallback((children: any[]): any[] => { + const convertChildren = useCallback((children: ElementAttributes[]): TreeViewFinderNodeProps[] => { return children.map((e) => { return { id: e.elementUuid, @@ -210,7 +200,7 @@ export function DirectoryItemSelector({ }, []); const convertRoots = useCallback( - (newRoots: ElementAttributes[]) => { + (newRoots: ElementAttributes[]): TreeViewFinderNodeProps[] => { return newRoots.map((e) => { return { id: e.elementUuid, @@ -258,16 +248,18 @@ export function DirectoryItemSelector({ const typeList = types.includes(ElementType.DIRECTORY) ? [] : types; fetchDirectoryContent(nodeId, typeList) .then((children) => { - const childrenMatchedTypes = children.filter((item: any) => contentFilter().has(item.type)); + const childrenMatchedTypes = children.filter((item: ElementAttributes) => + contentFilter().has(item.type) + ); if (childrenMatchedTypes.length > 0 && equipmentTypes && equipmentTypes.length > 0) { fetchElementsInfos( - childrenMatchedTypes.map((e: any) => e.elementUuid), + childrenMatchedTypes.map((e: ElementAttributes) => e.elementUuid), types, equipmentTypes - ).then((childrenWithMetadata) => { + ).then((childrenWithMetadata: ElementAttributes[]) => { const filtredChildren = itemFilter - ? childrenWithMetadata.filter((val: any) => { + ? childrenWithMetadata.filter((val: ElementAttributes) => { // Accept every directory if (val.type === ElementType.DIRECTORY) { return true; diff --git a/src/components/filter/FilterCreationDialog.tsx b/src/components/filter/FilterCreationDialog.tsx index 8207860b..ba417dd5 100644 --- a/src/components/filter/FilterCreationDialog.tsx +++ b/src/components/filter/FilterCreationDialog.tsx @@ -14,16 +14,17 @@ import { useSnackMessage } from '../../hooks/useSnackMessage'; import { CustomMuiDialog } from '../dialogs/customMuiDialog/CustomMuiDialog'; import { explicitNamingFilterSchema, - FILTER_EQUIPMENTS_ATTRIBUTES, getExplicitNamingFilterEmptyFormData, } from './explicitNaming/ExplicitNamingFilterForm'; import { FieldConstants } from '../../utils/constants/fieldConstants'; import yup from '../../utils/yupConfig'; import { FilterForm } from './FilterForm'; -import { EXPERT_FILTER_QUERY, expertFilterSchema, getExpertFilterEmptyFormData } from './expert/ExpertFilterForm'; +import { expertFilterSchema, getExpertFilterEmptyFormData } from './expert/ExpertFilterForm'; import { FilterType } from './constants/FilterConstants'; import { ElementExistsType } from '../../utils/types/elementType'; import { MAX_CHAR_DESCRIPTION } from '../../utils/constants/uiConstants'; +import { EXPERT_FILTER_QUERY } from './expert/expertFilterConstants'; +import { FILTER_EQUIPMENTS_ATTRIBUTES } from './explicitNaming/ExplicitNamingFilterConstants'; const emptyFormData = { [FieldConstants.NAME]: '', diff --git a/src/components/filter/expert/ExpertFilterEditionDialog.tsx b/src/components/filter/expert/ExpertFilterEditionDialog.tsx index 42ec88a5..6cec8456 100644 --- a/src/components/filter/expert/ExpertFilterEditionDialog.tsx +++ b/src/components/filter/expert/ExpertFilterEditionDialog.tsx @@ -17,9 +17,11 @@ import { FilterType, NO_ITEM_SELECTION_FOR_COPY } from '../constants/FilterConst import { FilterEditionProps } from '../filter.type'; import { FilterForm } from '../FilterForm'; import { saveExpertFilter } from '../utils/filterApi'; -import { EXPERT_FILTER_QUERY, expertFilterSchema } from './ExpertFilterForm'; +import { expertFilterSchema } from './ExpertFilterForm'; import { importExpertRules } from './expertFilterUtils'; import { HeaderFilterSchema } from '../HeaderFilterForm'; +import { catchErrorHandler } from '../../../services'; +import { EXPERT_FILTER_QUERY } from './expertFilterConstants'; const formSchema = yup .object() @@ -65,21 +67,23 @@ export function ExpertFilterEditionDialog({ if (id && open) { setDataFetchStatus(FetchStatus.FETCHING); getFilterById(id) - .then((response: { [prop: string]: any }) => { + .then((response) => { setDataFetchStatus(FetchStatus.FETCH_SUCCESS); reset({ [FieldConstants.NAME]: name, [FieldConstants.DESCRIPTION]: description, [FieldConstants.FILTER_TYPE]: FilterType.EXPERT.id, [FieldConstants.EQUIPMENT_TYPE]: response[FieldConstants.EQUIPMENT_TYPE], - [EXPERT_FILTER_QUERY]: importExpertRules(response[EXPERT_FILTER_QUERY]), + [EXPERT_FILTER_QUERY]: importExpertRules(response[EXPERT_FILTER_QUERY]!), }); }) - .catch((error: { message: any }) => { - setDataFetchStatus(FetchStatus.FETCH_ERROR); - snackError({ - messageTxt: error.message, - headerId: 'cannotRetrieveFilter', + .catch((error: unknown) => { + catchErrorHandler(error, (message: string) => { + setDataFetchStatus(FetchStatus.FETCH_ERROR); + snackError({ + messageTxt: message, + headerId: 'cannotRetrieveFilter', + }); }); }); } diff --git a/src/components/filter/expert/ExpertFilterForm.tsx b/src/components/filter/expert/ExpertFilterForm.tsx index 325354a7..9d6dcc08 100644 --- a/src/components/filter/expert/ExpertFilterForm.tsx +++ b/src/components/filter/expert/ExpertFilterForm.tsx @@ -19,6 +19,7 @@ import { COMBINATOR_OPTIONS, EXPERT_FILTER_EQUIPMENTS, EXPERT_FILTER_FIELDS, + EXPERT_FILTER_QUERY, OPERATOR_OPTIONS, RULES, } from './expertFilterConstants'; @@ -44,8 +45,6 @@ yup.setLocale({ }, }); -export const EXPERT_FILTER_QUERY = 'rules'; - function isSupportedEquipmentType(equipmentType: string): boolean { return Object.values(EXPERT_FILTER_EQUIPMENTS) .map((equipments) => equipments.id) diff --git a/src/components/filter/expert/expertFilterConstants.ts b/src/components/filter/expert/expertFilterConstants.ts index 8452eeaa..22f449e0 100644 --- a/src/components/filter/expert/expertFilterConstants.ts +++ b/src/components/filter/expert/expertFilterConstants.ts @@ -26,6 +26,8 @@ export enum RULES { BETWEEN_RULE = 'betweenRule', } +export const EXPERT_FILTER_QUERY = 'rules'; + export const EXPERT_FILTER_EQUIPMENTS = { SUBSTATION: { id: 'SUBSTATION', diff --git a/src/components/muiVirtualizedTable/index.ts b/src/components/filter/explicitNaming/ExplicitNamingFilterConstants.ts similarity index 60% rename from src/components/muiVirtualizedTable/index.ts rename to src/components/filter/explicitNaming/ExplicitNamingFilterConstants.ts index 4192ef9a..ea9ca22c 100644 --- a/src/components/muiVirtualizedTable/index.ts +++ b/src/components/filter/explicitNaming/ExplicitNamingFilterConstants.ts @@ -1,8 +1,8 @@ /** - * Copyright (c) 2021, RTE (http://www.rte-france.com) + * 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 * from './KeyedColumnsRowIndexer'; -export * from './MuiVirtualizedTable'; + +export const FILTER_EQUIPMENTS_ATTRIBUTES = 'filterEquipmentsAttributes'; diff --git a/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx b/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx index 5c0399be..e536fc8f 100644 --- a/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx +++ b/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx @@ -15,13 +15,14 @@ import { FieldConstants } from '../../../utils/constants/fieldConstants'; import yup from '../../../utils/yupConfig'; import { CustomMuiDialog } from '../../dialogs/customMuiDialog/CustomMuiDialog'; import { saveExplicitNamingFilter } from '../utils/filterApi'; -import { explicitNamingFilterSchema, FILTER_EQUIPMENTS_ATTRIBUTES } from './ExplicitNamingFilterForm'; +import { explicitNamingFilterSchema } from './ExplicitNamingFilterForm'; import { FetchStatus } from '../../../utils/constants/fetchStatus'; import { FilterForm } from '../FilterForm'; import { FilterType, NO_ITEM_SELECTION_FOR_COPY } from '../constants/FilterConstants'; import { FilterEditionProps } from '../filter.type'; import { HeaderFilterSchema } from '../HeaderFilterForm'; +import { FILTER_EQUIPMENTS_ATTRIBUTES } from './ExplicitNamingFilterConstants'; const formSchema = yup .object() @@ -75,7 +76,7 @@ export function ExplicitNamingFilterEditionDialog({ [FieldConstants.DESCRIPTION]: description, [FieldConstants.FILTER_TYPE]: FilterType.EXPLICIT_NAMING.id, [FieldConstants.EQUIPMENT_TYPE]: response[FieldConstants.EQUIPMENT_TYPE], - [FILTER_EQUIPMENTS_ATTRIBUTES]: response[FILTER_EQUIPMENTS_ATTRIBUTES].map((row: any) => ({ + [FILTER_EQUIPMENTS_ATTRIBUTES]: response[FILTER_EQUIPMENTS_ATTRIBUTES]?.map((row: any) => ({ [FieldConstants.AG_GRID_ROW_UUID]: uuid4(), ...row, })), diff --git a/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx b/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx index 6e71e737..47b65175 100644 --- a/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx +++ b/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx @@ -13,10 +13,7 @@ import { v4 as uuid4 } from 'uuid'; import { UUID } from 'crypto'; import { FieldConstants } from '../../../utils/constants/fieldConstants'; import yup from '../../../utils/yupConfig'; -import { - CustomAgGridTable, - ROW_DRAGGING_SELECTION_COLUMN_DEF, -} from '../../inputs/reactHookForm/agGridTable/CustomAgGridTable'; +import { CustomAgGridTable } from '../../inputs/reactHookForm/agGridTable/CustomAgGridTable'; import { SelectInput } from '../../inputs/reactHookForm/selectInputs/SelectInput'; import { Generator, Load } from '../../../utils/types/equipmentTypes'; import { NumericEditor } from '../../inputs/reactHookForm/agGridTable/cellEditors/numericEditor'; @@ -30,8 +27,7 @@ import { ModifyElementSelection } from '../../dialogs/modifyElementSelection/Mod import { exportFilter } from '../../../services/study'; import { EquipmentType } from '../../../utils/types/equipmentType'; import { unscrollableDialogStyles } from '../../dialogs'; - -export const FILTER_EQUIPMENTS_ATTRIBUTES = 'filterEquipmentsAttributes'; +import { FILTER_EQUIPMENTS_ATTRIBUTES } from './ExplicitNamingFilterConstants'; function isGeneratorOrLoad(equipmentType: string): boolean { return equipmentType === Generator.type || equipmentType === Load.type; @@ -129,11 +125,11 @@ export function ExplicitNamingFilterForm({ sourceFilterForExplicitNamingConversi const columnDefs = useMemo(() => { const newColumnDefs: any[] = [ - ...ROW_DRAGGING_SELECTION_COLUMN_DEF, { headerName: intl.formatMessage({ id: FieldConstants.EQUIPMENT_ID, }), + rowDrag: true, field: FieldConstants.EQUIPMENT_ID, editable: true, singleClickEdit: true, @@ -246,7 +242,12 @@ export function ExplicitNamingFilterForm({ sourceFilterForExplicitNamingConversi makeDefaultRowData={makeDefaultRowData} pagination paginationPageSize={100} - suppressRowClickSelection + rowSelection={{ + mode: 'multiRow', + enableClickSelection: false, + checkboxes: true, + headerCheckbox: true, + }} alwaysShowVerticalScroll stopEditingWhenCellsLoseFocus csvProps={{ diff --git a/src/components/filter/explicitNaming/index.ts b/src/components/filter/explicitNaming/index.ts index 75eedca3..c7843b8b 100644 --- a/src/components/filter/explicitNaming/index.ts +++ b/src/components/filter/explicitNaming/index.ts @@ -6,3 +6,4 @@ */ export * from './ExplicitNamingFilterEditionDialog'; export * from './ExplicitNamingFilterForm'; +export * from './ExplicitNamingFilterConstants'; diff --git a/src/components/filter/filter.type.ts b/src/components/filter/filter.type.ts index 3ba8e434..f8ce5905 100644 --- a/src/components/filter/filter.type.ts +++ b/src/components/filter/filter.type.ts @@ -6,7 +6,10 @@ */ import { UUID } from 'crypto'; -import { ElementExistsType } from '../../utils'; +import { ElementExistsType, FieldConstants } from '../../utils'; +import { RuleGroupTypeExport } from './expert/expertFilter.type'; +import { EXPERT_FILTER_QUERY } from './expert/expertFilterConstants'; +import { FILTER_EQUIPMENTS_ATTRIBUTES } from './explicitNaming/ExplicitNamingFilterConstants'; /** * Represent an item/object in directories. @@ -20,6 +23,11 @@ export type ItemSelectionForCopy = { specificTypeItem: string | null; }; +type EquipmentsFilter = { + equipmentID: string; + distributionKey?: number; +}; + export interface FilterEditionProps { id: string; name: string; @@ -29,9 +37,21 @@ export interface FilterEditionProps { broadcastChannel: BroadcastChannel; itemSelectionForCopy: ItemSelectionForCopy; setItemSelectionForCopy: (selection: ItemSelectionForCopy) => void; - getFilterById: (id: string) => Promise<{ [prop: string]: any }>; + getFilterById: (id: string) => Promise<{ + [FieldConstants.EQUIPMENT_TYPE]: string; + [EXPERT_FILTER_QUERY]?: RuleGroupTypeExport; + [FILTER_EQUIPMENTS_ATTRIBUTES]?: EquipmentsFilter[]; + }>; activeDirectory?: UUID; elementExists?: ElementExistsType; language?: string; description?: string; } + +export interface NewFilterType { + id: string | null; + type: string; + equipmentType: string; + rules?: RuleGroupTypeExport; + filterEquipmentsAttributes?: EquipmentsFilter[]; +} diff --git a/src/components/filter/utils/filterApi.ts b/src/components/filter/utils/filterApi.ts index 960fb208..9ba2e9e8 100644 --- a/src/components/filter/utils/filterApi.ts +++ b/src/components/filter/utils/filterApi.ts @@ -11,6 +11,7 @@ import { Generator, Load } from '../../../utils/types/equipmentTypes'; import { exportExpertRules } from '../expert/expertFilterUtils'; import { DISTRIBUTION_KEY, FilterType } from '../constants/FilterConstants'; import { createFilter, saveFilter } from '../../../services/explore'; +import { catchErrorHandler } from '../../../services'; export const saveExplicitNamingFilter = ( tableValues: any[], @@ -40,6 +41,7 @@ export const saveExplicitNamingFilter = ( if (isFilterCreation) { createFilter( { + id: null, type: FilterType.EXPLICIT_NAMING.id, equipmentType, filterEquipmentsAttributes: cleanedTableValues, @@ -82,7 +84,7 @@ export const saveExpertFilter = ( name: string, description: string, isFilterCreation: boolean, - activeDirectory: any, + activeDirectory: UUID | undefined | null, onClose: () => void, onError: (message: string) => void, token?: string @@ -90,6 +92,7 @@ export const saveExpertFilter = ( if (isFilterCreation) { createFilter( { + id: null, type: FilterType.EXPERT.id, equipmentType, rules: exportExpertRules(query), @@ -102,8 +105,10 @@ export const saveExpertFilter = ( .then(() => { onClose(); }) - .catch((error: any) => { - onError(error.message); + .catch((error: unknown) => { + catchErrorHandler(error, (message: string) => { + onError(message); + }); }); } else { saveFilter( @@ -120,8 +125,10 @@ export const saveExpertFilter = ( .then(() => { onClose(); }) - .catch((error: any) => { - onError(error.message); + .catch((error: unknown) => { + catchErrorHandler(error, (message: string) => { + onError(message); + }); }); } }; diff --git a/src/components/flatParameters/FlatParameters.tsx b/src/components/flatParameters/FlatParameters.tsx index d7762760..9a3b84af 100644 --- a/src/components/flatParameters/FlatParameters.tsx +++ b/src/components/flatParameters/FlatParameters.tsx @@ -50,7 +50,7 @@ const IntegerRE = /^-?\d*$/; const ListRE = /^\[(.*)]$/; const sepRE = /[, ]/; -export function extractDefault(paramDescription: any) { +export function extractDefault(paramDescription: Parameter) { const d = paramDescription.defaultValue; if (paramDescription.type === 'BOOLEAN') { return !!d; @@ -95,7 +95,7 @@ export type Parameter = { type: 'BOOLEAN' | 'DOUBLE' | 'INTEGER' | 'STRING_LIST' | 'STRING'; description?: string; name: string; - possibleValues: any; + possibleValues: string[]; defaultValue: any; }; diff --git a/src/components/index.ts b/src/components/index.ts index ab247581..5a43a3e6 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -14,7 +14,6 @@ export * from './elementSearch'; export * from './filter'; export * from './flatParameters'; export * from './inputs'; -export * from './muiVirtualizedTable'; export * from './multipleSelectionDialog'; export * from './overflowableText'; export * from './snackbarProvider'; diff --git a/src/components/inputs/reactHookForm/DirectoryItemsInput.tsx b/src/components/inputs/reactHookForm/DirectoryItemsInput.tsx index 129c6212..8054678f 100644 --- a/src/components/inputs/reactHookForm/DirectoryItemsInput.tsx +++ b/src/components/inputs/reactHookForm/DirectoryItemsInput.tsx @@ -8,7 +8,7 @@ import { Chip, FormControl, Grid, IconButton, Theme, Tooltip } from '@mui/material'; import FolderIcon from '@mui/icons-material/Folder'; import { useCallback, useMemo, useState } from 'react'; -import { useController, useFieldArray } from 'react-hook-form'; +import { FieldValues, useController, useFieldArray } from 'react-hook-form'; import { useIntl } from 'react-intl'; import { UUID } from 'crypto'; import { RawReadOnlyInput } from './RawReadOnlyInput'; @@ -23,6 +23,7 @@ import { OverflowableText } from '../../overflowableText'; import { MidFormError } from './errorManagement/MidFormError'; import { DirectoryItemSelector } from '../../directoryItemSelector/DirectoryItemSelector'; import { fetchDirectoryElementPath } from '../../../services'; +import { ElementAttributes } from '../../../utils'; export const NAME = 'name'; @@ -59,7 +60,7 @@ export interface DirectoryItemsInputProps { name: string; elementType: string; equipmentTypes?: string[]; - itemFilter?: any; + itemFilter?: (val: ElementAttributes) => boolean; titleId?: string; hideErrorMessage?: boolean; onRowChanged?: (a: boolean) => void; @@ -113,14 +114,14 @@ export function DirectoryItemsInput({ // if we select a chip and return a new values, we remove it to be replaced if (selected?.length > 0 && values?.length > 0) { selected.forEach((chip) => { - remove(getValues(name).findIndex((item: any) => item.id === chip)); + remove(getValues(name).findIndex((item: FieldValues) => item.id === chip)); }); } values.forEach((value) => { const { icon, children, ...otherElementAttributes } = value; // Check if the element is already present - if (getValues(name).find((v: any) => v?.id === otherElementAttributes.id) !== undefined) { + if (getValues(name).find((v: FieldValues) => v?.id === otherElementAttributes.id) !== undefined) { snackError({ messageTxt: '', headerId: 'directory_items_input/ElementAlreadyUsed', @@ -148,10 +149,10 @@ export function DirectoryItemsInput({ const handleChipClick = useCallback( (index: number) => { - const chips = getValues(name) as any[]; + const chips = getValues(name); const chip = chips.at(index)?.id; if (chip) { - fetchDirectoryElementPath(chip).then((response: any[]) => { + fetchDirectoryElementPath(chip).then((response: ElementAttributes[]) => { const path = response.filter((e) => e.elementUuid !== chip).map((e) => e.elementUuid); setExpanded(path); diff --git a/src/components/inputs/reactHookForm/agGridTable/CustomAgGridTable.tsx b/src/components/inputs/reactHookForm/agGridTable/CustomAgGridTable.tsx index 094fcd30..306c7c23 100644 --- a/src/components/inputs/reactHookForm/agGridTable/CustomAgGridTable.tsx +++ b/src/components/inputs/reactHookForm/agGridTable/CustomAgGridTable.tsx @@ -7,7 +7,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; -import { AgGridReact } from 'ag-grid-react'; +import { AgGridReact, AgGridReactProps } from 'ag-grid-react'; import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-theme-alpine.css'; import { Box, useTheme } from '@mui/material'; @@ -16,15 +16,6 @@ import { CellEditingStoppedEvent, ColumnState, SortChangedEvent } from 'ag-grid- import { BottomRightButtons } from './BottomRightButtons'; import { FieldConstants } from '../../../../utils/constants/fieldConstants'; -export const ROW_DRAGGING_SELECTION_COLUMN_DEF = [ - { - rowDrag: true, - headerCheckboxSelection: true, - checkboxSelection: true, - maxWidth: 50, - }, -]; - const style = (customProps: any) => ({ grid: (theme: any) => ({ width: 'auto', @@ -92,7 +83,7 @@ export interface CustomAgGridTableProps { defaultColDef: unknown; pagination: boolean; paginationPageSize: number; - suppressRowClickSelection: boolean; + rowSelection?: AgGridReactProps['rowSelection']; alwaysShowVerticalScroll: boolean; stopEditingWhenCellsLoseFocus: boolean; } @@ -106,11 +97,11 @@ export function CustomAgGridTable({ defaultColDef, pagination, paginationPageSize, - suppressRowClickSelection, + rowSelection, alwaysShowVerticalScroll, stopEditingWhenCellsLoseFocus, ...props -}: CustomAgGridTableProps) { +}: Readonly) { // FIXME: right type => Theme --> not defined there ( gridStudy and gridExplore definition not the same ) const theme: any = useTheme(); const [gridApi, setGridApi] = useState(null); @@ -243,11 +234,10 @@ export function CustomAgGridTable({ onGridReady={onGridReady} getLocaleText={getLocaleText} cacheOverflowSize={10} - rowSelection="multiple" + rowSelection={rowSelection ?? 'multiple'} rowDragEntireRow rowDragManaged onRowDragEnd={(e) => move(getIndex(e.node.data), e.overIndex)} - suppressBrowserResizeObserver columnDefs={columnDefs} detailRowAutoHeight onSelectionChanged={() => { @@ -259,9 +249,9 @@ export function CustomAgGridTable({ getRowId={(row) => row.data[FieldConstants.AG_GRID_ROW_UUID]} pagination={pagination} paginationPageSize={paginationPageSize} - suppressRowClickSelection={suppressRowClickSelection} alwaysShowVerticalScroll={alwaysShowVerticalScroll} stopEditingWhenCellsLoseFocus={stopEditingWhenCellsLoseFocus} + theme="legacy" {...props} /> diff --git a/src/components/inputs/reactHookForm/agGridTable/cellEditors/numericEditor.ts b/src/components/inputs/reactHookForm/agGridTable/cellEditors/numericEditor.ts index e4d8cb9a..37f284cd 100644 --- a/src/components/inputs/reactHookForm/agGridTable/cellEditors/numericEditor.ts +++ b/src/components/inputs/reactHookForm/agGridTable/cellEditors/numericEditor.ts @@ -56,11 +56,11 @@ export class NumericEditor implements ICellEditorComp { this.cancelBeforeStart = !!isNotANumber; } - static isBackspace(event: any) { + static isBackspace(event: KeyboardEvent) { return event.key === KEY_BACKSPACE; } - static isNavigationKey(event: any) { + static isNavigationKey(event: KeyboardEvent) { return event.key === 'ArrowLeft' || event.key === 'ArrowRight'; } @@ -92,7 +92,7 @@ export class NumericEditor implements ICellEditorComp { return charStr && !!/\d|,|\./.test(charStr); } - static isNumericKey(event: any) { + static isNumericKey(event: KeyboardEvent) { const charStr = event.key; return NumericEditor.isCharNumeric(charStr); } diff --git a/src/components/inputs/reactHookForm/agGridTable/csvUploader/CsvUploader.tsx b/src/components/inputs/reactHookForm/agGridTable/csvUploader/CsvUploader.tsx index 69cba501..75cc1b4f 100644 --- a/src/components/inputs/reactHookForm/agGridTable/csvUploader/CsvUploader.tsx +++ b/src/components/inputs/reactHookForm/agGridTable/csvUploader/CsvUploader.tsx @@ -25,7 +25,7 @@ import { CancelButton } from '../../utils/CancelButton'; export interface CsvUploaderProps { name: string; onClose: () => void; - open: true; + open: boolean; title: string[]; fileHeaders: string[]; fileName: string; diff --git a/src/components/inputs/reactHookForm/booleans/BooleanInput.tsx b/src/components/inputs/reactHookForm/booleans/BooleanInput.tsx index 552ef68a..f749a7e5 100644 --- a/src/components/inputs/reactHookForm/booleans/BooleanInput.tsx +++ b/src/components/inputs/reactHookForm/booleans/BooleanInput.tsx @@ -7,13 +7,13 @@ import { ChangeEvent, useCallback } from 'react'; import { useIntl } from 'react-intl'; -import { Checkbox, FormControlLabel, Switch } from '@mui/material'; +import { Checkbox, CheckboxProps, FormControlLabel, Switch, SwitchProps } from '@mui/material'; import { useController } from 'react-hook-form'; export interface BooleanInputProps { name: string; label?: string; - formProps?: any; + formProps?: SwitchProps | CheckboxProps; Input: typeof Switch | typeof Checkbox; } diff --git a/src/components/inputs/reactHookForm/numbers/RangeInput.tsx b/src/components/inputs/reactHookForm/numbers/RangeInput.tsx index 9c380f6d..248d4678 100644 --- a/src/components/inputs/reactHookForm/numbers/RangeInput.tsx +++ b/src/components/inputs/reactHookForm/numbers/RangeInput.tsx @@ -9,13 +9,14 @@ import { FormattedMessage } from 'react-intl'; import { useMemo } from 'react'; import { type ObjectSchema } from 'yup'; import { FormControl, Grid, InputLabel } from '@mui/material'; +import { Theme } from '@mui/system'; import { FloatInput } from './FloatInput'; import yup from '../../../../utils/yupConfig'; import { MuiSelectInput } from '../selectInputs/MuiSelectInput'; import { FieldConstants } from '../../../../utils/constants/fieldConstants'; const style = { - inputLegend: (theme: any) => ({ + inputLegend: (theme: Theme) => ({ backgroundImage: 'linear-gradient(rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.16))', backgroundColor: theme.palette.background.paper, padding: '0 8px 0 8px', diff --git a/src/components/inputs/reactHookForm/numbers/SliderInput.tsx b/src/components/inputs/reactHookForm/numbers/SliderInput.tsx index bd0ee17c..79055f07 100644 --- a/src/components/inputs/reactHookForm/numbers/SliderInput.tsx +++ b/src/components/inputs/reactHookForm/numbers/SliderInput.tsx @@ -11,7 +11,7 @@ import { identity } from '../utils/functions'; export interface SliderInputProps extends SliderProps { name: string; - onValueChanged: (value: any) => void; + onValueChanged: (value: number | number[]) => void; } export function SliderInput({ name, min, max, step, size = 'small', onValueChanged = identity }: SliderInputProps) { diff --git a/src/components/inputs/reactHookForm/text/TextInput.tsx b/src/components/inputs/reactHookForm/text/TextInput.tsx index 61ccb5a6..a654b505 100644 --- a/src/components/inputs/reactHookForm/text/TextInput.tsx +++ b/src/components/inputs/reactHookForm/text/TextInput.tsx @@ -67,7 +67,7 @@ export function TextInput({ onChange(outputTransform('')); }; - const handleValueChanged = (e: any) => { + const handleValueChanged = (e: React.ChangeEvent) => { if (acceptValue(e.target.value)) { onChange(outputTransform(e.target.value)); } diff --git a/src/components/inputs/reactQueryBuilder/CountryValueEditor.tsx b/src/components/inputs/reactQueryBuilder/CountryValueEditor.tsx index 8f8da5ae..2a94ca9c 100644 --- a/src/components/inputs/reactQueryBuilder/CountryValueEditor.tsx +++ b/src/components/inputs/reactQueryBuilder/CountryValueEditor.tsx @@ -48,7 +48,7 @@ export function CountryValueEditor(props: ValueEditorProps) { getOptionLabel={(code: string) => (code ? translate(code) : '')} valid={valid} title={title} - onChange={(event: SyntheticEvent, newValue: any) => { + onChange={(event: SyntheticEvent, newValue) => { handleOnChange(newValue); }} fullWidth diff --git a/src/components/inputs/reactQueryBuilder/ElementValueEditor.tsx b/src/components/inputs/reactQueryBuilder/ElementValueEditor.tsx index 586336de..91b1ac17 100644 --- a/src/components/inputs/reactQueryBuilder/ElementValueEditor.tsx +++ b/src/components/inputs/reactQueryBuilder/ElementValueEditor.tsx @@ -10,6 +10,7 @@ import { useEffect } from 'react'; import { useCustomFormContext } from '../reactHookForm/provider/useCustomFormContext'; import { fetchElementsInfos } from '../../../services'; import { DirectoryItemsInput } from '../reactHookForm/DirectoryItemsInput'; +import { ElementAttributes } from '../../../utils'; interface ElementValueEditorProps { name: string; @@ -18,7 +19,7 @@ interface ElementValueEditorProps { titleId: string; hideErrorMessage: boolean; onChange?: (e: any) => void; - itemFilter?: any; + itemFilter?: (val: ElementAttributes) => boolean; defaultValue?: any; } @@ -37,7 +38,7 @@ export function ElementValueEditor(props: ElementValueEditorProps) { fetchElementsInfos(defaultValue).then((childrenWithMetadata) => { setValue( name, - childrenWithMetadata.map((v: any) => { + childrenWithMetadata.map((v: ElementAttributes) => { return { id: v.elementUuid, name: v.elementName, diff --git a/src/components/inputs/reactQueryBuilder/PropertyValueEditor.tsx b/src/components/inputs/reactQueryBuilder/PropertyValueEditor.tsx index eddf6cf3..70eb3f50 100644 --- a/src/components/inputs/reactQueryBuilder/PropertyValueEditor.tsx +++ b/src/components/inputs/reactQueryBuilder/PropertyValueEditor.tsx @@ -80,7 +80,7 @@ export function PropertyValueEditor(props: ExpertFilterPropertyProps) { autoSelect forcePopupIcon renderInput={(params) => } - onChange={(event, value: any) => { + onChange={(event, value) => { onChange(FieldConstants.PROPERTY_NAME, value); }} size="small" @@ -92,7 +92,7 @@ export function PropertyValueEditor(props: ExpertFilterPropertyProps) { size="small" title={valueEditorProps?.title} error={!valid} - onChange={(event, value: any) => { + onChange={(event, value) => { onChange(FieldConstants.PROPERTY_OPERATOR, value); }} > @@ -112,7 +112,7 @@ export function PropertyValueEditor(props: ExpertFilterPropertyProps) { renderInput={(params) => } freeSolo autoSelect - onChange={(event, value: any) => { + onChange={(event, value) => { onChange(FieldConstants.PROPERTY_VALUES, value); }} size="small" diff --git a/src/components/inputs/reactQueryBuilder/TranslatedValueEditor.tsx b/src/components/inputs/reactQueryBuilder/TranslatedValueEditor.tsx index c9b255c5..05883e26 100644 --- a/src/components/inputs/reactQueryBuilder/TranslatedValueEditor.tsx +++ b/src/components/inputs/reactQueryBuilder/TranslatedValueEditor.tsx @@ -47,7 +47,7 @@ export function TranslatedValueEditor(props: ValueEditorProps) { options={Object.keys(translatedValuesAutocomplete)} title={title} getOptionLabel={(code: string) => translatedValuesAutocomplete[code]} - onChange={(event, newValue: any) => handleOnChange(newValue)} + onChange={(event, newValue) => handleOnChange(newValue)} multiple fullWidth renderInput={(params) => } diff --git a/src/components/inputs/reactQueryBuilder/ValueEditor.tsx b/src/components/inputs/reactQueryBuilder/ValueEditor.tsx index a3a45a0e..fbc09d93 100644 --- a/src/components/inputs/reactQueryBuilder/ValueEditor.tsx +++ b/src/components/inputs/reactQueryBuilder/ValueEditor.tsx @@ -24,6 +24,7 @@ import { FilterType } from '../../filter/constants/FilterConstants'; import { GroupValueEditor } from './compositeRuleEditor/GroupValueEditor'; import { OPERATOR_OPTIONS } from '../../filter/expert/expertFilterConstants'; import { FieldType } from '../../../utils/types/fieldType'; +import { ElementAttributes } from '../../../utils'; const styles = { noArrows: { @@ -41,7 +42,7 @@ export function ValueEditor(props: ValueEditorProps) { const formContext = useFormContext(); const { getValues } = formContext; const itemFilter = useCallback( - (filterValue: any) => { + (filterValue: ElementAttributes) => { if (filterValue?.type === ElementType.FILTER) { return ( // we do not authorize to use an expert filter in the rules of diff --git a/src/components/muiVirtualizedTable/ColumnHeader.tsx b/src/components/muiVirtualizedTable/ColumnHeader.tsx deleted file mode 100644 index 3ce92576..00000000 --- a/src/components/muiVirtualizedTable/ColumnHeader.tsx +++ /dev/null @@ -1,154 +0,0 @@ -/** - * 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/. - */ - -import { ComponentProps, forwardRef, MouseEvent, ReactNode, useCallback, useMemo, useRef, useState } from 'react'; - -import { - ArrowDownward as ArrowDownwardIcon, - ArrowUpward as ArrowUpwardIcon, - FilterAltOutlined as FilterAltOutlinedIcon, -} from '@mui/icons-material'; - -import { styled } from '@mui/system'; -import { Box, BoxProps, Theme } from '@mui/material'; -import { mergeSx } from '../../utils/styles'; - -const styles = { - label: { - fontWeight: 'bold', - fontSize: '0.875rem', // to mimic TableCellRoot 'binding' - }, - divFlex: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - height: '100%', - }, - divNum: { - flexDirection: 'row-reverse', - textAlign: 'right', - }, - sortDiv: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - }, - sortButton: { - fill: 'currentcolor', - }, - filterButton: { - stroke: 'currentcolor', - }, - filterTooLossy: (theme: Theme) => ({ - stroke: theme.palette.secondary.main, - }), - transparent: { - opacity: 0, - }, - hovered: { - opacity: 0.5, - }, -}; - -interface SortButtonProps { - signedRank?: number; - headerHovered: boolean; - hovered?: boolean; - onClick: BoxProps['onClick']; -} - -// Shows an arrow pointing to smaller value when sorting is active. -// signedRank of 0 means no sorting, we only show the arrow on hovering of the header, -// 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 }: SortButtonProps) { - const sortRank = Math.abs(signedRank); - const visibilityStyle = (!signedRank || undefined) && (props.headerHovered ? styles.hovered : styles.transparent); - return ( - - {signedRank >= 0 ? : } - {sortRank > 1 && !props.hovered && {sortRank}} - - ); -} - -interface FilterButtonProps { - filterLevel: number; - headerHovered: boolean; - onClick: ComponentProps['onClick']; -} - -function FilterButton(props: FilterButtonProps) { - const { filterLevel, headerHovered, onClick } = props; - const visibilityStyle = !filterLevel && (headerHovered ? styles.hovered : styles.transparent); - return ( - 1 && styles.filterTooLossy, - visibilityStyle - )} - /> - ); -} - -export interface ColumnHeaderProps extends BoxProps { - label: ReactNode; - numeric: boolean; - sortSignedRank: SortButtonProps['signedRank']; - filterLevel: FilterButtonProps['filterLevel']; - onSortClick: SortButtonProps['onClick']; - onFilterClick: FilterButtonProps['onClick']; -} - -export const ColumnHeaderComponent = forwardRef((props, ref) => { - const { className, label, numeric, sortSignedRank, filterLevel, onSortClick, onFilterClick, onContextMenu, style } = - props; - - const [hovered, setHovered] = useState(false); - const onHover = useCallback((evt: Event) => { - setHovered(evt.type === 'mouseenter'); - }, []); - - const topmostDiv = useRef(); - - const handleFilterClick = useMemo(() => { - if (!onFilterClick) { - return undefined; - } - return (evt: MouseEvent) => { - onFilterClick(evt); - }; - }, [onFilterClick]); - - return ( - // @ts-ignore it does not let us define Box with onMouseEnter/onMouseLeave attributes with 'div' I think, not sure though - - {/* we cheat here to get the _variable_ height */} - - {label} - - {onSortClick && } - {handleFilterClick && ( - - )} - - ); -}); - -export const ColumnHeader = styled(ColumnHeaderComponent)({}); diff --git a/src/components/muiVirtualizedTable/KeyedColumnsRowIndexer.tsx b/src/components/muiVirtualizedTable/KeyedColumnsRowIndexer.tsx deleted file mode 100644 index 5a9c7a89..00000000 --- a/src/components/muiVirtualizedTable/KeyedColumnsRowIndexer.tsx +++ /dev/null @@ -1,737 +0,0 @@ -/** - * 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 @typescript-eslint/no-unused-vars, no-param-reassign */ -import { ReactElement } from 'react'; -import { ColumnProps } from 'react-virtualized'; -import { equalsArray } from '../../utils/algos'; - -export interface RowProps { - notClickable?: boolean; -} - -export interface CustomColumnProps extends ColumnProps { - sortable?: boolean; - numeric?: boolean; - indexer?: KeyedColumnsRowIndexer; - label: string; - clickable?: boolean; - fractionDigits?: number; - unit?: number; - extra?: ReactElement; - nostat?: boolean; -} - -export enum ChangeWays { - SIMPLE = 'Simple', - TAIL = 'Tail', - AMEND = 'Amend', - REMOVE = 'Remove', -} - -/* This is not real code commented -const someTypicalColumns = [ - { label: 'strings column', dataKey: 'key1' }, - { - label: 'integers column', - dataKey: 'key2', - numeric: true, - fractionDigits: 0, - }, - { - label: 'column to ignore', - dataKey: 'key3', - nostat: true, - }, -]; - */ - -export interface ColStat { - imin?: number | null; - imax?: number | null; - seen?: any; - kept?: any; -} - -export const noOpHelper = Object.freeze({ - debugName: 'noOp', - maintainsStats: false, - initStat: () => undefined, - updateStat: (colStat: ColStat, value: number) => {}, - accepts: (value: any, userParams: any[] | undefined, outerParams: any[] | undefined) => { - return true; - }, -}); - -const numericHelper = Object.freeze({ - debugName: 'num', - maintainsStats: true, - initStat: () => { - return { imin: null, imax: null }; - }, - updateStat: (colStat: ColStat, value: number) => { - if (colStat.imin === undefined || colStat.imin === null || colStat.imin > value) { - colStat.imin = value; - } - if (colStat.imax === undefined || colStat.imax === null || colStat.imax < value) { - colStat.imax = value; - } - }, - accepts: (value: number, userParams: any[] | undefined, outerParams: any[] | undefined) => { - return true; - }, -}); - -export const collectibleHelper = Object.freeze({ - debugName: 'collectible', - maintainsStats: true, - initStat: () => { - return { seen: {}, kept: {} }; - }, - updateStat: (colStat: ColStat, cellValue: number, isForKeep: boolean) => { - const m = isForKeep ? colStat.kept : colStat.seen; - if (!m[cellValue]) { - m[cellValue] = 1; - } else { - m[cellValue] += 1; - } - }, - accepts: (value: number, userParams: any[] | undefined, outerParams: any[] | undefined) => { - return !userParams || userParams.some((v) => v === value); - }, -}); - -export const getHelper = (column?: CustomColumnProps) => { - if (column?.numeric) { - return numericHelper; - } - if (!column?.nostat) { - return collectibleHelper; - } - return noOpHelper; -}; - -export interface Preferences { - isThreeState: boolean; - singleColumnByDefault: boolean; -} - -export interface FilteredRows { - rowAndOrigIndex: [RowProps, number][]; - colsStats: Record; - 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++) { - 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 - * a view index to model index array. - */ -export class KeyedColumnsRowIndexer { - static get CHANGE_WAYS() { - return ChangeWays; - } - - versionSetter: ((version: number) => void) | null; - - byColFilter: Record | 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( - isThreeState = true, - singleColumnByDefault = false, - delegatorCallback = null, - versionSetter: ((version: number) => void) | null = null - ) { - this.versionSetter = versionSetter; - this.version = 0; - this.filterVersion = 0; - - this.delegatorCallback = delegatorCallback; - this.indirectionStatus = null; - - this.isThreeState = isThreeState; - this.singleColumnByDefault = singleColumnByDefault; - - this.lastUsedRank = 1; - this.sortingState = null; - - this.groupingCount = 0; - this.byColFilter = null; // common - this.byRowFilter = null; - } - - hasVersionSetter = () => { - return !!this.versionSetter; - }; - - getVersion = () => { - return this.version; - }; - - bumpVersion = (isFilter = false) => { - this.version += 1; - if (isFilter) { - this.filterVersion = this.version; - } - if (this.delegatorCallback) { - this.indirectionStatus = 'to sort'; - this.delegatorCallback(this, (updated_ok: boolean) => { - this.indirectionStatus = updated_ok ? 'done' : 'no_luck'; - }); - } - if (this.versionSetter) { - this.versionSetter(this.version); - } - }; - - updatePreferences = (preferences: Preferences) => { - if ( - preferences.isThreeState === this.isThreeState && - preferences.singleColumnByDefault === this.singleColumnByDefault - ) { - return false; - } - - if (preferences.isThreeState !== this.isThreeState) { - this.isThreeState = preferences.isThreeState; - } - if (preferences.singleColumnByDefault !== this.singleColumnByDefault) { - this.singleColumnByDefault = preferences.singleColumnByDefault; - } - - this.bumpVersion(); - return true; - }; - - // Does not mutate any internal - // returns - // { rowAndOrigIndex : [[row, originalRowIndex],...], - // colsStats : { - // colKey1 : { - // seen : Map(value : count), - // kept : Map(value : count) - // }, - // colKey2 : { - // imin : number, - // imax : number, - // }, colKeyN, ... - // } - // } - preFilterRowMapping = ( - columns: CustomColumnProps[], - rows: RowProps[], - rowFilter: (row: RowProps) => boolean - ): FilteredRows | null => { - if (!rows?.length || !columns?.length) { - return null; - } - - const ri: [RowProps, number][] = []; - const cs: Record = {}; - - 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++) { - const row = rows[rowIdx]; - let acceptsRow = true; - const acceptedOnRow: Record = {}; - for (let colIdx = 0; colIdx < columns.length; colIdx++) { - const col = columns[colIdx]; - const helper = getHelper(col); - const colKey = col.dataKey; - // @ts-ignore should not access to value directly - const cellValue = row[colKey]; - helper.updateStat(cs[colKey], cellValue, false); - - const colFilterParams = this.byColFilter?.[colKey]; - let acceptsCell = true; - if (colFilterParams) { - const up = colFilterParams.userParams; - const op = colFilterParams.outerParams; - - if (!helper.accepts(cellValue, up, op)) { - acceptsCell = false; - } - } - if (helper.maintainsStats && acceptsCell) { - acceptedOnRow[colIdx] = cellValue; - } - acceptsRow &&= acceptsCell; - } - - if (acceptsRow && rowFilter) { - acceptsRow = rowFilter(row); - } - if (acceptsRow && this.byRowFilter) { - acceptsRow = this.byRowFilter(row); - } - - if (acceptsRow) { - 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]); - } - } - - return { rowAndOrigIndex: ri, colsStats: cs, rowsCount: rows.length }; - }; - - // Does not mutate any internal - // returns an array of indexes in rows given to preFilter - makeGroupAndSortIndirector = (preFilteredRowPairs: FilteredRows | null, columns: CustomColumnProps[]) => { - if (!preFilteredRowPairs) { - return null; - } - - const codedColumns = !this.sortingState ? null : codedColumnsFromKeyAndDirection(this.sortingState, columns); - const groupingColumnsCount = this.groupingCount; - const indexedArray = groupAndSort(preFilteredRowPairs, codedColumns, groupingColumnsCount, columns); - - return !indexedArray ? null : indexedArray.map((k) => k[1]); - }; - - getSortingAsKeyAndCodedRank = () => { - if (!this.sortingState) { - return []; - } - - return this.sortingState.map((kd, i) => { - const sign = giveDirSignFor(kd[1]); - const codedRank = sign * (i + 1); - return [kd[0], codedRank]; - }); - }; - - // returns true if really changed (and calls versionSetter if needed) - updateSortingFromUser = (colKey: string, change_way: ChangeWays) => { - const keyAndDirections = this.sortingState; - - if (change_way === ChangeWays.REMOVE) { - if (!keyAndDirections) { - return false; - } - - const would = keyAndDirections.filter((p) => p[0] !== colKey); - if (would.length === keyAndDirections.length) { - return false; - } - - this.sortingState = would.length ? would : null; - } else if (!keyAndDirections) { - this.sortingState = [[colKey, canonicalForSign(1)]]; - this.lastUsedRank = 1; - } else { - const wasAtIdx = keyAndDirections.findIndex((p) => p[0] === colKey); - const wasFuzzyDir = wasAtIdx < 0 ? 0 : keyAndDirections[wasAtIdx][1]; - const wasSignDir = giveDirSignFor(wasFuzzyDir); - - if (change_way === ChangeWays.SIMPLE) { - if (wasSignDir < 0 && this.isThreeState) { - if (this.sortingState?.length === 1) { - this.sortingState = null; - } else { - this.sortingState?.splice(wasAtIdx, 1); - } - } else { - if (this.singleColumnByDefault || wasAtIdx < 0) { - this.sortingState = []; - } else { - this.sortingState?.splice(wasAtIdx, 1); - } - const nextSign = wasSignDir ? -wasSignDir : 1; - const nextKD: [string, string | undefined] = [colKey, canonicalForSign(nextSign)]; - this.sortingState?.unshift(nextKD); - } - } else if (change_way === ChangeWays.TAIL) { - if (wasAtIdx < 0) { - this.sortingState?.push([colKey, canonicalForSign(1)]); - } else if (wasAtIdx !== keyAndDirections.length - 1) { - return false; - } 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.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(); - return true; - }; - - codedRankByColumnIndex = (columns: CustomColumnProps[]) => { - return codedColumnsFromKeyAndDirection(this.sortingState, columns); - }; - - columnSortingSignedRank = (colKey: string) => { - if (!this?.sortingState?.length) { - return 0; - } - const idx = this.sortingState.findIndex((kd) => kd[0] === colKey); - if (idx < 0) { - return 0; - } - const colSorting = this.sortingState[idx]; - return giveDirSignFor(colSorting[1]) * (idx + 1); - }; - - highestCodedColumn = (columns: CustomColumnProps[]) => { - if (!this?.sortingState?.length) { - return 0; - } - const colSorting = this.sortingState[0]; - const colKey = colSorting[0]; - const idx = columns.findIndex((col) => col.dataKey === colKey); - if (idx < 0) { - return 0; - } - return giveDirSignFor(colSorting[1]) * (idx + 1); - }; - - getColFilterParams = (colKey: string | null, isForUser: boolean) => { - if (!colKey || !this.byColFilter) { - return undefined; - } - const colFilter = this.byColFilter[colKey]; - if (!colFilter) { - return undefined; - } - - return colFilter[isForUser ? 'userParams' : 'outerParams']; - }; - - setColFilterParams = (colKey: string | null, params: any[] | null, isForUser: boolean) => { - if (!colKey) { - if (params) { - throw new Error('column key has to be defined'); - } - return false; - } - - const fieldName = isForUser ? 'userParams' : 'outerParams'; - - if (params) { - if (!this.byColFilter) { - this.byColFilter = {}; - } - let colFilter: { userParams?: any[]; outerParams?: any[] } = this.byColFilter[colKey]; - if (!colFilter) { - colFilter = {}; - this.byColFilter[colKey] = colFilter; - } else if (colFilter[fieldName] === params) { - return false; - } - - colFilter[fieldName] = params; - } else { - if (!this.byColFilter) { - return false; - } - - const otherFieldName = !isForUser ? 'userParams' : 'outerParams'; - if (this.byColFilter[colKey]?.[otherFieldName]) { - delete this.byColFilter[colKey][fieldName]; - } else { - delete this.byColFilter[colKey]; - } - } - if (isForUser) { - this.bumpVersion(true); - } - return true; - }; - - getColFilterOuterParams = (colKey: string | null) => { - return this.getColFilterParams(colKey, false); - }; - - setColFilterOuterParams = (colKey: string, outerParams: any[]) => { - return this.setColFilterParams(colKey, outerParams, false); - }; - - getColFilterUserParams = (colKey: string | null) => { - return this.getColFilterParams(colKey, true); - }; - - setColFilterUserParams = (colKey: string | null, params: any[] | null) => { - return this.setColFilterParams(colKey, params, true); - }; - - getUserFiltering = () => { - const ret: Record = {}; - if (this.byColFilter) { - Object.entries(this.byColFilter).forEach(([k, v]) => { - if (!v.userParams) { - return; - } - // @ts-ignore must be an iterator to use spread iterator - ret[k] = [...v.userParams]; - }); - } - return ret; - }; - - updateRowFiltering = (rowFilterFunc: (row: RowProps) => boolean) => { - if (typeof rowFilterFunc !== 'function') { - throw new Error('row filter should be a function'); - } - this.byRowFilter = rowFilterFunc; - this.bumpVersion(); - }; -} - -export const forTesting = { - codedColumnsFromKeyAndDirection, - makeCompositeComparatorFromCodedColumns, -}; diff --git a/src/components/muiVirtualizedTable/MuiVirtualizedTable.tsx b/src/components/muiVirtualizedTable/MuiVirtualizedTable.tsx deleted file mode 100644 index 2e5f4e08..00000000 --- a/src/components/muiVirtualizedTable/MuiVirtualizedTable.tsx +++ /dev/null @@ -1,888 +0,0 @@ -/** - * 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 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/ - */ -import { createRef, PureComponent, ReactElement, ReactNode, MouseEvent, KeyboardEvent, MutableRefObject } from 'react'; -import { FormattedMessage } from 'react-intl'; -import clsx from 'clsx'; -import memoize from 'memoize-one'; -import { Autocomplete, Chip, IconButton, Popover, SxProps, TableCell, TextField } from '@mui/material'; -import { styled } from '@mui/system'; -import { GetApp as GetAppIcon } from '@mui/icons-material'; -import { AutoSizer, Column, ColumnProps, RowMouseEventHandlerParams, Table, TableCellProps } from 'react-virtualized'; -import CsvDownloader from 'react-csv-downloader'; -import { OverflowableText } from '../overflowableText/OverflowableText'; -import { makeComposeClasses, toNestedGlobalSelectors } from '../../utils/styles'; -import { - ChangeWays, - collectibleHelper, - CustomColumnProps, - getHelper, - KeyedColumnsRowIndexer, - RowProps, -} from './KeyedColumnsRowIndexer'; -import { ColumnHeader } from './ColumnHeader'; - -function getTextWidth(text: any): number { - // re-use canvas object for better performance - let canvas = - //@ts-ignore this is questioning - getTextWidth.canvas || - //@ts-ignore this is questioning - (getTextWidth.canvas = document.createElement('canvas')); - let 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); - return metrics.width; -} - -export const DEFAULT_CELL_PADDING = 16; -export const DEFAULT_HEADER_HEIGHT = 48; -export const DEFAULT_ROW_HEIGHT = 48; - -// As a bunch of individual variables to try to make it easier -// to track that they are all used. Not sure, maybe group them in an object ? -const cssFlexContainer = 'flexContainer'; -const cssTable = 'table'; -const cssTableRow = 'tableRow'; -const cssTableRowHover = 'tableRowHover'; -const cssTableCell = 'tableCell'; -const cssTableCellColor = 'tableCellColor'; -const cssNoClick = 'noClick'; -const cssHeader = 'header'; -const cssRowBackgroundDark = 'rowBackgroundDark'; -const cssRowBackgroundLight = 'rowBackgroundLight'; - -// converted to nested rules -const defaultStyles = { - [cssFlexContainer]: { - display: 'flex', - alignItems: 'center', - boxSizing: 'border-box', - }, - [cssTable]: { - // temporary right-to-left patch, waiting for - // https://github.com/bvaughn/react-virtualized/issues/454 - '& .ReactVirtualized__Table__headerRow': { - flip: false, - }, - }, - [cssTableRow]: { - cursor: 'pointer', - }, - [cssTableRowHover]: {}, - [cssTableCell]: { - flex: 1, - padding: DEFAULT_CELL_PADDING + 'px', - }, - [cssTableCellColor]: {}, - [cssNoClick]: { - cursor: 'initial', - }, - [cssHeader]: { - fontWeight: 'bold', - }, - [cssRowBackgroundDark]: {}, - [cssRowBackgroundLight]: {}, -}; - -// TODO find a better system, maybe MUI will fix/improve this ? -// Different from all others because of the poor nested sx support for portals -// requiring classname sharing/forwarding between the content and the tooltip -const defaultTooltipSx = { - maxWidth: '1260px', - fontSize: '0.9rem', -}; - -//TODO do we need to export this to clients (index.js) ? -export const generateMuiVirtualizedTableClass = (className: string) => `MuiVirtualizedTable-${className}`; -const composeClasses = makeComposeClasses(generateMuiVirtualizedTableClass); - -interface AmongChooserProps { - options: T[]; - value?: T[]; - setValue: (values: T[]) => void; - onDropDownVisibility: (visibility: boolean) => void; - id: string; -} - -const AmongChooser = (props: AmongChooserProps) => { - const { options, value, setValue, id, onDropDownVisibility } = props; - - return ( - - { - setValue(newVal); - }} - onClose={() => onDropDownVisibility(false)} - onOpen={() => onDropDownVisibility(true)} - options={options} - renderInput={(props) => } - renderTags={(val, getTagsProps) => { - return val.map((code, index) => { - return ; - }); - }} - /> - - ); -}; - -function makeIndexRecord( - viewIndexToModel: number[] | null, - rows: Record -): { - viewIndexToModel: number[] | null; - rowGetter: (index: number) => RowProps; -} { - return { - viewIndexToModel, - rowGetter: !viewIndexToModel - ? (viewIndex: number) => rows[viewIndex] - : (viewIndex: number) => { - if (viewIndex >= viewIndexToModel.length || viewIndex < 0) { - return {} as RowProps; - } - const modelIndex = viewIndexToModel[viewIndex]; - return rows[modelIndex]; - }, - }; -} - -const initIndexer = (props: CustomColumnProps, versionSetter: (version: number) => void) => { - if (!props.sortable) { - return null; - } - - if (props.indexer) { - return props.indexer; - } - - return new KeyedColumnsRowIndexer(true, true, null, versionSetter); -}; - -const preFilterData = memoize( - ( - columns, - rows, - filterFromProps, - indexer, - filterVersion // filterVersion is unused directly, used only as a workaround just to reset the memoization - ) => { - return indexer.preFilterRowMapping(columns, rows, filterFromProps); - } -); - -const reorderIndex = memoize( - ( - indexer, - indirectionVersion, - rows, - columns, - filterFromProps, - sortFromProps - ): { - viewIndexToModel: number[] | null; - rowGetter: ((index: number) => RowProps) | ((index: number) => number); - } => { - if (!rows) { - return { - viewIndexToModel: [], - rowGetter: (viewIndex: number) => viewIndex, - }; - } - - const highestCodedColumn = !indexer ? 0 : indexer.highestCodedColumn(columns); - if (sortFromProps && highestCodedColumn) { - const colIdx = Math.abs(highestCodedColumn) - 1; - let reorderedIndex = sortFromProps( - columns[colIdx].dataKey, - highestCodedColumn > 0, - !!columns[colIdx].numeric - ); - return makeIndexRecord(reorderedIndex, rows); - } - if (sortFromProps) { - try { - 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 - console.warn( - 'error in external sort. consider adding support for datakey=null in your external sort function' - ); - } - } - if (indexer) { - const prefiltered = preFilterData(columns, rows, filterFromProps, indexer, indirectionVersion); - const reorderedIndex = indexer.makeGroupAndSortIndirector(prefiltered, columns); - return makeIndexRecord(reorderedIndex, rows); - } - if (filterFromProps) { - const viewIndexToModel = rows - .map((r: unknown, i: number) => [r, i]) - .filter(([r, _]: [unknown, number]) => filterFromProps(r)) - .map(([_, j]: [unknown, number]) => j); - return makeIndexRecord(viewIndexToModel, rows); - } - - return makeIndexRecord(null, rows); - } -); - -export interface MuiVirtualizedTableProps extends CustomColumnProps { - headerHeight: number; - columns: CustomColumnProps[]; - defersFilterChanges: (() => void) | null; - rows: RowProps[]; - filter: unknown; - sort: unknown; - classes: Record; - onRowClick?: (event: RowMouseEventHandlerParams) => void; - rowHeight: number; - onCellClick: (row: RowProps, column: ColumnProps) => void; - tooltipSx: SxProps; - name: string; - exportCSVDataKeys: unknown[]; - enableExportCSV: boolean; -} - -export interface MuiVirtualizedTableState { - headerHeight: number; - indexer: KeyedColumnsRowIndexer | null; - indirectionVersion: number; - popoverAnchorEl: Element | null; - popoverColKey: string | null; - deferredFilterChange: null | { - newVal: unknown[] | null; - colKey: string | null; - }; -} - -class MuiVirtualizedTableComponent extends PureComponent { - static defaultProps = { - headerHeight: DEFAULT_HEADER_HEIGHT, - rowHeight: DEFAULT_ROW_HEIGHT, - enableExportCSV: false, - classes: {}, - }; - - headers: MutableRefObject; - observer: IntersectionObserver; - dropDownVisible: boolean; - - 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); - // 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 = { - root: null, - rootMargin: '0px', - threshold: 0.1, - }; - this.observer = new IntersectionObserver(this._computeHeaderSize, options); - this.dropDownVisible = false; - this.state = { - headerHeight: this.props.headerHeight, - indexer: initIndexer(props, this.setVersion), - indirectionVersion: 0, - popoverAnchorEl: null, - popoverColKey: null, - deferredFilterChange: null, - }; - } - - setVersion = (v: number) => { - this.setState({ indirectionVersion: v }); - }; - - componentDidUpdate(oldProps: MuiVirtualizedTableProps) { - if (oldProps.indexer !== this.props.indexer || oldProps.sortable !== this.props.sortable) { - this.setState((state) => { - return { - indexer: initIndexer(this.props, this.setVersion), - indirectionVersion: (state?.indirectionVersion ?? 0) + 1, - }; - }); - } - if (oldProps.headerHeight !== this.props.headerHeight) { - this._computeHeaderSize(); - } - } - - componentDidMount() { - window.addEventListener('resize', this._computeHeaderSize); - } - - componentWillUnmount() { - window.removeEventListener('resize', this._computeHeaderSize); - this.observer.disconnect(); - } - - _registerHeader(label: string, header: unknown) { - if (this.headers.current) { - this.headers.current[label] = header; - } - } - - _registerObserver(element: Element) { - if (element !== null) { - this.observer.observe(element); - } - } - - computeDataWidth = (text: string) => { - return getTextWidth(text || '') + 2 * DEFAULT_CELL_PADDING; - }; - - 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)); - } - if (col.maxWidth) { - size = Math.min(col.maxWidth, size); - } - sizes[col.dataKey] = Math.ceil(size); - } - }); - return sizes; - }); - - openPopover = (popoverTarget: Element, colKey: string) => { - const col = this.props.columns.find((c) => c.dataKey === colKey); - if (getHelper(col) !== collectibleHelper) { - return; - } - - this.dropDownVisible = false; - this.setState({ - popoverAnchorEl: popoverTarget, - popoverColKey: colKey, - }); - }; - - 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(); - } - this.setState((state, _) => { - return { - popoverAnchorEl: null, - popoverColKey: null, - deferredFilterChange: null, - indirectionVersion: state.indirectionVersion + (bumpsVersion ? 1 : 0), - }; - }); - }; - - makeColumnFilterEditor = () => { - const colKey = this.state.popoverColKey; - const outerParams = this.state.indexer?.getColFilterOuterParams(colKey); - const userParams = - !this.props.defersFilterChanges || !this.state.deferredFilterChange - ? this.state.indexer?.getColFilterUserParams(colKey) - : this.state.deferredFilterChange.newVal ?? undefined; - const prefiltered = preFilterData( - this.props.columns, - this.props.rows, - this.props.filter, - this.state.indexer, - this.state.indirectionVersion - ); - - 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) { - for (const key of Object.getOwnPropertyNames(colStat.seen)) { - if (options.findIndex((o) => o === key) < 0) { - options.push(key); - } - } - } - options.sort(); - - return ( - { - this.onFilterParamsChange(newVal, colKey); - }} - onDropDownVisibility={(visible) => (this.dropDownVisible = visible)} - /> - ); - }; - - _commitFilterChange = () => { - if (this.state.deferredFilterChange) { - const colKey = this.state.deferredFilterChange.colKey; - let newVal = this.state.deferredFilterChange.newVal; - if (newVal?.length === 0) { - newVal = null; - } - if (this.state.indexer?.setColFilterUserParams(colKey, newVal)) { - return true; - } - } - - 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, - }); - } - } - - sortClickHandler = (evt: MouseEvent, _: unknown, columnIndex: number) => { - const colKey = this.props.columns[columnIndex].dataKey; - - if (evt.altKey) { - //@ts-ignore should be currentTarget maybe ? - this.openPopover(evt.target, colKey); - return; - } - - let way = ChangeWays.SIMPLE; - if (evt.ctrlKey && evt.shiftKey) { - way = ChangeWays.AMEND; - } else if (evt.ctrlKey) { - way = ChangeWays.REMOVE; - } else if (evt.shiftKey) { - way = ChangeWays.TAIL; - } - - if (this.state.indexer?.updateSortingFromUser(colKey, way)) { - this.setState({ - indirectionVersion: this.state.indirectionVersion + 1, - }); - } - }; - - filterClickHandler = (_: MouseEvent, target: Element | undefined, columnIndex: number) => { - // ColumnHeader to (header) TableCell - const retargeted = target?.parentNode ?? target; - - const colKey = this.props.columns[columnIndex].dataKey; - //@ts-ignore still not the good types - this.openPopover(retargeted, colKey); - }; - - sortableHeader = ({ label, columnIndex }: { label: string; columnIndex: number }) => { - 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].numeric; - - const prefiltered = preFilterData(columns, this.props.rows, this.props.filter, indexer, indexer?.filterVersion); - const colStat = prefiltered?.colsStats?.[colKey]; - let filterLevel = 0; - if (userParams?.length) { - filterLevel += 1; - if (!colStat?.seen) { - filterLevel += 2; - } else if (userParams.filter((v: string) => !colStat.seen[v]).length) { - filterLevel += 2; - } - } - - // disable filtering when either: - // - the column is numeric, we only handle tags for string values - // - 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 - ? undefined - : (ev: MouseEvent, retargeted?: Element) => { - this.filterClickHandler(ev, retargeted, columnIndex); - }; - return ( - this._registerHeader(label, e)} - sortSignedRank={signedRank} - filterLevel={filterLevel} - numeric={numeric ?? false} - onSortClick={(ev: MouseEvent, name?: Element) => { - this.sortClickHandler(ev, name, columnIndex); - }} - onFilterClick={onFilterClick} - /> - ); - }; - - simpleHeaderRenderer = ({ label }: { label: string }) => { - return ( -
{ - this._registerHeader(label, element); - }} - > - {label} -
- ); - }; - - getRowClassName = ({ - index, - rowGetter, - }: { - index: number; - rowGetter: any; // Should be ((index: number) => RowProps) | ((index: number) => number) but it's not compatible with the code reorderIndex should be fixed to return only (index: number) => RowProps - }) => { - const { classes, onRowClick } = this.props; - return clsx( - composeClasses(classes, cssTableRow), - composeClasses(classes, cssFlexContainer), - index % 2 === 0 && composeClasses(classes, cssRowBackgroundDark), - index % 2 !== 0 && composeClasses(classes, cssRowBackgroundLight), - rowGetter(index)?.notClickable === true && composeClasses(classes, cssNoClick), // Allow to define a row as not clickable - { - [composeClasses(classes, cssTableRowHover)]: index !== -1 && onRowClick != null, - } - ); - }; - - onClickableRowClick = (event: RowMouseEventHandlerParams) => { - if (event.rowData?.notClickable !== true || event.event?.shiftKey || event.event?.ctrlKey) { - //@ts-ignore onRowClick is possibly undefined - this.props.onRowClick(event); - } - }; - - cellRenderer = ({ cellData, columnIndex, rowIndex }: TableCellProps) => { - const { columns, classes, rowHeight, onCellClick, rows, tooltipSx } = this.props; - - let displayedValue = this.getDisplayValue(columns[columnIndex], cellData); - - return ( - { - if ( - onCellClick && - columns[columnIndex].clickable !== undefined && - !rows[rowIndex]?.notClickable === true && - columns[columnIndex].clickable - ) { - onCellClick(rows[rowIndex], columns[columnIndex]); - } - }} - > - - - ); - }; - // 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); - } - - if (column.unit !== undefined) { - displayedValue += ' '; - displayedValue += column.unit; - } - return displayedValue; - } - - _computeHeaderSize() { - const headers = Object.values(this.headers.current); - if (headers.length === 0) { - return; - } - // for now keep 'historical' use of scrollHeight, - // 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( - Math.max(...scrollHeights) + DEFAULT_CELL_PADDING, - this.props.headerHeight - // hides (most often) padding override by forcing height - ); - if (headerHeight !== this.state.headerHeight) { - this.setState({ - headerHeight: headerHeight, - }); - } - } - - makeHeaderRenderer(dataKey: string, columnIndex: number) { - const { columns, classes } = this.props; - return (headerProps: any) => { - return ( - this._registerObserver(e)} - > - {this.props.sortable && this.state.indexer - ? this.sortableHeader({ - ...headerProps, - columnIndex, - key: { dataKey }, - }) - : this.simpleHeaderRenderer({ - ...headerProps, - })} - {columns[columnIndex].extra && columns[columnIndex].extra} - - ); - }; - } - - 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 { viewIndexToModel, rowGetter } = reorderIndex( - this.state.indexer, - this.state.indirectionVersion, - this.props.rows, - this.props.columns, - this.props.filter, - this.props.sort - ); - - const sizes = this.sizes(this.props.columns, this.props.rows, rowGetter); - const csvHeaders = this.csvHeaders(this.props.columns, this.props.exportCSVDataKeys); - - return ( -
- {this.props.enableExportCSV && ( -
- - - - - - -
- )} -
- - {({ height, width }) => this.makeSizedTable(height, width, sizes, viewIndexToModel, rowGetter)} - -
- {this.state.popoverAnchorEl && ( - - {this.makeColumnFilterEditor()} - - )} -
- ); - } -} - -const nestedGlobalSelectorsStyles = toNestedGlobalSelectors(defaultStyles, generateMuiVirtualizedTableClass); - -export const MuiVirtualizedTable = styled(MuiVirtualizedTableComponent)(nestedGlobalSelectorsStyles); diff --git a/src/components/notifications/hooks/useNotificationsListener.ts b/src/components/notifications/hooks/useNotificationsListener.ts index 4b80f448..f0279bc0 100644 --- a/src/components/notifications/hooks/useNotificationsListener.ts +++ b/src/components/notifications/hooks/useNotificationsListener.ts @@ -17,7 +17,7 @@ export const useNotificationsListener = ( listenerCallbackOnReopen, propsId, }: { - listenerCallbackMessage?: (event: MessageEvent) => void; + listenerCallbackMessage?: (event: MessageEvent) => void; listenerCallbackOnReopen?: () => void; propsId?: string; } diff --git a/src/components/treeViewFinder/TreeViewFinder.tsx b/src/components/treeViewFinder/TreeViewFinder.tsx index e3160048..7117d188 100644 --- a/src/components/treeViewFinder/TreeViewFinder.tsx +++ b/src/components/treeViewFinder/TreeViewFinder.tsx @@ -160,7 +160,7 @@ function TreeViewFinderComponant(props: TreeViewFinderProps) { // Controlled selected for TreeView const [selected, setSelected] = useState(defaultSelected ?? []); - const scrollRef = useRef([]); + const scrollRef = useRef<(HTMLLIElement | null)[]>([]); const [autoScrollAllowed, setAutoScrollAllowed] = useState(true); /* Utilities */ diff --git a/src/hooks/useSnackMessage.ts b/src/hooks/useSnackMessage.ts index 11ffb364..e71166d0 100644 --- a/src/hooks/useSnackMessage.ts +++ b/src/hooks/useSnackMessage.ts @@ -13,7 +13,7 @@ import { useIntlRef } from './useIntlRef'; interface SnackInputs extends Omit { messageTxt?: string; messageId?: string; - messageValues?: { [key: string]: string }; + messageValues?: Record; headerTxt?: string; headerId?: string; headerValues?: Record; @@ -26,13 +26,18 @@ export interface UseSnackMessageReturn { closeSnackbar: typeof closeSnackbarFromNotistack; } -function checkInputs(txt?: string, id?: string, values?: any) { +function checkInputs(txt?: string, id?: string, values?: SnackInputs['messageValues']) { if (txt && (id || values)) { console.warn('Snack inputs should be [*Txt] OR [*Id, *Values]'); } } -function checkAndTranslateIfNecessary(intlRef: MutableRefObject, txt?: string, id?: string, values?: any) { +function checkAndTranslateIfNecessary( + intlRef: MutableRefObject, + txt?: string, + id?: string, + values?: SnackInputs['messageValues'] +) { checkInputs(txt, id, values); return ( txt ?? diff --git a/src/module-mui.d.ts b/src/module-mui.d.ts index eb2d1037..b3063960 100644 --- a/src/module-mui.d.ts +++ b/src/module-mui.d.ts @@ -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/. */ +import '@mui/material/Switch'; // used to customize mui theme // https://mui.com/material-ui/customization/theming/#typescript @@ -19,3 +20,9 @@ declare module '@mui/material/styles/createTheme' { }; } } + +declare module '@mui/material/Switch' { + interface SwitchPropsSizeOverrides { + large: true; + } +} diff --git a/src/services/explore.ts b/src/services/explore.ts index 0e0fc8e5..d25268e4 100644 --- a/src/services/explore.ts +++ b/src/services/explore.ts @@ -8,14 +8,15 @@ import { UUID } from 'crypto'; import { backendFetch, backendFetchJson, getRequestParamFromList } from './utils'; import { ElementAttributes } from '../utils/types/types'; +import { NewFilterType } from '../components/filter/filter.type'; const PREFIX_EXPLORE_SERVER_QUERIES = `${import.meta.env.VITE_API_GATEWAY}/explore`; export function createFilter( - newFilter: any, + newFilter: NewFilterType, name: string, description: string, - parentDirectoryUuid?: UUID, + parentDirectoryUuid: UUID | undefined | null, token?: string ) { const urlSearchParams = new URLSearchParams(); @@ -35,7 +36,7 @@ export function createFilter( ); } -export function saveFilter(filter: any, name: string, description: string, token?: string) { +export function saveFilter(filter: NewFilterType, name: string, description: string, token?: string) { const urlSearchParams = new URLSearchParams(); urlSearchParams.append('name', name); urlSearchParams.append('description', description); diff --git a/src/services/utils.ts b/src/services/utils.ts index be4fd8df..2709af49 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -7,6 +7,13 @@ import { getUserToken } from '../redux/commonStore'; +export interface BackendFetchInit { + method: string; + headers?: HeadersInit; + body?: string | FormData; + signal?: AbortSignal; +} + const parseError = (text: string) => { try { return JSON.parse(text); @@ -15,7 +22,7 @@ const parseError = (text: string) => { } }; -const prepareRequest = (init: any, token?: string) => { +const prepareRequest = (init: BackendFetchInit, token?: string) => { if (!(typeof init === 'undefined' || typeof init === 'object')) { throw new TypeError(`First argument of prepareRequest is not an object : ${typeof init}`); } @@ -26,7 +33,7 @@ const prepareRequest = (init: any, token?: string) => { return initCopy; }; -const handleError = (response: any) => { +const handleError = (response: Response) => { return response.text().then((text: string) => { const errorName = 'HttpResponseError : '; const errorJson = parseError(text); @@ -38,22 +45,22 @@ const handleError = (response: any) => { customError.status = errorJson.status; } else { customError = new Error(`${errorName + response.status} ${response.statusText}, message : ${text}`); - customError.status = response.status; + customError.status = response.statusText; } throw customError; }); }; -const safeFetch = (url: string, initCopy: any) => { +const safeFetch = (url: string, initCopy: BackendFetchInit) => { return fetch(url, initCopy).then((response) => (response.ok ? response : handleError(response))); }; -export const backendFetch = (url: string, init: any, token?: string) => { +export const backendFetch = (url: string, init: BackendFetchInit, token?: string) => { const initCopy = prepareRequest(init, token); return safeFetch(url, initCopy); }; -export const backendFetchJson = (url: string, init: any, token?: string) => { +export const backendFetchJson = (url: string, init: BackendFetchInit, token?: string) => { const initCopy = prepareRequest(init, token); return safeFetch(url, initCopy).then((safeResponse) => (safeResponse.status === 204 ? null : safeResponse.json())); }; diff --git a/src/utils/algos.ts b/src/utils/algos.ts index 0d00e7d6..d1f04344 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) { +export function equalsArray(a: T[] | null, b: T[]) { if (b === a) { return true; } @@ -17,11 +17,14 @@ export function equalsArray(a: Array, b: Array) { } 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])) { + const newA = a[i]; + const newB = b[i]; + + if (newA instanceof Array && newB instanceof Array) { + if (!equalsArray(newA, newB)) { return false; } - } else if (a[i] !== b[i]) { + } else if (newA !== newB) { // Warning - two different object instances will never be equal: {x:20} != {x:20} return false; } diff --git a/src/utils/mapper/getFileIcon.tsx b/src/utils/mapper/getFileIcon.tsx index 930a71dd..d98807cf 100644 --- a/src/utils/mapper/getFileIcon.tsx +++ b/src/utils/mapper/getFileIcon.tsx @@ -35,6 +35,7 @@ export function getFileIcon(type: ElementType, style: SxProps) { case ElementType.LOADFLOW_PARAMETERS: case ElementType.SENSITIVITY_PARAMETERS: case ElementType.SHORT_CIRCUIT_PARAMETERS: + case ElementType.NETWORK_VISUALIZATIONS_PARAMETERS: return ; case ElementType.SPREADSHEET_CONFIG: return ; diff --git a/src/utils/types/elementType.ts b/src/utils/types/elementType.ts index 91494ac5..78493165 100644 --- a/src/utils/types/elementType.ts +++ b/src/utils/types/elementType.ts @@ -20,6 +20,7 @@ export enum ElementType { LOADFLOW_PARAMETERS = 'LOADFLOW_PARAMETERS', SENSITIVITY_PARAMETERS = 'SENSITIVITY_PARAMETERS', SHORT_CIRCUIT_PARAMETERS = 'SHORT_CIRCUIT_PARAMETERS', + NETWORK_VISUALIZATIONS_PARAMETERS = 'NETWORK_VISUALIZATIONS_PARAMETERS', SPREADSHEET_CONFIG = 'SPREADSHEET_CONFIG', SPREADSHEET_CONFIG_COLLECTION = 'SPREADSHEET_CONFIG_COLLECTION', } diff --git a/src/utils/types/types.ts b/src/utils/types/types.ts index 367d2cd6..477782d5 100644 --- a/src/utils/types/types.ts +++ b/src/utils/types/types.ts @@ -24,9 +24,14 @@ export type ElementAttributes = { lastModificationDate: string; lastModifiedBy: string; // id lastModifiedByLabel?: string; // enrich with user identity server - children: any[]; + children: ElementAttributes[]; parentUuid: null | UUID; - specificMetadata: Record; + specificMetadata: { + type: string; + equipmentType: string; + sheetType?: string; + format?: string; + }; uploading?: boolean; hasMetadata?: boolean; subtype?: string; diff --git a/vite.config.mts b/vite.config.mts index c9aa0f24..e0b5c936 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -7,16 +7,13 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; -import type { PluginOption } from 'vite'; // eslint-disable-line no-unused-vars import eslint from 'vite-plugin-eslint'; import svgr from 'vite-plugin-svgr'; import { libInjectCss } from 'vite-plugin-lib-inject-css'; import dts from 'vite-plugin-dts'; import { globSync } from 'glob'; import * as path from 'node:path'; -import * as fs from 'node:fs/promises'; import * as url from 'node:url'; -import { createRequire } from 'node:module'; export default defineConfig((config) => ({ plugins: [ @@ -26,7 +23,6 @@ export default defineConfig((config) => ({ lintOnStart: true, }), svgr(), // works on every import with the pattern "**/*.svg?react" - reactVirtualized(), libInjectCss(), dts({ include: ['src'], @@ -63,29 +59,3 @@ export default defineConfig((config) => ({ minify: false, // easier to debug on the apps using this lib }, })); - -// Workaround for react-virtualized with vite -// https://github.com/bvaughn/react-virtualized/issues/1632#issuecomment-1483966063 -function reactVirtualized(): PluginOption { - const WRONG_CODE = `import { bpfrpt_proptype_WindowScroller } from "../WindowScroller.js";`; - return { - name: 'flat:react-virtualized', - // Note: we cannot use the `transform` hook here - // because libraries are pre-bundled in vite directly, - // plugins aren't able to hack that step currently. - // so instead we manually edit the file in node_modules. - // all we need is to find the timing before pre-bundling. - configResolved: async () => { - const require = createRequire(import.meta.url); - const reactVirtualizedPath = require.resolve('react-virtualized'); - const { pathname: reactVirtualizedFilePath } = new url.URL(reactVirtualizedPath, import.meta.url); - const file = reactVirtualizedFilePath.replace( - path.join('dist', 'commonjs', 'index.js'), - path.join('dist', 'es', 'WindowScroller', 'utils', 'onScroll.js') - ); - const code = await fs.readFile(file, 'utf-8'); - const modified = code.replace(WRONG_CODE, ''); - await fs.writeFile(file, modified); - }, - }; -}