From 3e4a683711ac73f355fcd854ee16685d25921fb6 Mon Sep 17 00:00:00 2001 From: Enrique Moreno Date: Wed, 26 Feb 2025 12:58:42 +0100 Subject: [PATCH 1/3] Fixed problem with empty selectable datagrid --- packages/lib/src/data-grid/DataGrid.stories.tsx | 11 +++++++++++ packages/lib/src/data-grid/utils.tsx | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/data-grid/DataGrid.stories.tsx b/packages/lib/src/data-grid/DataGrid.stories.tsx index 5928392240..87af151eb1 100644 --- a/packages/lib/src/data-grid/DataGrid.stories.tsx +++ b/packages/lib/src/data-grid/DataGrid.stories.tsx @@ -721,6 +721,17 @@ const DataGrid = () => { + + + <DxcDataGrid + columns={columns} + rows={[]} + uniqueRowId="id" + selectable + selectedRows={selectedChildRows} + onSelectRows={setSelectedChildRows} + /> + </ExampleContainer> <ExampleContainer> <Title title="Controlled Rows" theme="light" level={4} /> <DxcDataGrid diff --git a/packages/lib/src/data-grid/utils.tsx b/packages/lib/src/data-grid/utils.tsx index 7c08c2df88..ca2026c352 100644 --- a/packages/lib/src/data-grid/utils.tsx +++ b/packages/lib/src/data-grid/utils.tsx @@ -244,7 +244,7 @@ export const renderHeaderCheckbox = ( ) => ( <HalstackProvider advancedTheme={overwriteTheme(colorsTheme)}> <DxcCheckbox - checked={!rows.some((row) => !selectedRows.has(rowKeyGetter(row, uniqueRowId)))} + checked={rows.length > 0 && !rows.some((row) => !selectedRows.has(rowKeyGetter(row, uniqueRowId)))} onChange={(checked) => { const updatedSelection = new Set(selectedRows); @@ -601,7 +601,7 @@ export const getPaginatedNodes = ( * @param {string} key - The key to check if it exists in the row object. * @param {T} obj - The row object to check the key against. * @returns {boolean} - Returns `true` if `key` is a valid key of `obj`, otherwise `false`. - * + * */ export const isKeyOfRow = <T extends GridRow>(key: string, obj: T): key is Extract<keyof T, string> => { return key in obj; From f90a47ebd89572d3f9da6d2c58f80b49b3cd5b4e Mon Sep 17 00:00:00 2001 From: Enrique Moreno <enrique.moreno@dxc.com> Date: Wed, 26 Feb 2025 15:25:12 +0100 Subject: [PATCH 2/3] Added improvement to disable interaction with rows with an invalid uniqueRowId --- .../lib/src/data-grid/DataGrid.stories.tsx | 141 ++++++++++++++++++ packages/lib/src/data-grid/DataGrid.tsx | 42 +++--- packages/lib/src/data-grid/utils.tsx | 51 ++++--- 3 files changed, 194 insertions(+), 40 deletions(-) diff --git a/packages/lib/src/data-grid/DataGrid.stories.tsx b/packages/lib/src/data-grid/DataGrid.stories.tsx index 87af151eb1..5c3f1c41c6 100644 --- a/packages/lib/src/data-grid/DataGrid.stories.tsx +++ b/packages/lib/src/data-grid/DataGrid.stories.tsx @@ -933,6 +933,143 @@ const DataGridSortedExpandable = () => { ); }; +const DataGridUnknownUniqueRowId = () => { + const [selectedRows, setSelectedRows] = useState((): Set<number | string> => new Set()); + const [selectedChildRows, setSelectedChildRows] = useState((): Set<number | string> => new Set()); + + const [itemsPerPage, setItemsPerPage] = useState(5); + const [rowsControlled, setRowsControlled] = useState(expandableRows.slice(0, itemsPerPage)); + const [page, setPage] = useState(0); + + return ( + <> + <ExampleContainer> + <Title title="Default" theme="light" level={4} /> + <DxcDataGrid columns={columns} rows={expandableRows} uniqueRowId="error" /> + </ExampleContainer> + <ExampleContainer> + <Title title="Expandable" theme="light" level={4} /> + <DxcDataGrid columns={columns} rows={expandableRows} uniqueRowId="error" expandable /> + </ExampleContainer> + <ExampleContainer> + <Title title="Selectable" theme="light" level={4} /> + <DxcDataGrid + columns={columns} + rows={expandableRows} + uniqueRowId="error" + selectable + selectedRows={selectedRows} + onSelectRows={setSelectedRows} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Selectable & expandable" theme="light" level={4} /> + <DxcDataGrid + columns={columns} + rows={expandableRows} + uniqueRowId="error" + expandable + selectable + selectedRows={selectedRows} + onSelectRows={setSelectedRows} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="DataGrid with children" theme="light" level={4} /> + <DxcDataGrid columns={childcolumns} rows={childRows} uniqueRowId="error" /> + </ExampleContainer> + <ExampleContainer> + <Title title="DataGrid with children" theme="light" level={4} /> + <DxcDataGrid + columns={childcolumns} + rows={childRows} + uniqueRowId="error" + selectable + selectedRows={selectedChildRows} + onSelectRows={setSelectedChildRows} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Summary row" theme="light" level={4} /> + <DxcDataGrid + columns={columns} + rows={expandableRows} + summaryRow={{ label: "Total", total: 100 }} + uniqueRowId="error" + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Scrollable Data Grid" theme="light" level={4} /> + <DxcContainer height="250px"> + <DxcDataGrid columns={columns} rows={expandableRows} uniqueRowId="error" /> + </DxcContainer> + </ExampleContainer> + <ExampleContainer> + <Title title="Empty Data Grid" theme="light" level={4} /> + <DxcDataGrid + columns={columns} + rows={[]} + uniqueRowId="error" + selectable + selectedRows={selectedChildRows} + onSelectRows={setSelectedChildRows} + /> + </ExampleContainer> + <ExampleContainer> + <Title title="Controlled Rows" theme="light" level={4} /> + <DxcDataGrid + columns={columns} + rows={rowsControlled} + uniqueRowId="error" + showPaginator + onSort={(sortColumn) => { + if (sortColumn) { + const { columnKey, direction } = sortColumn; + console.log(`Sorting the column '${columnKey}' by '${direction}' direction`); + setRowsControlled((currentRows) => { + return currentRows.sort((a, b) => { + if (isKeyOfRow(columnKey, a) && isKeyOfRow(columnKey, b)) { + const valueA = a[columnKey]; + const valueB = b[columnKey]; + if (valueA != null && valueB != null) { + if (direction === "ASC") { + return valueA < valueB ? -1 : valueA > valueB ? 1 : 0; + } else { + return valueA < valueB ? 1 : valueA > valueB ? -1 : 0; + } + } else { + return 0; + } + } else { + return 0; + } + }); + }); + } else { + console.log("Removed sorting criteria"); + setRowsControlled(expandableRows.slice(page * itemsPerPage, page * itemsPerPage + itemsPerPage)); + } + }} + onPageChange={(page) => { + const internalPage = page - 1; + setPage(internalPage); + setRowsControlled( + expandableRows.slice(internalPage * itemsPerPage, internalPage * itemsPerPage + itemsPerPage) + ); + }} + itemsPerPage={itemsPerPage} + itemsPerPageOptions={[5, 10]} + itemsPerPageFunction={(n) => { + setItemsPerPage(n); + setRowsControlled(expandableRows.slice(0, n)); + }} + totalItems={expandableRows.length} + /> + </ExampleContainer> + </> + ); +}; + type Story = StoryObj<typeof DxcDataGrid>; export const Chromatic: Story = { @@ -1002,3 +1139,7 @@ export const DataGridSortedExpanded: Story = { button37 && (await userEvent.click(button37)); }, }; + +export const UnknownUniqueId: Story = { + render: DataGridUnknownUniqueRowId, +}; \ No newline at end of file diff --git a/packages/lib/src/data-grid/DataGrid.tsx b/packages/lib/src/data-grid/DataGrid.tsx index 4caf502637..ea02a53dce 100644 --- a/packages/lib/src/data-grid/DataGrid.tsx +++ b/packages/lib/src/data-grid/DataGrid.tsx @@ -197,26 +197,30 @@ const DxcDataGrid = ({ const sortedRows = useMemo((): readonly GridRow[] | HierarchyGridRow[] | ExpandableGridRow[] => { const sortFunctions = getCustomSortFn(columns); if (!onSort) { - if (expandable && sortColumns.length > 0) { - const innerSortedRows = sortRows( - rowsToRender.filter((row) => !row.isExpandedChildContent), - sortColumns, - sortFunctions - ); - rowsToRender - .filter((row) => row.isExpandedChildContent) - .map((expandedRow) => - addRow( - innerSortedRows, - innerSortedRows.findIndex((trigger) => rowKeyGetter(trigger, uniqueRowId) === expandedRow.triggerRowKey) + - 1, - expandedRow - ) + if (sortColumns.length > 0 && uniqueRowId) { + if (expandable) { + const innerSortedRows = sortRows( + rowsToRender.filter((row) => !row.isExpandedChildContent), + sortColumns, + sortFunctions ); - return innerSortedRows; - } - if (!expandable && sortColumns.length > 0 && uniqueRowId) { - return sortHierarchyRows(rowsToRender, sortColumns, sortFunctions, uniqueRowId); + if (innerSortedRows.some((row) => uniqueRowId in row)) { + rowsToRender + .filter((row) => row.isExpandedChildContent) + .map((expandedRow) => + addRow( + innerSortedRows, + innerSortedRows.findIndex( + (trigger) => rowKeyGetter(trigger, uniqueRowId) === expandedRow.triggerRowKey + ) + 1, + expandedRow + ) + ); + return innerSortedRows; + } + } else { + return sortHierarchyRows(rowsToRender, sortColumns, sortFunctions, uniqueRowId); + } } } return rowsToRender; diff --git a/packages/lib/src/data-grid/utils.tsx b/packages/lib/src/data-grid/utils.tsx index ca2026c352..3d1489e2d6 100644 --- a/packages/lib/src/data-grid/utils.tsx +++ b/packages/lib/src/data-grid/utils.tsx @@ -122,6 +122,7 @@ export const renderExpandableTrigger = ( }); } }} + disabled={!rows.some((row) => uniqueRowId in row)} /> ); @@ -143,6 +144,7 @@ export const renderHierarchyTrigger = ( ) => ( <button type="button" + disabled={!rows.some((row) => uniqueRowId in row)} onClick={() => { let newRowsToRender = [...rows]; if (!triggerRow.visibleChildren) { @@ -205,26 +207,29 @@ export const renderCheckbox = ( uniqueRowId: string, selectedRows: Set<string | number>, onSelectRows: (_selected: Set<string | number>) => void -) => ( - <DxcCheckbox - checked={selectedRows.has(rowKeyGetter(row, uniqueRowId))} - onChange={(checked) => { - const selected = new Set(selectedRows); - if (checked) { - selected.add(rowKeyGetter(row, uniqueRowId)); - } else { - selected.delete(rowKeyGetter(row, uniqueRowId)); - } - if (row.childRows && Array.isArray(row.childRows)) { - getChildrenSelection(row.childRows, uniqueRowId, selected, checked); - } - if (row.parentKey) { - getParentSelectedState(rows, row.parentKey, uniqueRowId, selected, checked); - } - onSelectRows(selected); - }} - /> -); +) => { + return ( + <DxcCheckbox + checked={selectedRows.has(rowKeyGetter(row, uniqueRowId))} + onChange={(checked) => { + const selected = new Set(selectedRows); + if (checked) { + selected.add(rowKeyGetter(row, uniqueRowId)); + } else { + selected.delete(rowKeyGetter(row, uniqueRowId)); + } + if (row.childRows && Array.isArray(row.childRows)) { + getChildrenSelection(row.childRows, uniqueRowId, selected, checked); + } + if (row.parentKey) { + getParentSelectedState(rows, row.parentKey, uniqueRowId, selected, checked); + } + onSelectRows(selected); + }} + disabled={!rows.some((row) => uniqueRowId in row)} + /> + ); +}; /** * Renders a header checkbox that controls the selection of all rows. @@ -266,6 +271,7 @@ export const renderHeaderCheckbox = ( onSelectRows(updatedSelection); }} + disabled={rows.length === 0 || !rows.some((row) => uniqueRowId in row)} /> </HalstackProvider> ); @@ -384,7 +390,7 @@ export const sortHierarchyRows = ( ); // add children directly under the parent if it is available while (sortedChildren.length) { - if (uniqueRowId) { + if (uniqueRowId && sortedChildren.some((row) => uniqueRowId in row)) { sortedChildren = sortedChildren.reduce( ( remainingChilds: GridRow[] | HierarchyGridRow[] | ExpandableGridRow[], @@ -498,6 +504,9 @@ export const getParentSelectedState = ( selectedRows: Set<ReactNode>, checkedStateToMatch: boolean ) => { + if (!rowList.some((row) => uniqueRowId in row)) { + return; + } const parentRow = rowFinderBasedOnId(rowList, uniqueRowId, parentKeyValue) as HierarchyGridRow; if (!parentRow) { From 63d9d477774390795d03c45bc0d0342bea11c16e Mon Sep 17 00:00:00 2001 From: Enrique Moreno <enrique.moreno@dxc.com> Date: Mon, 10 Mar 2025 11:50:10 +0100 Subject: [PATCH 3/3] Added inline return --- packages/lib/src/data-grid/utils.tsx | 44 +++++++++++++--------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/lib/src/data-grid/utils.tsx b/packages/lib/src/data-grid/utils.tsx index 3d1489e2d6..f10bde38dc 100644 --- a/packages/lib/src/data-grid/utils.tsx +++ b/packages/lib/src/data-grid/utils.tsx @@ -207,29 +207,27 @@ export const renderCheckbox = ( uniqueRowId: string, selectedRows: Set<string | number>, onSelectRows: (_selected: Set<string | number>) => void -) => { - return ( - <DxcCheckbox - checked={selectedRows.has(rowKeyGetter(row, uniqueRowId))} - onChange={(checked) => { - const selected = new Set(selectedRows); - if (checked) { - selected.add(rowKeyGetter(row, uniqueRowId)); - } else { - selected.delete(rowKeyGetter(row, uniqueRowId)); - } - if (row.childRows && Array.isArray(row.childRows)) { - getChildrenSelection(row.childRows, uniqueRowId, selected, checked); - } - if (row.parentKey) { - getParentSelectedState(rows, row.parentKey, uniqueRowId, selected, checked); - } - onSelectRows(selected); - }} - disabled={!rows.some((row) => uniqueRowId in row)} - /> - ); -}; +) => ( + <DxcCheckbox + checked={selectedRows.has(rowKeyGetter(row, uniqueRowId))} + onChange={(checked) => { + const selected = new Set(selectedRows); + if (checked) { + selected.add(rowKeyGetter(row, uniqueRowId)); + } else { + selected.delete(rowKeyGetter(row, uniqueRowId)); + } + if (row.childRows && Array.isArray(row.childRows)) { + getChildrenSelection(row.childRows, uniqueRowId, selected, checked); + } + if (row.parentKey) { + getParentSelectedState(rows, row.parentKey, uniqueRowId, selected, checked); + } + onSelectRows(selected); + }} + disabled={!rows.some((row) => uniqueRowId in row)} + /> +); /** * Renders a header checkbox that controls the selection of all rows.