diff --git a/src/components/common/data/stdSimpleTable/StdSimpleTable.tsx b/src/components/common/data/stdSimpleTable/StdSimpleTable.tsx index 07ddddd..5a3d153 100644 --- a/src/components/common/data/stdSimpleTable/StdSimpleTable.tsx +++ b/src/components/common/data/stdSimpleTable/StdSimpleTable.tsx @@ -12,6 +12,7 @@ import { useReactTable, } from '@tanstack/react-table'; import TableCore, { TableCoreProps } from '../stdTable/TableCore'; +import { ReadOnlyFeature } from '@common/data/stdTable/features/readOnly.ts'; export type StdSimpleTableProps = { getCoreRowModel?: (table: Table) => () => RowModel; @@ -33,15 +34,18 @@ const StdSimpleTable = ({ columnResizeMode = undefined, enableRowSelection = false, enableMultiRowSelection = false, + enableReadOnly = false, ...tableOptions }: StdSimpleTableProps) => { const table = useReactTable({ + _features: [ReadOnlyFeature], columns, data, getCoreRowModel: getCustomCoreRowModel ?? getTstCoreRowModel(), columnResizeMode, enableRowSelection, enableMultiRowSelection, + enableReadOnly, ...tableOptions, }); diff --git a/src/components/common/data/stdTable/TableCore.tsx b/src/components/common/data/stdTable/TableCore.tsx index 5eaa8e5..f5d8704 100644 --- a/src/components/common/data/stdTable/TableCore.tsx +++ b/src/components/common/data/stdTable/TableCore.tsx @@ -119,18 +119,20 @@ const TableCore = ({ table, id: propId, striped, trClassName, columnSize - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - ))} + {table.getRowModel().rows.map((row) => { + return ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ); + })} ); diff --git a/src/components/common/data/stdTable/features/readOnly.ts b/src/components/common/data/stdTable/features/readOnly.ts index 15a3619..9bba237 100644 --- a/src/components/common/data/stdTable/features/readOnly.ts +++ b/src/components/common/data/stdTable/features/readOnly.ts @@ -4,8 +4,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { Row, RowData, Table, TableFeature } from '@tanstack/react-table'; -import { ReadOnlyOptions, ReadOnlyTableState } from '../types/readOnly.type'; +import { functionalUpdate, makeStateUpdater, Row, RowData, Table, TableFeature, Updater } from '@tanstack/react-table'; +import { ReadOnlyObject, ReadOnlyOptions, ReadOnlyTableState } from '../types/readOnly.type'; export const ReadOnlyFeature: TableFeature = { getInitialState: (state): ReadOnlyTableState => ({ @@ -13,9 +13,26 @@ export const ReadOnlyFeature: TableFeature = { ...state, }), - getDefaultOptions: (_table: Table): ReadOnlyOptions => ({}) as ReadOnlyOptions, + getDefaultOptions: (table: Table): ReadOnlyOptions => { + return { + enableReadOnly: false, + onReadOnlyChange: makeStateUpdater('readOnly', table), + } as ReadOnlyOptions; + }, + + createTable: (table: Table): void => { + table.setReadOnly = (updater) => { + const safeUpdater: Updater = (old) => { + return functionalUpdate(updater, old); + }; + return table.options.onReadOnlyChange?.(safeUpdater); + }; + // table.toggleReadOnly = value => { + // table.setReadOnly(old => !old); + // }; + }, createRow: (row: Row, table: Table): void => { - row.getReadOnly = () => !!table.getState().readOnly[row.id]; + row.getReadOnly = () => table.getState().readOnly[row.id]; }, }; diff --git a/src/components/common/data/stdTable/tableCoreRowClassBuilder.ts b/src/components/common/data/stdTable/tableCoreRowClassBuilder.ts index a8bcea6..31ee8f8 100644 --- a/src/components/common/data/stdTable/tableCoreRowClassBuilder.ts +++ b/src/components/common/data/stdTable/tableCoreRowClassBuilder.ts @@ -6,9 +6,10 @@ import clsx from 'clsx'; -export const STRIPED_CLASSSES = 'even:bg-primary-200'; -export const SELECTED_ROW_CLASSSES = 'bg-primary-100'; -export const READONLY_ROW_CLASSSES = 'bg-gray-100'; +export const STRIPED_CLASSES = 'even:bg-primary-200'; +export const SELECTED_ROW_CLASSES = 'bg-primary-100'; +export const READONLY_ROW_CLASSES = 'pointer-events-none bg-gray-100 [&_button]:bg-gray-400 [&_button]:border-gray-400'; +export const READONLY_SELECTED_ROW_CLASSES = 'hover:bg-gray-100'; export const tableCoreRowClassBuilder = ( isStriped?: boolean, @@ -19,10 +20,10 @@ export const tableCoreRowClassBuilder = ( clsx( { group: true, - [STRIPED_CLASSSES]: isStriped, - [SELECTED_ROW_CLASSSES]: isSelected, - [READONLY_ROW_CLASSSES]: isReadOnly, - 'hover:bg-gray-100': !isReadOnly && !isSelected, + [STRIPED_CLASSES]: isStriped, + [SELECTED_ROW_CLASSES]: isSelected, + [READONLY_ROW_CLASSES]: isReadOnly, + [READONLY_SELECTED_ROW_CLASSES]: !isReadOnly && !isSelected, }, trClassNames, ); diff --git a/src/components/common/data/stdTable/tests/tableCoreRowClassBuilder.test.ts b/src/components/common/data/stdTable/tests/tableCoreRowClassBuilder.test.ts index a140288..e35b44e 100644 --- a/src/components/common/data/stdTable/tests/tableCoreRowClassBuilder.test.ts +++ b/src/components/common/data/stdTable/tests/tableCoreRowClassBuilder.test.ts @@ -5,9 +5,10 @@ */ import { - READONLY_ROW_CLASSSES, - SELECTED_ROW_CLASSSES, - STRIPED_CLASSSES, + READONLY_ROW_CLASSES, + READONLY_SELECTED_ROW_CLASSES, + SELECTED_ROW_CLASSES, + STRIPED_CLASSES, tableCoreRowClassBuilder, } from '../tableCoreRowClassBuilder'; @@ -15,18 +16,22 @@ const TEST_TR_CLASSES = 'bg-acc1-500 text-caption'; describe('tableCoreRowClassBuilder function', () => { it('should have the expected striped classes', () => { - expect(tableCoreRowClassBuilder(true).includes(STRIPED_CLASSSES)).toBe(true); + expect(tableCoreRowClassBuilder(true).includes(STRIPED_CLASSES)).toBe(true); }); it('should have the expected selected classes', () => { - expect(tableCoreRowClassBuilder(false, true).includes(SELECTED_ROW_CLASSSES)).toBe(true); + expect(tableCoreRowClassBuilder(false, true).includes(SELECTED_ROW_CLASSES)).toBe(true); }); it('should have the expected readonly classes', () => { - expect(tableCoreRowClassBuilder(false, false, true).includes(READONLY_ROW_CLASSSES)).toBe(true); + expect(tableCoreRowClassBuilder(false, false, true).includes(READONLY_ROW_CLASSES)).toBe(true); }); it('should have the expected additional "tr" classes', () => { - expect(tableCoreRowClassBuilder(false, false, false, TEST_TR_CLASSES).includes(TEST_TR_CLASSES)).toBe(true); + expect( + tableCoreRowClassBuilder(false, false, false, READONLY_SELECTED_ROW_CLASSES).includes( + READONLY_SELECTED_ROW_CLASSES, + ), + ).toBe(true); }); }); diff --git a/src/components/common/data/stdTable/types/readOnly.type.d.ts b/src/components/common/data/stdTable/types/readOnly.type.d.ts index 5e10a5b..bb595ff 100644 --- a/src/components/common/data/stdTable/types/readOnly.type.d.ts +++ b/src/components/common/data/stdTable/types/readOnly.type.d.ts @@ -6,16 +6,23 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-empty-object-type */ +import { OnChangeFn, Updater } from '@tanstack/react-table'; + export type ReadOnlyObject = Record; + export interface ReadOnlyTableState { readOnly: ReadOnlyObject; } -export interface ReadOnlyOptions {} +export interface ReadOnlyOptions { + enableReadOnly?: boolean; + onReadOnlyChange?: OnChangeFn; +} // Define types for our new feature's table APIs export interface ReadOnlyTableInstance { setReadOnly: (updater: Updater) => void; + toggleReadOnly: (value?: ReadOnlyObject) => void; } export interface ReadOnlyRow { @@ -25,7 +32,10 @@ export interface ReadOnlyRow { declare module '@tanstack/react-table' { interface TableState extends ReadOnlyTableState {} + interface TableOptionsResolved extends ReadOnlyOptions {} + interface Table extends ReadOnlyTableInstance {} + interface Row extends ReadOnlyRow {} } diff --git a/src/pages/pegase/studies/studyDetails/AreaLinkTab.tsx b/src/pages/pegase/studies/studyDetails/AreaLinkTab.tsx index beca7fe..c60297c 100644 --- a/src/pages/pegase/studies/studyDetails/AreaLinkTab.tsx +++ b/src/pages/pegase/studies/studyDetails/AreaLinkTab.tsx @@ -4,76 +4,81 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import {useState} from 'react'; -import {ColumnDef} from '@tanstack/react-table'; +import { useState } from 'react'; +import { ColumnDef } from '@tanstack/react-table'; import StdSimpleTable from '@common/data/stdSimpleTable/StdSimpleTable'; -import {RdsButton, RdsInputText} from "rte-design-system-react"; - +import { RdsButton, RdsInputText } from 'rte-design-system-react'; +import { ReadOnlyObject } from '@common/data/stdTable/types/readOnly.type'; type RowStatus = 'Missing' | 'OK' | 'Error'; type RowData = { - hypothesis: string; - trajectory: string | null; // Null si aucune trajectoire n'est sélectionnée - status: RowStatus; + hypothesis: string; + trajectory: string | null; // Null si aucune trajectoire n'est sélectionnée + status: RowStatus; }; const AreaLinkTab = () => { - const [data, setData] = useState([ - {hypothesis: 'Areas', trajectory: null, status: 'Missing'}, - {hypothesis: 'Links', trajectory: null, status: 'Missing'}, - ]); + const [data, setData] = useState([ + { hypothesis: 'Areas', trajectory: null, status: 'Missing' }, + { hypothesis: 'Links', trajectory: null, status: 'Missing' }, + ]); + const [readOnly, _] = useState({ '0': false, '1': !data[0].trajectory }); - const handleImport = (index: number) => { - const updatedData = [...data]; - updatedData[index].trajectory = 'BP_23_ref'; - updatedData[index].status = 'OK'; - setData(updatedData); - }; + const handleImport = (index: number) => { + const updatedData = [...data]; + updatedData[index].trajectory = 'BP_23_ref'; + updatedData[index].status = 'OK'; + setData(updatedData); + }; - const columns: ColumnDef[] = [ - { - header: 'Hypothesis', - accessorKey: 'hypothesis', - }, - { - header: 'Trajectory to use', - cell: ({row}) => { - const index = row.index; - const {trajectory} = row.original; - return trajectory ? ( -
- {trajectory} -
- ) : ( -
- - or - handleImport(index)}/> -
- ); - }, - }, - { - header: 'Status', - cell: ({row}) => { - const {status} = row.original; - if (status === 'Missing') return ❓ Missing; - if (status === 'OK') return ✔ OK; - if (status === 'Error') return ❌ Error; - return null; - }, - }, - ]; + const columns: ColumnDef[] = [ + { + header: 'Hypothesis', + accessorKey: 'hypothesis', + }, + { + header: 'Trajectory to use', + cell: ({ row }) => { + const index = row.index; + const { trajectory } = row.original; + return trajectory ? ( +
+ {trajectory} +
+ ) : ( +
+ + or + handleImport(index)} /> +
+ ); + }, + }, + { + header: 'Status', + cell: ({ row }) => { + const { status } = row.original; + if (status === 'Missing') return ❓ Missing; + if (status === 'OK') return ✔ OK; + if (status === 'Error') return ❌ Error; + return null; + }, + }, + ]; - return ( -
- -
- ); + return ( +
+ +
+ ); }; export default AreaLinkTab; diff --git a/src/pages/pegase/studies/studyDetails/StudyNavigationMenu.tsx b/src/pages/pegase/studies/studyDetails/StudyNavigationMenu.tsx index 2bf70e8..cc0b0d7 100644 --- a/src/pages/pegase/studies/studyDetails/StudyNavigationMenu.tsx +++ b/src/pages/pegase/studies/studyDetails/StudyNavigationMenu.tsx @@ -1,71 +1,78 @@ -import React, {useEffect, useState} from 'react'; -import {RdsTabItem} from 'rte-design-system-react'; -import {StdIconId} from "@/shared/utils/common/mappings/iconMaps"; -import LoadTab from "@/pages/pegase/studies/studyDetails/LoadTab"; -import ThermalTab from "@/pages/pegase/studies/studyDetails/ThermalTab"; -import EnrTab from "@/pages/pegase/studies/studyDetails/EnrTab"; -import MiscTab from "@/pages/pegase/studies/studyDetails/MiscLinkTab"; -import AreaLinkTab from "@/pages/pegase/studies/studyDetails/AreaLinkTab"; -import StdIcon from "@common/base/stdIcon/StdIcon"; +/* + * 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 https://mozilla.org/MPL/2.0/. + */ -const StudyNavigationMenu = ({onRenderActiveComponent}: { onRenderActiveComponent?: (content: React.ReactNode) => void }) => { - const [activeTab, setActiveTab] = useState('areasAndLinks'); +import React, { ReactNode, useEffect, useState } from 'react'; +import { RdsTabItem } from 'rte-design-system-react'; +import { StdIconId } from '@/shared/utils/common/mappings/iconMaps'; +import LoadTab from '@/pages/pegase/studies/studyDetails/LoadTab'; +import ThermalTab from '@/pages/pegase/studies/studyDetails/ThermalTab'; +import EnrTab from '@/pages/pegase/studies/studyDetails/EnrTab'; +import MiscTab from '@/pages/pegase/studies/studyDetails/MiscLinkTab'; +import AreaLinkTab from '@/pages/pegase/studies/studyDetails/AreaLinkTab'; +import StdIcon from '@common/base/stdIcon/StdIcon'; - const renderActiveComponent = () => { - switch (activeTab) { - case 'areasAndLinks': - return ; - case 'load': - return ; - case 'thermal': - return ; - case 'enr': - return ; - case 'misc': - return ; - default: - return null; - } - }; +const StudyNavigationMenu = ({ + onRenderActiveComponent, +}: { + onRenderActiveComponent?: (content: ReactNode | null) => void; +}) => { + const [activeTab, setActiveTab] = useState('areasAndLinks'); - useEffect(() => { - if (onRenderActiveComponent) { - onRenderActiveComponent(renderActiveComponent()); - } - }, [activeTab, onRenderActiveComponent]); + const renderActiveComponent = (): ReactNode | null => { + switch (activeTab) { + case 'areasAndLinks': + return ; + case 'load': + return ; + case 'thermal': + return ; + case 'enr': + return ; + case 'misc': + return ; + default: + return null; + } + }; - const handleTabClick = (selectedItemName: string) => { - setActiveTab(selectedItemName); - console.log(`Tab clicked: ${selectedItemName}`); - }; + useEffect(() => { + if (onRenderActiveComponent) { + onRenderActiveComponent(renderActiveComponent()); + } + }, [activeTab, onRenderActiveComponent]); - const tabs = [ - {name: 'areasAndLinks', label: 'Areas & Links', icon: StdIconId.LinkedServices}, - {name: 'load', label: 'Load', icon: StdIconId.BatteryChargingFull}, - {name: 'thermal', label: 'Thermal', icon: StdIconId.LocalFireDepartment}, - {name: 'enr', label: 'ENR', icon: StdIconId.EnergySavingsLeaf}, - {name: 'misc', label: 'Misc', icon: StdIconId.Category}, - ]; + const handleTabClick = (selectedItemName: string) => { + setActiveTab(selectedItemName); + console.log(`Tab clicked: ${selectedItemName}`); + }; - return ( -
-
- {tabs.map((tab) => ( -
- - handleTabClick(tab.name)} - /> -
+ const tabs = [ + { name: 'areasAndLinks', label: 'Areas & Links', icon: StdIconId.LinkedServices }, + { name: 'load', label: 'Load', icon: StdIconId.BatteryChargingFull }, + { name: 'thermal', label: 'Thermal', icon: StdIconId.LocalFireDepartment }, + { name: 'enr', label: 'ENR', icon: StdIconId.EnergySavingsLeaf }, + { name: 'misc', label: 'Misc', icon: StdIconId.Category }, + ]; - ))} -
+ return ( +
+ {tabs.map((tab) => ( +
+ + handleTabClick(tab.name)} + />
- ); + ))} +
+ ); }; -export default StudyNavigationMenu; \ No newline at end of file +export default StudyNavigationMenu; diff --git a/src/pages/pegase/studies/studyDetails/areaLinkTable.tsx b/src/pages/pegase/studies/studyDetails/areaLinkTable.tsx deleted file mode 100644 index 0faa666..0000000 --- a/src/pages/pegase/studies/studyDetails/areaLinkTable.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 https://mozilla.org/MPL/2.0/. - */ - -import { useState } from 'react'; -import { ColumnDef } from '@tanstack/react-table'; -import StdSimpleTable from '@common/data/stdSimpleTable/StdSimpleTable'; -import { RdsButton, RdsIconId } from 'rte-design-system-react'; - -type RowStatus = 'Missing' | 'OK' | 'Error'; - -type RowData = { - hypothesis: string; - trajectory: string | null; // Null si aucune trajectoire n'est sélectionnée - status: RowStatus; -}; - -const AreaLinkTable = () => { - const [data, setData] = useState([ - { hypothesis: 'Areas', trajectory: null, status: 'Missing' }, - { hypothesis: 'Links', trajectory: null, status: 'Missing' }, - ]); - - const handleImport = (index: number) => { - const updatedData = [...data]; - updatedData[index].trajectory = 'BP_23_ref'; - updatedData[index].status = 'OK'; - setData(updatedData); - }; - - const handleDelete = (index: number) => { - const updatedData = [...data]; - updatedData[index].trajectory = null; - updatedData[index].status = 'Missing'; - setData(updatedData); - }; - - const columns: ColumnDef[] = [ - { - header: 'Hypothesis', - accessorKey: 'hypothesis', - }, - { - header: 'Trajectory to use', - cell: ({ row }) => { - const index = row.index; - const { trajectory } = row.original; - return trajectory ? ( -
- {trajectory} - handleDelete(index)} /> -
- ) : ( -
- - or - handleImport(index)} size="medium" /> -
- ); - }, - }, - { - header: 'Status', - cell: ({ row }) => { - const { status } = row.original; - if (status === 'Missing') return ❓ Missing; - if (status === 'OK') return ✔ OK; - if (status === 'Error') return ❌ Error; - return null; - }, - }, - ]; - - return ( -
- -
- ); -}; - -export default AreaLinkTable;