import React, { useMemo, useState, Suspense } from "react"; import { useAtom, useSetAtom } from "jotai"; import { useDebouncedCallback, useThrottledCallback } from "use-debounce"; 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 { 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 EmptyState from "@src/components/EmptyState"; // import BulkActions from "./BulkActions"; import AddRow from "@src/components/TableToolbar/AddRow"; import { AddRow as AddRowIcon } from "@src/assets/icons"; import Loading from "@src/components/Loading"; import ContextMenu from "./ContextMenu"; import { projectScope, userRolesAtom, userSettingsAtom, } from "@src/atoms/projectScope"; import { tableScope, tableIdAtom, tableSettingsAtom, tableSchemaAtom, tableColumnsOrderedAtom, tableRowsAtom, tableNextPageAtom, tablePageAtom, updateColumnAtom, updateFieldAtom, selectedCellAtom, } from "@src/atoms/tableScope"; 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"; 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 }; export default function Table({ dataGridRef, }: { dataGridRef?: React.MutableRefObject; }) { const [userRoles] = useAtom(userRolesAtom, projectScope); const [userSettings] = useAtom(userSettingsAtom, projectScope); const [tableId] = useAtom(tableIdAtom, tableScope); const [tableSettings] = useAtom(tableSettingsAtom, tableScope); const [tableSchema] = useAtom(tableSchemaAtom, tableScope); const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope); const [tableRows] = useAtom(tableRowsAtom, tableScope); const [tableNextPage] = useAtom(tableNextPageAtom, tableScope); const setTablePage = useSetAtom(tablePageAtom, tableScope); const [selectedCell, setSelectedCell] = useAtom(selectedCellAtom, tableScope); const updateColumn = useSetAtom(updateColumnAtom, tableScope); const updateField = useSetAtom(updateFieldAtom, tableScope); 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 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 ?? 100, })); 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, }); } return _columns; }, [ tableColumnsOrdered, userDocHiddenFields, tableSettings.readOnly, userRoles, ]); 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(".")); // 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 || 0, rowIdx: -1, }); 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 ); return ( }> {/* */} {showLeftScrollDivider &&
} { 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([]); }} /> */}
); }