From 8dddfcd5335f5bd68a9053ff14c57f5c4690e051 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Fri, 14 Oct 2022 16:15:41 +1100 Subject: [PATCH] add basic read-only table --- package.json | 1 + src/components/Table/Styled/StyledCell.tsx | 18 + src/components/Table/Styled/StyledRow.tsx | 11 + src/components/Table/Styled/StyledTable.tsx | 7 + src/components/Table/Table.tsx | 409 +++++++----------- .../fields/SingleSelect/Settings.tsx | 2 +- src/layouts/Navigation/Navigation.tsx | 2 +- src/pages/Table/TablePage.tsx | 26 +- yarn.lock | 12 + 9 files changed, 221 insertions(+), 267 deletions(-) create mode 100644 src/components/Table/Styled/StyledCell.tsx create mode 100644 src/components/Table/Styled/StyledRow.tsx create mode 100644 src/components/Table/Styled/StyledTable.tsx diff --git a/package.json b/package.json index fb029fc7..e743edda 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@mui/x-date-pickers": "^5.0.0-alpha.4", "@rowy/form-builder": "^0.7.0", "@rowy/multiselect": "^0.4.1", + "@tanstack/react-table": "^8.5.15", "@tinymce/tinymce-react": "^3", "@uiw/react-md-editor": "^3.14.1", "algoliasearch": "^4.13.1", diff --git a/src/components/Table/Styled/StyledCell.tsx b/src/components/Table/Styled/StyledCell.tsx new file mode 100644 index 00000000..0676daa2 --- /dev/null +++ b/src/components/Table/Styled/StyledCell.tsx @@ -0,0 +1,18 @@ +import { styled } from "@mui/material"; + +export const StyledCell = styled("div")(({ theme }) => ({ + display: "flex", + alignItems: "center", + "--cell-padding": theme.spacing(1.5), + padding: "0 var(--cell-padding)", + + overflow: "visible", + contain: "none", + position: "relative", + + lineHeight: "calc(var(--row-height) - 1px)", + + borderBottom: `1px solid ${theme.palette.divider}`, + borderLeft: `1px solid ${theme.palette.divider}`, +})); +StyledCell.displayName = "StyledCell"; diff --git a/src/components/Table/Styled/StyledRow.tsx b/src/components/Table/Styled/StyledRow.tsx new file mode 100644 index 00000000..4e76b1d8 --- /dev/null +++ b/src/components/Table/Styled/StyledRow.tsx @@ -0,0 +1,11 @@ +import { styled } from "@mui/material"; + +import { DEFAULT_ROW_HEIGHT } from "@src/components/Table"; + +export const StyledRow = styled("div")(({ theme }) => ({ + display: "flex", + height: DEFAULT_ROW_HEIGHT, + + backgroundColor: theme.palette.background.paper, +})); +StyledRow.displayName = "StyledRow"; diff --git a/src/components/Table/Styled/StyledTable.tsx b/src/components/Table/Styled/StyledTable.tsx new file mode 100644 index 00000000..fc855036 --- /dev/null +++ b/src/components/Table/Styled/StyledTable.tsx @@ -0,0 +1,7 @@ +import { styled } from "@mui/material"; + +export const StyledTable = styled("div")(({ theme }) => ({ + ...(theme.typography.caption as any), + lineHeight: "inherit !important", +})); +StyledTable.displayName = "StyledTable"; diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 308e8767..6dbddcaf 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -5,19 +5,26 @@ import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; import { findIndex } from "lodash-es"; -// import "react-data-grid/dist/react-data-grid.css"; -import DataGrid, { - Column, - DataGridHandle, - // SelectColumn as _SelectColumn, -} from "react-data-grid"; +import { + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { TOP_BAR_HEIGHT } from "@src/layouts/Navigation/TopBar"; +import { TABLE_TOOLBAR_HEIGHT } from "@src/components/TableToolbar"; +import { StyledTable } from "./Styled/StyledTable"; +import { StyledRow } from "./Styled/StyledRow"; +import ColumnHeaderComponent from "./Column"; + import { LinearProgress } from "@mui/material"; import TableContainer, { OUT_OF_ORDER_MARGIN } from "./TableContainer"; import ColumnHeader, { COLUMN_HEADER_HEIGHT } from "./ColumnHeader"; import FinalColumnHeader from "./FinalColumnHeader"; import FinalColumn from "./formatters/FinalColumn"; -import TableRow from "./TableRow"; +// import TableRow from "./TableRow"; import EmptyState from "@src/components/EmptyState"; // import BulkActions from "./BulkActions"; import AddRow from "@src/components/TableToolbar/AddRow"; @@ -47,22 +54,21 @@ import { import { getFieldType, getFieldProp } from "@src/components/fields"; import { FieldType } from "@src/constants/fields"; import { formatSubTableName } from "@src/utils/table"; -import { ColumnConfig } from "@src/types/table"; +import { TableRow, ColumnConfig } from "@src/types/table"; +import { StyledCell } from "./Styled/StyledCell"; -export type DataGridColumn = ColumnConfig & Column & { isNew?: true }; export const DEFAULT_ROW_HEIGHT = 41; export const DEFAULT_COL_WIDTH = 150; export const MAX_COL_WIDTH = 380; -const rowKeyGetter = (row: any) => row.id; -const rowClass = (row: any) => (row._rowy_outOfOrder ? "out-of-order" : ""); -//const SelectColumn = { ..._SelectColumn, width: 42, maxWidth: 42 }; +declare module "@tanstack/table-core" { + interface ColumnMeta extends ColumnConfig {} +} -export default function Table({ - dataGridRef, -}: { - dataGridRef?: React.MutableRefObject; -}) { +const columnHelper = createColumnHelper(); +const getRowId = (row: TableRow) => row._rowy_ref.path || row._rowy_ref.id; + +export default function TableComponent() { const [userRoles] = useAtom(userRolesAtom, projectScope); const [userSettings] = useAtom(userSettingsAtom, projectScope); @@ -78,266 +84,143 @@ export default function Table({ const updateColumn = useSetAtom(updateColumnAtom, tableScope); const updateField = useSetAtom(updateFieldAtom, tableScope); + const canAddColumn = userRoles.includes("ADMIN"); const userDocHiddenFields = userSettings.tables?.[formatSubTableName(tableId)]?.hiddenFields; - // Get column configs from table schema and map them to DataGridColumns - // Also filter out hidden columns and add end column + // Get column defs from table schema + // Also add end column for admins const columns = useMemo(() => { - const _columns: DataGridColumn[] = tableColumnsOrdered - .filter((column) => { - if (column.hidden) return false; - if ( - Array.isArray(userDocHiddenFields) && - userDocHiddenFields.includes(column.key) - ) - return false; - return true; - }) - .map((column: any) => ({ - draggable: true, - resizable: true, - frozen: column.fixed, - headerRenderer: ColumnHeader, - formatter: - getFieldProp("TableCell", getFieldType(column)) ?? - function InDev() { - return null; - }, - editor: - getFieldProp("TableEditor", getFieldType(column)) ?? - function InDev() { - return null; - }, - ...column, - editable: - tableSettings.readOnly && !userRoles.includes("ADMIN") - ? false - : column.editable ?? true, - width: column.width ?? DEFAULT_COL_WIDTH, - })); + const _columns = tableColumnsOrdered + // .filter((column) => { + // if (column.hidden) return false; + // if ( + // Array.isArray(userDocHiddenFields) && + // userDocHiddenFields.includes(column.key) + // ) + // return false; + // return true; + // }) + .map((columnConfig) => + columnHelper.accessor(columnConfig.fieldName, { + meta: columnConfig, + // draggable: true, + // resizable: true, + // frozen: columnConfig.fixed, + // headerRenderer: ColumnHeader, + // formatter: + // getFieldProp("TableCell", getFieldType(columnConfig)) ?? + // function InDev() { + // return null; + // }, + // editor: + // getFieldProp("TableEditor", getFieldType(columnConfig)) ?? + // function InDev() { + // return null; + // }, + // ...columnConfig, + // editable: + // tableSettings.readOnly && !userRoles.includes("ADMIN") + // ? false + // : columnConfig.editable ?? true, + // width: columnConfig.width ?? DEFAULT_COL_WIDTH, + }) + ); - if (userRoles.includes("ADMIN") || !tableSettings.readOnly) { - _columns.push({ - isNew: true, - key: "new", - fieldName: "_rowy_new", - name: "Add column", - type: FieldType.last, - index: _columns.length ?? 0, - width: 154, - headerRenderer: FinalColumnHeader, - headerCellClass: "final-column-header", - cellClass: "final-column-cell", - formatter: FinalColumn, - editable: false, - }); - } + // if (canAddColumn || !tableSettings.readOnly) { + // _columns.push({ + // isNew: true, + // key: "new", + // fieldName: "_rowy_new", + // name: "Add column", + // type: FieldType.last, + // index: _columns.length ?? 0, + // width: 154, + // headerRenderer: FinalColumnHeader, + // headerCellClass: "final-column-header", + // cellClass: "final-column-cell", + // formatter: FinalColumn, + // editable: false, + // }); + // } return _columns; }, [ tableColumnsOrdered, - userDocHiddenFields, - tableSettings.readOnly, - userRoles, + // userDocHiddenFields, + // tableSettings.readOnly, + // canAddColumn, ]); - const selectedColumnIndex = useMemo(() => { - if (!selectedCell?.columnKey) return -1; - return findIndex(columns, ["key", selectedCell.columnKey]); - }, [selectedCell?.columnKey, columns]); - // Handle columns with field names that use dot notation (nested fields) - const rows = - useMemo(() => { - // const columnsWithNestedFieldNames = columns - // .map((col) => col.fieldName) - // .filter((fieldName) => fieldName.includes(".")); + const table = useReactTable({ + data: tableRows, + columns, + getCoreRowModel: getCoreRowModel(), + getRowId, + columnResizeMode: "onChange", + // debugRows: true, + }); + console.log(table); - // if (columnsWithNestedFieldNames.length === 0) - return tableRows; - - // return tableRows.map((row) => - // columnsWithNestedFieldNames.reduce( - // (acc, fieldName) => ({ - // ...acc, - // [fieldName]: get(row, fieldName), - // }), - // { ...row } - // ) - // ); - }, [tableRows]) ?? []; - - // const [selectedRowsSet, setSelectedRowsSet] = useState>(); - // const [selectedRows, setSelectedRows] = useState([]); - - // Gets more rows when scrolled down. - // https://github.com/adazzle/react-data-grid/blob/ead05032da79d7e2b86e37cdb9af27f2a4d80b90/stories/demos/AllFeatures.tsx#L60 - const handleScroll = useThrottledCallback( - (event: React.UIEvent) => { - // Select corresponding header cell when scrolled to prevent jumping - dataGridRef?.current?.selectCell({ - idx: - selectedColumnIndex > -1 ? selectedColumnIndex : columns.length - 1, - rowIdx: -1, - }); - // console.log( - // "scroll", - // dataGridRef?.current, - // selectedColumnIndex, - // columns.length - // ); - - const target = event.target as HTMLDivElement; - - // TODO: - // if (navPinned && !columns[0].fixed) - // setShowLeftScrollDivider(target.scrollLeft > 16); - - const offset = 800; - const isAtBottom = - target.clientHeight + target.scrollTop >= target.scrollHeight - offset; - if (!isAtBottom) return; - // Call for the next page - setTablePage((p) => p + 1); - }, - 250 - ); - - const [showLeftScrollDivider, setShowLeftScrollDivider] = useState(false); - - const rowHeight = tableSchema.rowHeight ?? DEFAULT_ROW_HEIGHT; - const handleResize = useDebouncedCallback( - (colIndex: number, width: number) => { - const column = columns[colIndex]; - if (!column.key) return; - updateColumn({ key: column.key, config: { width } }); - }, - 1000 - ); + const handleKeyDown = (e: React.KeyboardEvent) => {}; return ( - }> - {/* */} - - - {showLeftScrollDivider &&
} + <> + +
+ {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {/*
*/} + + ))} + + ))} +
- { - if (dataGridRef) dataGridRef.current = handle; - }} - rows={rows} - columns={columns} - // Increase row height of out of order rows to add margins - rowHeight={({ row }) => { - if (row._rowy_outOfOrder) - return rowHeight + OUT_OF_ORDER_MARGIN + 1; - - return rowHeight; - }} - headerRowHeight={DEFAULT_ROW_HEIGHT + 1} - className="rdg-light" // Handle dark mode in MUI theme - cellNavigationMode="LOOP_OVER_ROW" - rowRenderer={TableRow} - rowKeyGetter={rowKeyGetter} - rowClass={rowClass} - // selectedRows={selectedRowsSet} - // onSelectedRowsChange={(newSelectedSet) => { - // const newSelectedArray = newSelectedSet - // ? [...newSelectedSet] - // : []; - // const prevSelectedRowsArray = selectedRowsSet - // ? [...selectedRowsSet] - // : []; - // const addedSelections = difference( - // newSelectedArray, - // prevSelectedRowsArray - // ); - // const removedSelections = difference( - // prevSelectedRowsArray, - // newSelectedArray - // ); - // addedSelections.forEach((id) => { - // const newRow = find(rows, { id }); - // setSelectedRows([...selectedRows, newRow]); - // }); - // removedSelections.forEach((rowId) => { - // setSelectedRows(selectedRows.filter((row) => row.id !== rowId)); - // }); - // setSelectedRowsSet(newSelectedSet); - // }} - // onRowsChange={() => { - //console.log('onRowsChange',rows) - // }} - // TODO: onFill={(e) => { - // console.log("onFill", e); - // const { columnKey, sourceRow, targetRows } = e; - // if (updateCell) - // targetRows.forEach((row) => - // updateCell(row._rowy_ref, columnKey, sourceRow[columnKey]) - // ); - // return []; - // }} - onPaste={(e, ...args) => { - console.log("onPaste", e, ...args); - const value = e.sourceRow[e.sourceColumnKey]; - updateField({ - path: e.targetRow._rowy_ref.path, - fieldName: e.targetColumnKey, - value, - }); - }} - onSelectedCellChange={({ rowIdx, idx }) => { - if (!rows[rowIdx]?._rowy_ref) return; // May be the header row - - const path = rows[rowIdx]._rowy_ref.path; - if (!path) return; - - const columnKey = tableColumnsOrdered.filter((col) => - userDocHiddenFields - ? !userDocHiddenFields.includes(col.key) - : true - )[idx]?.key; - if (!columnKey) return; // May be the final column - - setSelectedCell({ path, columnKey }); - }} - /> - - - {tableRows.length === 0 && ( - -
- -
- } - style={{ - position: "absolute", - inset: 0, - top: COLUMN_HEADER_HEIGHT, - height: "auto", - }} - /> - )} - {tableNextPage.loading && } - - - - {/* - { - setSelectedRowsSet(new Set()); - setSelectedRows([]); - }} - /> */} - +
+ {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + + ))} + + ))} +
+
+ ); } diff --git a/src/components/fields/SingleSelect/Settings.tsx b/src/components/fields/SingleSelect/Settings.tsx index 103bb2cd..2fc908b9 100644 --- a/src/components/fields/SingleSelect/Settings.tsx +++ b/src/components/fields/SingleSelect/Settings.tsx @@ -93,7 +93,7 @@ export default function Settings({ onChange, config }: ISettingsProps) { onChange={(e) => { setNewOption(e.target.value); }} - onKeyPress={(e: any) => { + onKeyDown={(e: any) => { if (e.key === "Enter") { handleAdd(); } diff --git a/src/layouts/Navigation/Navigation.tsx b/src/layouts/Navigation/Navigation.tsx index f17053a1..3f967301 100644 --- a/src/layouts/Navigation/Navigation.tsx +++ b/src/layouts/Navigation/Navigation.tsx @@ -62,7 +62,7 @@ export default function Navigation({ children }: React.PropsWithChildren<{}>) { } > -
+
{children}
diff --git a/src/pages/Table/TablePage.tsx b/src/pages/Table/TablePage.tsx index 4d682fc3..17e8213a 100644 --- a/src/pages/Table/TablePage.tsx +++ b/src/pages/Table/TablePage.tsx @@ -4,7 +4,7 @@ import { DataGridHandle } from "react-data-grid"; import { ErrorBoundary } from "react-error-boundary"; import { isEmpty } from "lodash-es"; -import { Fade } from "@mui/material"; +import { Box, Fade } from "@mui/material"; import ErrorFallback, { InlineErrorFallback, } from "@src/components/ErrorFallback"; @@ -27,6 +27,12 @@ import { import useBeforeUnload from "@src/hooks/useBeforeUnload"; import ActionParamsProvider from "@src/components/fields/Action/FormDialog/Provider"; import { useSnackLogContext } from "@src/contexts/SnackLogContext"; +import { TOP_BAR_HEIGHT } from "@src/layouts/Navigation/TopBar"; +import { TABLE_TOOLBAR_HEIGHT } from "@src/components/TableToolbar"; +import { + DRAWER_COLLAPSED_WIDTH, + DRAWER_WIDTH, +} from "@src/components/SideDrawer"; // prettier-ignore const BuildLogsSnack = lazy(() => import("@src/components/TableModals/CloudLogsModal/BuildLogs/BuildLogsSnack" /* webpackChunkName: "TableModals-BuildLogsSnack" */)); @@ -93,7 +99,23 @@ export default function TablePage({ }> - + + `max(env(safe-area-inset-bottom), ${theme.spacing(2)})`, + }, + }} + > +
+ diff --git a/yarn.lock b/yarn.lock index 4d281370..b6e7cfa3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2688,6 +2688,18 @@ dependencies: "@jest/create-cache-key-function" "^27.4.2" +"@tanstack/react-table@^8.5.15": + version "8.5.15" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.5.15.tgz#8179d24d7fdf909799a517e8897501c44e51284d" + integrity sha512-9rSvhIFeMpfXksFgQNTWnVoJbkae/U8CkHnHYGWAIB/O0Ca51IKap0Rjp5WkIUVBWxJ7Wfl2y13oY+aWcyM6Rg== + dependencies: + "@tanstack/table-core" "8.5.15" + +"@tanstack/table-core@8.5.15": + version "8.5.15" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.5.15.tgz#e1e674135cd6c36f29a1562a2b846f824861149b" + integrity sha512-k+BcCOAYD610Cij6p1BPyEqjMQjZIdAnVDoIUKVnA/tfHbF4JlDP7pKAftXPBxyyX5Z1yQPurPnOdEY007Snyg== + "@testing-library/dom@^8.5.0": version "8.13.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5"