From 5566be9f684b83b9cbf5b76736019fd6a57a3202 Mon Sep 17 00:00:00 2001 From: Victor Bayas Date: Thu, 22 Aug 2024 20:19:46 -0500 Subject: [PATCH] Use generics in DataTable (#931) * Use generics in DataTable * Use explicit renderFunction and renderFullObjectFunction * Type actions * Update StoryBook stories --- src/components/DataTable/ColumnsSelector.tsx | 31 +- .../DataTable/DataTable.stories.tsx | 401 ++++++------------ src/components/DataTable/DataTable.tsx | 93 ++-- src/components/DataTable/DataTable.types.ts | 58 +-- src/components/DataTable/DataTable.utils.tsx | 103 +++-- .../DataTable/TableActionButton.tsx | 28 +- 6 files changed, 270 insertions(+), 444 deletions(-) diff --git a/src/components/DataTable/ColumnsSelector.tsx b/src/components/DataTable/ColumnsSelector.tsx index 6b72f438..8a384dbc 100644 --- a/src/components/DataTable/ColumnsSelector.tsx +++ b/src/components/DataTable/ColumnsSelector.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { FC, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import styled, { CSSObject } from "styled-components"; import get from "lodash/get"; import debounce from "lodash/debounce"; @@ -38,7 +38,7 @@ const SelectorBox = styled.div( backgroundColor: get( theme, "dropdownSelector.backgroundColor", - lightColors.white, + lightColors.white ), border: `1px solid ${get(theme, "borderColor", lightColors.borderColor)}`, padding: "10px 10px", @@ -53,7 +53,7 @@ const SelectorBox = styled.div( borderBottom: `1px solid ${get( theme, "borderColor", - lightColors.borderColor, + lightColors.borderColor )}`, marginBottom: 5, color: get(theme, "fontColor", lightColors.defaultFontColor), @@ -66,7 +66,7 @@ const SelectorBox = styled.div( overflowY: "auto", }, ...overridePropsParse(sx, theme), - }), + }) ); const calcElementPosition = (anchorEl: (EventTarget & HTMLElement) | null) => { @@ -86,14 +86,14 @@ const calcElementPosition = (anchorEl: (EventTarget & HTMLElement) | null) => { }; }; -const ColumnsSelector: FC = ({ +const ColumnsSelector = ({ columns, selectedOptionIDs, onSelect, closeTriggerAction, open, anchorEl = null, -}) => { +}: ColumnSelectorProps): JSX.Element | null => { const [coords, setCoords] = useState(null); useEffect(() => { @@ -120,7 +120,14 @@ const ColumnsSelector: FC = ({ window.addEventListener("scroll", () => { scrollResize(anchorEl); }); - }); + + return () => { + window.removeEventListener("resize", handleResize); + window.removeEventListener("scroll", () => { + scrollResize(anchorEl); + }); + }; + }, [anchorEl, closeTriggerAction]); if (!open || !coords) { return null; @@ -128,7 +135,7 @@ const ColumnsSelector: FC = ({ if (!anchorEl) { console.warn( - "AnchorEl not set. Element will be rendered on the top of the page", + "AnchorEl not set. Element will be rendered on the top of the page" ); } @@ -143,18 +150,18 @@ const ColumnsSelector: FC = ({ > Shown Columns - {columns.map((column: IColumns) => { + {columns.map((column: IColumns) => { return ( element === column.elementKey, + (element) => element === column.elementKey ) >= 0 } onChange={() => { - onSelect(column.elementKey || ""); + onSelect((column.elementKey as keyof T) || ("" as keyof T)); }} id={`chbox-${column.label}`} name={`chbox-${column.label}`} @@ -165,7 +172,7 @@ const ColumnsSelector: FC = ({ , - document.body, + document.body ); }; diff --git a/src/components/DataTable/DataTable.stories.tsx b/src/components/DataTable/DataTable.stories.tsx index 8ced64eb..f62db2f0 100644 --- a/src/components/DataTable/DataTable.stories.tsx +++ b/src/components/DataTable/DataTable.stories.tsx @@ -23,26 +23,40 @@ import GlobalStyles from "../GlobalStyles/GlobalStyles"; import Grid from "../Grid/Grid"; import CheckIcon from "../Icons/NewDesignIcons/CheckIcon"; +// Define the structure of the records +type RecordType = { + id?: number; + field1: string; + field2?: string; + field3?: string; +}; + export default { title: "MDS/Information/DataTable", - component: DataTable, + component: DataTable as unknown as React.ComponentType< + DataTableProps + >, argTypes: {}, -} as Meta; +} as Meta; -const Template: Story = (args) => { - const [selected, setSelected] = useState([]); - const [selectedColumns, setSelectedColumns] = useState(["field1"]); +const Template: Story> = (args) => { + const [selected, setSelected] = useState>( + [] + ); + const [selectedColumns, setSelectedColumns] = useState< + Array + >(["field1"]); const onSelectFunction = (e: React.ChangeEvent) => { const targetD = e.target; const value = targetD.value; const checked = targetD.checked; - let elements: string[] = [...selected]; // We clone the selected array + let elements: Array = [...selected]; // Clone the selected array if (checked) { // If the user has checked this field we need to push this to elements selection list - elements.push(value); + elements.push(value as keyof RecordType | string[]); } else { // User has unchecked this field, we need to remove it from the list elements = elements.filter((element) => element !== value); @@ -58,7 +72,7 @@ const Template: Story = (args) => { } const allItems = args.records.map( - (element) => `${element[`${args.idField}`]}`, + (element) => `${element[`${args.field1Field}`]}` ); setSelected(allItems); }; @@ -77,15 +91,15 @@ const Template: Story = (args) => { extraFunc = { ...extraFunc, columnsShown: selectedColumns, - onColumnChange: (columnKey) => { + onColumnChange: (columnKey: keyof RecordType) => { const itemFound = selectedColumns.findIndex( - (item) => item === columnKey, + (item) => item === columnKey ); // Item Exists, we remove it if (itemFound >= 0) { setSelectedColumns( - selectedColumns.filter((item) => item !== columnKey), + selectedColumns.filter((item) => item !== columnKey) ); } else { setSelectedColumns([...selectedColumns, columnKey]); @@ -99,7 +113,7 @@ const Template: Story = (args) => { - + {...args} {...extraFunc} /> @@ -110,9 +124,12 @@ export const Default = Template.bind({}); Default.args = { disabled: false, entityName: "Elements", - records: ["Element1", "Element2", "Element3"], - columns: [{ label: "Elements List" }], - onSelect: undefined, + records: [ + { field1: "Element1" }, + { field1: "Element2" }, + { field1: "Element3" }, + ], + columns: [{ label: "Elements List", elementKey: "field1" }], }; export const MultiColumn = Template.bind({}); @@ -122,25 +139,14 @@ MultiColumn.args = { idField: "field1", records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, { field1: "Value1-2", field2: "Value2-2", field3: "Value3-2" }, - { - field1: "Value1-3", - field2: "Value2-3", - field3: "Value3-3", - }, + { field1: "Value1-3", field2: "Value2-3", field3: "Value3-3" }, ], columns: [ { label: "Column1", elementKey: "field1" }, { label: "Column2", elementKey: "field2" }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], }; @@ -151,19 +157,12 @@ CustomColumnsWidth.args = { idField: "field1", records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, ], columns: [ { label: "Column1", elementKey: "field1", width: 200 }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], }; @@ -174,19 +173,12 @@ CustomRowStyle.args = { idField: "field1", records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, ], columns: [ { label: "Column1", elementKey: "field1", width: 200 }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], rowStyle: ({ index }) => (index === 1 ? "deleted" : ""), }; @@ -199,19 +191,12 @@ BackgroundEnabled.args = { noBackground: false, records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, ], columns: [ { label: "Column1", elementKey: "field1", width: 200 }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], }; @@ -223,19 +208,12 @@ CustomPaperHeight.args = { customPaperHeight: "250px", records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, ], columns: [ { label: "Column1", elementKey: "field1", width: 200 }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], }; @@ -247,19 +225,12 @@ CustomStyles.args = { customPaperHeight: "250px", records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, ], columns: [ { label: "Column1", elementKey: "field1", width: 200 }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], sx: { backgroundColor: "#f09", @@ -275,17 +246,9 @@ WithSorting.args = { customPaperHeight: "250px", records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, { field1: "An Item", field2: "A Second Item", field3: "A ThirdItem" }, - { - field1: "One Value", - field2: "Two Values", - field3: "Three Values", - }, + { field1: "One Value", field2: "Two Values", field3: "Three Values" }, { field1: "Some Other thing", field2: "Some Other thing", @@ -304,10 +267,7 @@ WithSorting.args = { width: 200, }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], sortEnabled: true, }; @@ -318,12 +278,10 @@ SortingOnSingleValue.args = { entityName: "Elements", idField: "field1", customPaperHeight: "250px", - records: ["A Value", "B Value", "C Value", "Z Value"], - columns: [ - { - label: "Only Column", - }, - ], + records: ["A Value", "B Value", "C Value", "Z Value"].map((field1) => ({ + field1, + })), + columns: [{ label: "Only Column", elementKey: "field1" }], sortEnabled: true, }; @@ -335,17 +293,9 @@ SortSomeColumnsOnly.args = { customPaperHeight: "250px", records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, { field1: "An Item", field2: "A Second Item", field3: "A ThirdItem" }, - { - field1: "One Value", - field2: "Two Values", - field3: "Three Values", - }, + { field1: "One Value", field2: "Two Values", field3: "Three Values" }, { field1: "Some Other thing", field2: "Some Other thing", @@ -364,10 +314,7 @@ SortSomeColumnsOnly.args = { width: 200, }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], sortEnabled: ["field1", "field3"], }; @@ -380,17 +327,9 @@ ManualControlledSort.args = { customPaperHeight: "250px", records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, { field1: "An Item", field2: "A Second Item", field3: "A ThirdItem" }, - { - field1: "One Value", - field2: "Two Values", - field3: "Three Values", - }, + { field1: "One Value", field2: "Two Values", field3: "Three Values" }, { field1: "Some Other thing", field2: "Some Other thing", @@ -414,10 +353,7 @@ ManualControlledSort.args = { width: 100, enableSort: false, }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], sortEnabled: { currentSort: "field1", @@ -438,52 +374,42 @@ WithItemActions.args = { itemActions: [ { type: "edit", - onClick: (itemID: string) => { - alert(itemID); + onClick: (item: RecordType) => { + alert(item.field1); }, - sendOnlyId: true, tooltip: "Edit", - isDisabled: true, + isDisabled: (value: RecordType) => value.field1 === "Value1", }, { type: "delete", - onClick: (deleteItem) => { - console.log("DELETE", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("DELETE", deleteItem.field1); }, - tooltip: "Delete, Disabled if Column 1 is Value1", - isDisabled: (value) => value.field1 === "Value1", + tooltip: "Delete", }, { type: "preview", - onClick: (deleteItem) => { - console.log("PREVIEW", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("PREVIEW", deleteItem.field1); }, tooltip: "Preview", }, { type: "cloud", - onClick: (deleteItem) => { - console.log("DELETE", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("CLOUD", deleteItem.field1); }, - tooltip: "Delete, Disabled if Column 1 is Value1", - showLoader: (value) => value.field1 === "Value1", + tooltip: "Cloud", }, ], records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, ], columns: [ { label: "Column1", elementKey: "field1", width: 200 }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], sortConfig: { currentSort: "field1", @@ -503,105 +429,97 @@ FullItemsActions.args = { itemActions: [ { type: "edit", - onClick: (itemID: string) => { - alert(itemID); + onClick: (item: RecordType) => { + alert(item.field1); }, - sendOnlyId: true, tooltip: "Edit", }, { type: "delete", - onClick: (deleteItem) => { - console.log("DELETE", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("DELETE", deleteItem.field1); }, tooltip: "Delete", }, { type: "console", - onClick: (deleteItem) => { - console.log("CONSOLE", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("CONSOLE", deleteItem.field1); }, tooltip: "Console", }, { type: "description", - onClick: (deleteItem) => { - console.log("DESCRIPTION", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("DESCRIPTION", deleteItem.field1); }, tooltip: "Description", }, { type: "cloud", - onClick: (deleteItem) => { - console.log("CLOUD", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("CLOUD", deleteItem.field1); }, tooltip: "Cloud", }, { type: "view", - onClick: (deleteItem, index) => { - console.log("VIEW", deleteItem, "INDEX", index); + onClick: (deleteItem: RecordType, index: number) => { + console.log("VIEW", deleteItem.field1, "INDEX", index); }, tooltip: "View", }, { type: "disable", - onClick: (deleteItem) => { - console.log("DISABLE", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("DISABLE", deleteItem.field1); }, tooltip: "Disable", }, { type: "download", - onClick: (deleteItem) => { - console.log("DOWNLOAD", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("DOWNLOAD", deleteItem.field1); }, tooltip: "Download", }, { type: "format", - onClick: (deleteItem) => { - console.log("FORMAT", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("FORMAT", deleteItem.field1); }, tooltip: "Format", }, { type: "preview", - onClick: (deleteItem) => { - console.log("PREVIEW", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("PREVIEW", deleteItem.field1); }, tooltip: "Preview", }, { type: "share", - onClick: (deleteItem) => { - console.log("SHARE", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("SHARE", deleteItem.field1); }, tooltip: "Share", }, { type: , - onClick: (deleteItem) => { - console.log("DELETE", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("DELETE", deleteItem.field1); }, tooltip: "Custom Icon", }, ], records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, ], columns: [ { label: "Column1", elementKey: "field1", width: 200 }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], sortConfig: { currentSort: "field1", @@ -621,27 +539,20 @@ SingleItemsAction.args = { itemActions: [ { type: "delete", - onClick: (deleteItem) => { - console.log("DELETE", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("DELETE", deleteItem.field1); }, tooltip: "Delete", }, ], records: [ { field1: "Value1", field2: "Value2", field3: "Value3" }, - { - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, ], columns: [ { label: "Column1", elementKey: "field1", width: 200 }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], sortConfig: { currentSort: "field1", @@ -662,16 +573,15 @@ ColumnsSelector.args = { itemActions: [ { type: "edit", - onClick: (itemID: string) => { - alert(itemID); + onClick: (item: RecordType) => { + alert(item.field1); }, - sendOnlyId: true, label: "Edit", }, { type: "delete", - onClick: (deleteItem) => { - console.log("DELETE", deleteItem); + onClick: (deleteItem: RecordType) => { + console.log("DELETE", deleteItem.field1); }, label: "Delete", }, @@ -709,28 +619,16 @@ ColumnsSelector.args = { columns: [ { label: "Column1", elementKey: "field1", width: 200 }, { label: "Column2", elementKey: "field2", width: 100 }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, { label: "Column4", elementKey: "field4", width: 200 }, { label: "Column5", elementKey: "field5", width: 100 }, - { - label: "Column6", - elementKey: "field6", - }, + { label: "Column6", elementKey: "field6" }, { label: "Column7", elementKey: "field7", width: 200 }, { label: "Column8", elementKey: "field8", width: 100 }, - { - label: "Column9", - elementKey: "field9", - }, + { label: "Column9", elementKey: "field9" }, { label: "Column10", elementKey: "field10", width: 200 }, { label: "Column11", elementKey: "field11", width: 100 }, - { - label: "Column12", - elementKey: "field12", - }, + { label: "Column12", elementKey: "field12" }, ], }; @@ -741,28 +639,15 @@ NumericIDs.args = { idField: "id", records: [ { id: 1, field1: "Value1", field2: "Value2", field3: "Value3" }, - { - id: 2, - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { id: 2, field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, { id: 3, field1: "Value1-2", field2: "Value2-2", field3: "Value3-2" }, - { - id: 4, - field1: "Value1-3", - field2: "Value2-3", - field3: "Value3-3", - }, + { id: 4, field1: "Value1-3", field2: "Value2-3", field3: "Value3-3" }, ], columns: [ { label: "ID", elementKey: "id" }, { label: "Column1", elementKey: "field1" }, { label: "Column2", elementKey: "field2" }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], }; @@ -773,19 +658,9 @@ LongTitles.args = { idField: "id", records: [ { id: 1, field1: "Value1", field2: "Value2", field3: "Value3" }, - { - id: 2, - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { id: 2, field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, { id: 3, field1: "Value1-2", field2: "Value2-2", field3: "Value3-2" }, - { - id: 4, - field1: "Value1-3", - field2: "Value2-3", - field3: "Value3-3", - }, + { id: 4, field1: "Value1-3", field2: "Value2-3", field3: "Value3-3" }, ], columns: [ { label: "ID", elementKey: "id" }, @@ -794,10 +669,7 @@ LongTitles.args = { elementKey: "field1", }, { label: "Column2", elementKey: "field2" }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], }; @@ -808,19 +680,9 @@ SelectOptions.args = { idField: "id", records: [ { id: 1, field1: "Value1", field2: "Value2", field3: "Value3" }, - { - id: 2, - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { id: 2, field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, { id: 3, field1: "Value1-2", field2: "Value2-2", field3: "Value3-2" }, - { - id: 4, - field1: "Value1-3", - field2: "Value2-3", - field3: "Value3-3", - }, + { id: 4, field1: "Value1-3", field2: "Value2-3", field3: "Value3-3" }, ], columns: [ { label: "ID", elementKey: "id" }, @@ -829,10 +691,7 @@ SelectOptions.args = { elementKey: "field1", }, { label: "Column2", elementKey: "field2" }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], }; @@ -844,19 +703,9 @@ CustomRowHeight.args = { rowHeight: 80, records: [ { id: 1, field1: "Value1", field2: "Value2", field3: "Value3" }, - { - id: 2, - field1: "Value1-1", - field2: "Value2-1", - field3: "Value3-1", - }, + { id: 2, field1: "Value1-1", field2: "Value2-1", field3: "Value3-1" }, { id: 3, field1: "Value1-2", field2: "Value2-2", field3: "Value3-2" }, - { - id: 4, - field1: "Value1-3", - field2: "Value2-3", - field3: "Value3-3", - }, + { id: 4, field1: "Value1-3", field2: "Value2-3", field3: "Value3-3" }, ], columns: [ { label: "ID", elementKey: "id" }, @@ -865,10 +714,7 @@ CustomRowHeight.args = { elementKey: "field1", }, { label: "Column2", elementKey: "field2" }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], }; @@ -887,9 +733,6 @@ CustomEmptyMessage.args = { elementKey: "field1", }, { label: "Column2", elementKey: "field2" }, - { - label: "Column3", - elementKey: "field3", - }, + { label: "Column3", elementKey: "field3" }, ], }; diff --git a/src/components/DataTable/DataTable.tsx b/src/components/DataTable/DataTable.tsx index a2de94de..b3a1b499 100644 --- a/src/components/DataTable/DataTable.tsx +++ b/src/components/DataTable/DataTable.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import React, { FC, Fragment, useState } from "react"; +import React, { Fragment, useState } from "react"; import { AutoSizer, Column, @@ -24,7 +24,6 @@ import { } from "react-virtualized"; import styled from "styled-components"; import get from "lodash/get"; -import isString from "lodash/isString"; import Checkbox from "../Checkbox/Checkbox"; import Loader from "../Loader/Loader"; import Grid from "../Grid/Grid"; @@ -78,14 +77,14 @@ const DataTableWrapper = styled.div( color: get( theme, "dataTable.titleColor", - themeColors["Color/Neutral/Text/colorTextLabel"].lightMode, + themeColors["Color/Neutral/Text/colorTextLabel"].lightMode ), fontSize: 12, padding: 10, borderBottom: `${get( theme, "dataTable.border", - "#E2E2E2", + "#E2E2E2" )} 1px solid`, width: "100%", }, @@ -108,7 +107,7 @@ const DataTableWrapper = styled.div( backgroundColor: get( theme, "dataTable.hoverColor", - themeColors["Color/Brand/Control/colorBgActive"].lightMode, + themeColors["Color/Brand/Control/colorBgActive"].lightMode ), "&.canClick": { cursor: "pointer", @@ -124,14 +123,14 @@ const DataTableWrapper = styled.div( color: get( theme, "dataTable.selected", - themeColors["Color/Neutral/Text/colorTextHeading"].lightMode, + themeColors["Color/Neutral/Text/colorTextHeading"].lightMode ), }, "&.deleted .selected": { color: get( theme, "dataTable.itemDisabled", - themeColors["Color/Neutral/Text/colorTextDisabled"].lightMode, + themeColors["Color/Neutral/Text/colorTextDisabled"].lightMode ), }, }, @@ -230,7 +229,7 @@ const DataTableWrapper = styled.div( height: 30, }, ...overridePropsParse(sx, theme), - }), + }) ); const TableRowPredefStyles: any = { @@ -244,11 +243,11 @@ const TableRowPredefStyles: any = { }; // Main function to render the Table Wrapper -const DataTable: FC = ({ +const DataTable = ({ itemActions, columns, onSelect, - records, + records = [] as T[], isLoading, loadingMessage =

Loading...

, entityName, @@ -259,7 +258,7 @@ const DataTable: FC = ({ columnsSelector = false, textSelectable = false, columnsShown = [], - onColumnChange = (column: string) => {}, + onColumnChange = () => {}, infiniteScrollConfig, autoScrollToBottom = false, disabled = false, @@ -270,16 +269,16 @@ const DataTable: FC = ({ rowHeight = 36, sortEnabled = false, sortCallBack, -}) => { +}: DataTableProps) => { const [columnSelectorOpen, setColumnSelectorOpen] = useState(false); const [currentSortColumn, setCurrentSortColumn] = useState< - string | undefined + keyof T | undefined >(undefined); const [currentSortDirection, setCurrentSortDirection] = useState("ASC"); - const [anchorEl, setAnchorEl] = useState(null); - const rowIDField = idField || ""; + + const rowIDField = idField || ("" as keyof T); const findView = itemActions ? itemActions.find((el) => el.type === "view") @@ -290,10 +289,9 @@ const DataTable: FC = ({ typeof sortEnabled === "object" && !Array.isArray(sortEnabled); - const clickAction = (rowItem: any, index: number) => { + const clickAction = (rowItem: T, index: number) => { if (findView) { - const valueClick = - findView.sendOnlyId && idField ? rowItem[rowIDField] : rowItem; + const valueClick = rowItem; let disabled = false; @@ -321,7 +319,7 @@ const DataTable: FC = ({ setAnchorEl(null); }; - const columnsSelection = (columns: IColumns[]) => { + const columnsSelection = (columns: IColumns[]) => { return ( = ({ ); }; - let tableSort: ((val: ITableSortInfo) => any) | undefined = undefined; - let tableSortBy: string | undefined = undefined; - let tableSortDirection: SortDirectionType | undefined = undefined; - const onSortClick = (sort: ITableSortInfo) => { - const newSortDirection = get(sort, "sortDirection", "DESC"); - setCurrentSortColumn(sort.sortBy); + const newSortDirection = get( + sort, + "sortDirection", + "DESC" + ) as SortDirectionType; + setCurrentSortColumn(sort.sortBy as keyof T); setCurrentSortDirection(newSortDirection); if (sortCallBack) { @@ -362,10 +360,14 @@ const DataTable: FC = ({ } }; + let tableSort: ((val: ITableSortInfo) => any) | undefined = undefined; + let tableSortBy: keyof T | undefined = undefined; + let tableSortDirection: SortDirectionType | undefined = undefined; + if (sortEnabled) { if (manualSortEnabled) { tableSort = sortEnabled.onSortClick; - tableSortBy = sortEnabled.currentSort; + tableSortBy = sortEnabled.currentSort as keyof T; tableSortDirection = sortEnabled.currentDirection; } else { tableSort = onSortClick; @@ -379,8 +381,8 @@ const DataTable: FC = ({ if (sortEnabled && currentSortColumn && !manualSortEnabled) { sortedRecords = sortRecords( records, - currentSortColumn, - currentSortDirection, + currentSortColumn as string, + currentSortDirection ); } @@ -412,7 +414,6 @@ const DataTable: FC = ({ {columnsSelection(columns)} )} {sortedRecords && !isLoading && sortedRecords.length > 0 ? ( - // @ts-ignore !!sortedRecords[index]} loadMoreRows={ @@ -427,14 +428,13 @@ const DataTable: FC = ({ } > {({ onRowsRendered, registerChild }) => ( - // @ts-ignore {({ width, height }: any) => { const optionsWidth = calculateOptionsSize( width, itemActions ? itemActions.filter((el) => el.type !== "view").length - : 0, + : 0 ); const hasSelect: boolean = !!(onSelect && selectedItems); const hasOptions: boolean = !!( @@ -444,7 +444,6 @@ const DataTable: FC = ({ itemActions[0].type !== "view") ); return ( - // @ts-ignore = ({ } onRowsRendered={onRowsRendered} sort={tableSort} - sortBy={tableSortBy} + sortBy={tableSortBy ? String(tableSortBy) : undefined} sortDirection={tableSortDirection} scrollToIndex={ autoScrollToBottom ? sortedRecords.length - 1 : -1 @@ -493,7 +492,6 @@ const DataTable: FC = ({ }} > {hasSelect && ( - // @ts-ignore ( @@ -516,25 +514,17 @@ const DataTable: FC = ({ )} )} - dataKey={`select-${rowIDField}`} + dataKey={`select-${String(rowIDField)}`} width={selectWidth} disableSort cellRenderer={({ rowData }) => { const isSelected = selectedItems - ? selectedItems.includes( - isString(rowData) - ? rowData - : `${rowData[rowIDField]}`, - ) + ? selectedItems.includes(rowData[rowIDField]) : false; return ( = ({ columnsSelector, columnsShown, sortEnabled, - tableSortBy || "", - tableSortDirection, + tableSortBy, + tableSortDirection )} {hasOptions && ( - // @ts-ignore = ({ className="optionsAlignment" cellRenderer={({ rowData }) => { const isSelected = selectedItems - ? selectedItems.includes( - isString(rowData) - ? rowData - : `${rowData[rowIDField]}`, - ) + ? selectedItems.includes(rowData[rowIDField]) : false; return elementActions( itemActions || [], rowData, isSelected, - rowIDField, + String(rowIDField) ); }} /> diff --git a/src/components/DataTable/DataTable.types.ts b/src/components/DataTable/DataTable.types.ts index 3318e7e6..76ef7822 100644 --- a/src/components/DataTable/DataTable.types.ts +++ b/src/components/DataTable/DataTable.types.ts @@ -34,27 +34,30 @@ export const actionsTypes = [ export type PredefinedActionTypes = (typeof actionsTypes)[number]; -export interface ItemActions { +export interface ItemActions { tooltip?: string; type: PredefinedActionTypes | React.ReactNode; - sendOnlyId?: boolean; - isDisabled?: boolean | ((itemValue: any) => boolean); - showLoader?: boolean | ((itemValue: any) => boolean); - onClick?(valueToSend: any, index?: number): any; + isDisabled?: boolean | ((itemValue: T) => boolean); + showLoader?: boolean | ((itemValue: T) => boolean); + onClick?(valueToSend: T, index?: number): any; } -export interface IColumns { +type Column = { label: string; - elementKey?: string; - renderFunction?: (input: any) => any; - renderFullObject?: boolean; + elementKey?: K; globalClass?: any; rowClass?: any; width?: number; headerTextAlign?: string; contentTextAlign?: string; enableSort?: boolean; -} + renderFunction?: (input: T[K]) => React.ReactNode | string; + renderFullObjectFunction?: (input: T) => React.ReactNode | string; +}; + +export type IColumns = { + [K in keyof T]: Column; +}[keyof T]; export interface IInfiniteScrollConfig { loadMoreRecords: (indexElements: { @@ -75,23 +78,23 @@ export interface ISortConfig { currentDirection: "ASC" | "DESC" | undefined; } -export interface DataTableProps { - itemActions?: ItemActions[] | null; - columns: IColumns[]; +export interface DataTableProps { + itemActions?: ItemActions[]; + columns: IColumns[]; onSelect?: (e: React.ChangeEvent) => void; - idField?: string; + idField?: K; isLoading?: boolean; loadingMessage?: React.ReactNode; - records: any[]; + records: T[]; entityName?: string; - selectedItems?: string[]; + selectedItems?: Array | string[]; customEmptyMessage?: string; customPaperHeight?: string; noBackground?: boolean; columnsSelector?: boolean; textSelectable?: boolean; - columnsShown?: string[]; - onColumnChange?: (column: string) => any; + columnsShown?: Array; + onColumnChange?: (column: K) => void; autoScrollToBottom?: boolean; infiniteScrollConfig?: IInfiniteScrollConfig; disabled?: boolean; @@ -104,7 +107,7 @@ export interface DataTableProps { parentClassName?: string; sx?: OverrideTheme; rowHeight?: number; - sortEnabled?: boolean | string[] | ISortConfig; + sortEnabled?: boolean | Array | ISortConfig; sortCallBack?: (info: ITableSortInfo) => void; } @@ -116,23 +119,20 @@ export interface DataTableWrapperProps extends HTMLAttributes { rowHeight: number; } -export interface IActionButton { +export interface IActionButton { tooltip?: string; type: PredefinedActionTypes | React.ReactNode; - onClick?: (valueToSend: any, index?: number) => any; - valueToSend: any; + onClick?: (valueToSend: T, index?: number) => any; + valueToSend: T; selected: boolean; - sendOnlyId?: boolean; - idField: string; disabled: boolean; } - -export interface ColumnSelectorProps { +export interface ColumnSelectorProps { open: boolean; closeTriggerAction: () => void; - onSelect: (column: string) => void; - columns: IColumns[]; - selectedOptionIDs: string[]; + onSelect: (column: K) => void; + columns: IColumns[]; + selectedOptionIDs: Array; sx?: OverrideTheme; anchorEl?: (EventTarget & HTMLElement) | null; } diff --git a/src/components/DataTable/DataTable.utils.tsx b/src/components/DataTable/DataTable.utils.tsx index d106f8d8..ca36a726 100644 --- a/src/components/DataTable/DataTable.utils.tsx +++ b/src/components/DataTable/DataTable.utils.tsx @@ -15,8 +15,6 @@ // along with this program. If not, see . import React, { Fragment } from "react"; -import get from "lodash/get"; -import isString from "lodash/isString"; import isPlainObject from "lodash/isPlainObject"; import { Column, SortDirectionType } from "react-virtualized"; import { IColumns, ISortConfig, ItemActions } from "./DataTable.types"; @@ -29,43 +27,44 @@ import ChevronDownIcon from "../Icons/NewDesignIcons/ChevronDownIcon"; export const selectWidth = 45; // Function to render elements in table -const subRenderFunction = ( - rowData: any, - column: IColumns, - isSelected: boolean, +const subRenderFunction = ( + rowData: T, + column: IColumns, + isSelected: boolean ) => { - const itemElement = isString(rowData) - ? rowData - : get(rowData, column.elementKey || "", null); // If the element is just a string, we render it as it is - const renderConst = column.renderFullObject ? rowData : itemElement; - - const renderElement = column.renderFunction - ? column.renderFunction(renderConst) - : renderConst; // If render function is set, we send the value to the function. - - return ( - - {renderElement} - - ); + let content: React.ReactNode; + + if (column.renderFullObjectFunction) { + content = column.renderFullObjectFunction(rowData); + } else if (column.renderFunction && column.elementKey) { + const value = rowData[column.elementKey]; + content = column.renderFunction(value); + } else if (column.elementKey) { + const value = rowData[column.elementKey]; + content = value?.toString() ?? "-"; + } else { + content = "-"; + } + + return {content}; }; // Function to calculate common column width for elements with no with size -const calculateColumnRest = ( - columns: IColumns[], +const calculateColumnRest = ( + columns: IColumns[], containerWidth: number, actionsWidth: number, hasSelect: boolean, hasActions: boolean, columnsSelector: boolean, - columnsShown: string[], + columnsShown: string[] ) => { if (columns) { let colsItems = [...columns]; if (columnsSelector) { colsItems = columns.filter((column) => - columnsShown.includes(column.elementKey!), + columnsShown.includes(String(column.elementKey!)) ); } @@ -90,19 +89,19 @@ const calculateColumnRest = ( }; // Function that renders Columns in table -export const generateColumnsMap = ( - columns: IColumns[], +export const generateColumnsMap = ( + columns: IColumns[], containerWidth: number, actionsWidth: number, hasSelect: boolean, hasActions: boolean, - selectedItems: string[], - idField: string, + selectedItems: Array | string[], + idField: keyof T, columnsSelector: boolean, - columnsShown: string[], - sortColumns: boolean | string[] | ISortConfig, - currentSortColumn: string | undefined, - currentSortDirection: "ASC" | "DESC" | undefined, + columnsShown: Array, + sortColumns: boolean | Array | ISortConfig, + currentSortColumn: keyof T | undefined, + currentSortDirection: "ASC" | "DESC" | undefined ) => { const manualSortEnabled = sortColumns && @@ -116,10 +115,13 @@ export const generateColumnsMap = ( hasSelect, hasActions, columnsSelector, - columnsShown, + columnsShown.map((key) => key.toString()) // Convert keys to strings ); - return columns.map((column: IColumns, index: number) => { - if (columnsSelector && !columnsShown.includes(column.elementKey!)) { + + return columns.map((column: IColumns, index: number) => { + const columnKey = column.elementKey as keyof T; + + if (columnsSelector && !columnsShown.includes(columnKey)) { return null; } @@ -130,14 +132,12 @@ export const generateColumnsMap = ( const disableSort = !sortColumns || (manualSortEnabled && !manualColumnSortEnabled) || - (Array.isArray(sortColumns) && - !sortColumns.includes(column?.elementKey || "")); + (Array.isArray(sortColumns) && !sortColumns.includes(columnKey)); return ( - // @ts-ignore {sortColumns || - (Array.isArray(sortColumns) && - sortColumns.includes(column.elementKey)) ? ( + (Array.isArray(sortColumns) && sortColumns.includes(columnKey)) ? ( - {currentSortColumn === column.elementKey || + {currentSortColumn === columnKey || (columns.length === 1 && currentSortColumn === "column-0") ? ( {currentSortDirection === "ASC" ? ( @@ -186,11 +185,9 @@ export const generateColumnsMap = ( } cellRenderer={({ rowData }) => { const isSelected = selectedItems - ? selectedItems.includes( - isString(rowData) ? rowData : `${rowData[idField]}`, - ) + ? selectedItems.includes(rowData[idField]) : false; - return subRenderFunction(rowData, column, isSelected); + return subRenderFunction(rowData as T, column, isSelected); }} width={column.width || commonRestWidth} disableSort={disableSort} @@ -201,13 +198,13 @@ export const generateColumnsMap = ( }; // Function to render the action buttons -export const elementActions = ( - actions: ItemActions[], +export const elementActions = ( + actions: ItemActions[], valueToSend: any, selected: boolean, - idField: string, + idField: string ) => { - return actions.map((action: ItemActions, index: number) => { + return actions.map((action: ItemActions, index: number) => { if (action.type === "view") { return null; } @@ -246,8 +243,6 @@ export const elementActions = ( valueToSend={valueToSend} selected={selected} key={`actions-${action.type}-${index.toString()}`} - idField={idField} - sendOnlyId={!!action.sendOnlyId} disabled={disabled} /> ); @@ -257,7 +252,7 @@ export const elementActions = ( // Function to calculate the options column width according elements inside export const calculateOptionsSize = ( containerWidth: number, - totalOptions: number, + totalOptions: number ) => { const minContainerSize = 36; const sizeOptions = totalOptions * 36; @@ -277,7 +272,7 @@ export const calculateOptionsSize = ( export const sortRecords = ( records: any[], sortColumn: string | undefined, - sortDirection: SortDirectionType, + sortDirection: SortDirectionType ) => { const sortedRecords = records; diff --git a/src/components/DataTable/TableActionButton.tsx b/src/components/DataTable/TableActionButton.tsx index 9882de50..61bd3ffa 100644 --- a/src/components/DataTable/TableActionButton.tsx +++ b/src/components/DataTable/TableActionButton.tsx @@ -52,13 +52,13 @@ const TableActionCustomIcon = styled.button(({ theme }) => { background: get( theme, `dataTable.actionButton.background`, - lightV2.plainIconButtonBG, + lightV2.plainIconButtonBG ), "& svg": { color: get( theme, `dataTable.actionButton.iconColor`, - lightV2.plainIconButtonColor, + lightV2.plainIconButtonColor ), margin: "calc(25% - 2px)", }, @@ -66,24 +66,24 @@ const TableActionCustomIcon = styled.button(({ theme }) => { background: get( theme, `dataTable.actionButton.hoverBackground`, - lightV2.plainIconButtonBG, + lightV2.plainIconButtonBG ), borderColor: get( theme, `dataTable.actionButton.hoverBorder`, - lightV2.plainIconButtonBorder, + lightV2.plainIconButtonBorder ), }, "&:active:not(:disabled)": { background: get( theme, `dataTable.actionButton.activeBackground`, - lightV2.plainIconButtonBG, + lightV2.plainIconButtonBG ), borderColor: get( theme, `dataTable.actionButton.activeBorder`, - lightV2.plainIconButtonBorder, + lightV2.plainIconButtonBorder ), }, "&:disabled": { @@ -91,18 +91,18 @@ const TableActionCustomIcon = styled.button(({ theme }) => { background: get( theme, `dataTable.actionButton.disabledBackground`, - "transparent", + "transparent" ), borderColor: get( theme, `dataTable.actionButton.disabledBorder`, - lightV2.disabledSecondary, + lightV2.disabledSecondary ), "& svg": { color: get( theme, `dataTable.actionButton.disabledIconColor`, - lightV2.disabledSecondaryText, + lightV2.disabledSecondaryText ), }, }, @@ -141,17 +141,13 @@ const defineIcon = (type: PredefinedActionTypes) => { return null; }; -const TableActionButton: FC = ({ +const TableActionButton = ({ type, onClick, valueToSend, - idField, - sendOnlyId = false, disabled = false, tooltip, -}) => { - const valueClick = sendOnlyId ? valueToSend[idField] : valueToSend; - +}: IActionButton) => { const icon = isPredefinedAction(type) ? defineIcon(type) : type; let buttonElement = ( = ({ ? (e) => { e.stopPropagation(); if (!disabled) { - onClick(valueClick); + onClick(valueToSend); } else { e.preventDefault(); }