Files
rowy/src/components/Table/Table.tsx

323 lines
10 KiB
TypeScript
Raw Normal View History

import React, { useMemo, useState } from "react";
2022-05-19 16:37:56 +10:00
import { useAtom, useSetAtom } from "jotai";
import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
2022-05-19 16:37:56 +10:00
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
// import "react-data-grid/dist/react-data-grid.css";
import DataGrid, {
Column,
2022-06-02 16:57:44 +10:00
DataGridHandle,
2022-05-19 16:37:56 +10:00
// SelectColumn as _SelectColumn,
} from "react-data-grid";
2022-05-27 15:10:06 +10:00
import { LinearProgress } from "@mui/material";
2022-05-19 16:37:56 +10:00
import TableContainer, { OUT_OF_ORDER_MARGIN } from "./TableContainer";
2022-05-27 15:10:06 +10:00
import ColumnHeader, { COLUMN_HEADER_HEIGHT } from "./ColumnHeader";
2022-05-19 16:37:56 +10:00
// import ContextMenu from "./ContextMenu";
import FinalColumnHeader from "./FinalColumnHeader";
import FinalColumn from "./formatters/FinalColumn";
import TableRow from "./TableRow";
2022-05-27 15:10:06 +10:00
import EmptyState from "@src/components/EmptyState";
2022-05-19 16:37:56 +10:00
// import BulkActions from "./BulkActions";
2022-05-27 15:10:06 +10:00
import AddRow from "@src/components/TableToolbar/AddRow";
import { AddRow as AddRowIcon } from "@src/assets/icons";
2022-05-19 16:37:56 +10:00
import {
globalScope,
userRolesAtom,
userSettingsAtom,
navPinnedAtom,
2022-05-19 16:37:56 +10:00
} from "@src/atoms/globalScope";
import {
tableScope,
tableIdAtom,
tableSettingsAtom,
tableSchemaAtom,
tableColumnsOrderedAtom,
tableRowsAtom,
tableNextPageAtom,
2022-05-19 16:37:56 +10:00
tablePageAtom,
updateColumnAtom,
updateFieldAtom,
selectedCellAtom,
2022-05-19 16:37:56 +10:00
} from "@src/atoms/tableScope";
2022-05-24 20:34:28 +10:00
import { getFieldType, getFieldProp } from "@src/components/fields";
2022-05-19 16:37:56 +10:00
import { FieldType } from "@src/constants/fields";
import { formatSubTableName } from "@src/utils/table";
import { ColumnConfig } from "@src/types/table";
export type DataGridColumn = ColumnConfig & Column<any> & { isNew?: true };
export const DEFAULT_ROW_HEIGHT = 41;
const rowKeyGetter = (row: any) => row.id;
const rowClass = (row: any) => (row._rowy_outOfOrder ? "out-of-order" : "");
//const SelectColumn = { ..._SelectColumn, width: 42, maxWidth: 42 };
2022-06-02 16:57:44 +10:00
export default function Table({
dataGridRef,
}: {
dataGridRef?: React.MutableRefObject<DataGridHandle | null>;
}) {
2022-05-19 16:37:56 +10:00
const [userRoles] = useAtom(userRolesAtom, globalScope);
const [userSettings] = useAtom(userSettingsAtom, globalScope);
const [navPinned] = useAtom(navPinnedAtom, globalScope);
2022-05-19 16:37:56 +10:00
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 setSelectedCell = useSetAtom(selectedCellAtom, tableScope);
2022-05-19 16:37:56 +10:00
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:
2022-05-24 20:34:28 +10:00
getFieldProp("TableCell", getFieldType(column)) ??
2022-05-19 16:37:56 +10:00
function InDev() {
return null;
},
editor:
2022-05-24 20:34:28 +10:00
getFieldProp("TableEditor", getFieldType(column)) ??
2022-05-19 16:37:56 +10:00
function InDev() {
return null;
},
...column,
editable:
tableSettings.readOnly && !userRoles.includes("ADMIN")
? false
: column.editable ?? true,
width: (column.width as number)
? (column.width as number) > 380
? 380
: (column.width as number)
: 150,
}));
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,
]);
// 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("."));
2022-05-19 16:37:56 +10:00
// if (columnsWithNestedFieldNames.length === 0)
return tableRows;
2022-05-19 16:37:56 +10:00
// return tableRows.map((row) =>
// columnsWithNestedFieldNames.reduce(
// (acc, fieldName) => ({
// ...acc,
// [fieldName]: get(row, fieldName),
// }),
// { ...row }
// )
// );
2022-05-27 15:10:06 +10:00
}, [tableRows]) ?? [];
2022-05-19 16:37:56 +10:00
// const [selectedRowsSet, setSelectedRowsSet] = useState<Set<React.Key>>();
// const [selectedRows, setSelectedRows] = useState<any[]>([]);
2022-05-19 16:37:56 +10:00
// 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<HTMLDivElement>) => {
const target = event.target as HTMLDivElement;
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
);
2022-05-19 16:37:56 +10:00
const [showLeftScrollDivider, setShowLeftScrollDivider] = useState(false);
2022-05-19 16:37:56 +10:00
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 (
<>
{/* <Suspense fallback={<Loading message="Loading header" />}>
<Hotkeys selectedCell={selectedCell} />
</Suspense> */}
<TableContainer rowHeight={rowHeight}>
2022-05-19 16:37:56 +10:00
<DndProvider backend={HTML5Backend}>
{showLeftScrollDivider && <div className="left-scroll-divider" />}
2022-05-19 16:37:56 +10:00
<DataGrid
onColumnResize={handleResize}
onScroll={handleScroll}
2022-06-02 16:57:44 +10:00
ref={(handle) => {
if (dataGridRef) dataGridRef.current = handle;
}}
2022-05-19 16:37:56 +10:00
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={42}
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);
// }}
2022-05-19 16:37:56 +10:00
// onRowsChange={() => {
//console.log('onRowsChange',rows)
// }}
// FIXME: onFill={(e) => {
// console.log("onFill", e);
// const { columnKey, sourceRow, targetRows } = e;
// if (updateCell)
// targetRows.forEach((row) =>
// updateCell(row.ref, columnKey, sourceRow[columnKey])
// );
// return [];
// }}
onPaste={(e) => {
const value = e.sourceRow[e.sourceColumnKey];
updateField({
path: e.targetRow._rowy_ref.path,
fieldName: e.targetColumnKey,
value,
});
}}
onSelectedCellChange={({ rowIdx, idx }) =>
setSelectedCell({
path: rows[rowIdx]._rowy_ref.path,
columnKey: tableColumnsOrdered.filter((col) =>
userDocHiddenFields
? !userDocHiddenFields.includes(col.key)
: true
)[idx].key,
2022-06-02 16:57:44 +10:00
})
}
2022-05-19 16:37:56 +10:00
/>
</DndProvider>
2022-05-27 15:10:06 +10:00
{tableRows.length === 0 && (
<EmptyState
Icon={AddRowIcon}
message="Add a row to get started"
description={
<div>
<br />
<AddRow />
</div>
}
style={{
position: "absolute",
inset: 0,
top: COLUMN_HEADER_HEIGHT,
height: "auto",
}}
/>
)}
{tableNextPage.loading && <LinearProgress />}
2022-05-19 16:37:56 +10:00
</TableContainer>
{/* <ContextMenu />
<BulkActions
selectedRows={selectedRows}
columns={columns}
clearSelection={() => {
setSelectedRowsSet(new Set());
setSelectedRows([]);
}}
/> */}
</>
);
}