Portal/rsconcept/frontend/src/components/DataTable/DataTable.tsx

233 lines
6.4 KiB
TypeScript
Raw Normal View History

2024-06-07 20:17:03 +03:00
'use client';
'use no memo';
2024-06-07 20:17:03 +03:00
import {
ColumnSort,
createColumnHelper,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
PaginationState,
RowData,
type RowSelectionState,
SortingState,
TableOptions,
useReactTable,
type VisibilityState
} from '@tanstack/react-table';
import { useMemo, useState } from 'react';
import { CProps } from '../props';
2024-06-07 20:17:03 +03:00
import DefaultNoData from './DefaultNoData';
import PaginationTools from './PaginationTools';
import TableBody from './TableBody';
import TableFooter from './TableFooter';
import TableHeader from './TableHeader';
2024-08-06 14:38:10 +03:00
export { type ColumnSort, createColumnHelper, type RowSelectionState, type VisibilityState };
2024-06-07 20:17:03 +03:00
/** Style to conditionally apply to rows. */
2024-06-07 20:17:03 +03:00
export interface IConditionalStyle<TData> {
/** Callback to determine if the style should be applied. */
2024-06-07 20:17:03 +03:00
when: (rowData: TData) => boolean;
/** Style to apply. */
2024-06-07 20:17:03 +03:00
style: React.CSSProperties;
}
export interface DataTableProps<TData extends RowData>
extends CProps.Styling,
Pick<TableOptions<TData>, 'data' | 'columns' | 'onRowSelectionChange' | 'onColumnVisibilityChange'> {
/** Id of the component. */
2024-06-07 20:17:03 +03:00
id?: string;
/** Indicates that padding should be minimal. */
2024-06-07 20:17:03 +03:00
dense?: boolean;
/** Number of rows to display. */
2024-06-07 20:17:03 +03:00
rows?: number;
/** Height of the content. */
2024-06-07 20:17:03 +03:00
contentHeight?: string;
/** Top position of sticky header (0 if no other sticky elements are present). */
2024-06-07 20:17:03 +03:00
headPosition?: string;
/** Disable header. */
2024-06-07 20:17:03 +03:00
noHeader?: boolean;
/** Disable footer. */
2024-06-07 20:17:03 +03:00
noFooter?: boolean;
/** List of styles to conditionally apply to rows. */
2024-06-07 20:17:03 +03:00
conditionalRowStyles?: IConditionalStyle<TData>[];
/** Component to display when there is no data. */
2024-06-07 20:17:03 +03:00
noDataComponent?: React.ReactNode;
/** Callback to be called when a row is clicked. */
2024-06-07 20:17:03 +03:00
onRowClicked?: (rowData: TData, event: CProps.EventMouse) => void;
/** Callback to be called when a row is double clicked. */
2024-06-07 20:17:03 +03:00
onRowDoubleClicked?: (rowData: TData, event: CProps.EventMouse) => void;
/** Enable row selection. */
2024-06-07 20:17:03 +03:00
enableRowSelection?: boolean;
/** Current row selection. */
2024-06-07 20:17:03 +03:00
rowSelection?: RowSelectionState;
/** Enable hiding of columns. */
2024-06-07 20:17:03 +03:00
enableHiding?: boolean;
/** Current column visibility. */
2024-06-07 20:17:03 +03:00
columnVisibility?: VisibilityState;
/** Enable pagination. */
2024-06-07 20:17:03 +03:00
enablePagination?: boolean;
/** Number of rows per page. */
2024-06-07 20:17:03 +03:00
paginationPerPage?: number;
/** List of options to choose from for pagination. */
2024-06-07 20:17:03 +03:00
paginationOptions?: number[];
/** Callback to be called when the pagination option is changed. */
2024-06-07 20:17:03 +03:00
onChangePaginationOption?: (newValue: number) => void;
/** Enable sorting. */
2024-06-07 20:17:03 +03:00
enableSorting?: boolean;
/** Initial sorting. */
2024-06-07 20:17:03 +03:00
initialSorting?: ColumnSort;
}
/**
* Dta representation as a table.
2024-06-07 20:17:03 +03:00
*
* @param headPosition - Top position of sticky header (0 if no other sticky elements are present).
* No sticky header if omitted
*/
function DataTable<TData extends RowData>({
id,
style,
className,
dense,
rows,
contentHeight = '1.1875rem',
headPosition,
conditionalRowStyles,
noFooter,
noHeader,
onRowClicked,
onRowDoubleClicked,
noDataComponent,
enableRowSelection,
rowSelection,
enableHiding,
columnVisibility,
enableSorting,
initialSorting,
enablePagination,
paginationPerPage = 10,
paginationOptions = [10, 20, 30, 40, 50],
onChangePaginationOption,
...restProps
}: DataTableProps<TData>) {
const [sorting, setSorting] = useState<SortingState>(initialSorting ? [initialSorting] : []);
const [lastSelected, setLastSelected] = useState<string | undefined>(undefined);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: paginationPerPage
});
const tableImpl = useReactTable({
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
getPaginationRowModel: enablePagination ? getPaginationRowModel() : undefined,
state: {
pagination: pagination,
sorting: sorting,
rowSelection: rowSelection ?? {},
columnVisibility: columnVisibility ?? {}
},
enableHiding: enableHiding,
onPaginationChange: enablePagination ? setPagination : undefined,
onSortingChange: enableSorting ? setSorting : undefined,
enableMultiRowSelection: enableRowSelection,
...restProps
});
const isEmpty = tableImpl.getRowModel().rows.length === 0;
const fixedSize = useMemo(() => {
if (!rows) {
return undefined;
}
if (dense) {
return `calc(2px + (2px + ${contentHeight} + 0.5rem)*${rows} + ${noHeader ? '0px' : '(2px + 2.1875rem)'})`;
} else {
return `calc(2px + (2px + ${contentHeight} + 1rem)*${rows + (noHeader ? 0 : 1)})`;
}
}, [rows, dense, noHeader, contentHeight]);
const columnSizeVars = useMemo(() => {
const headers = tableImpl.getFlatHeaders();
const colSizes: Record<string, number> = {};
for (const header of headers) {
colSizes[`--header-${header.id}-size`] = header.getSize();
colSizes[`--col-${header.column.id}-size`] = header.column.getSize();
}
return colSizes;
}, [tableImpl]);
2024-06-07 20:17:03 +03:00
return (
<div tabIndex={-1} id={id} className={className} style={{ minHeight: fixedSize, maxHeight: fixedSize, ...style }}>
<table className='w-full' style={{ ...columnSizeVars }}>
2024-06-07 20:17:03 +03:00
{!noHeader ? (
<TableHeader
table={tableImpl}
enableRowSelection={enableRowSelection}
enableSorting={enableSorting}
headPosition={headPosition}
resetLastSelected={() => setLastSelected(undefined)}
2024-06-07 20:17:03 +03:00
/>
) : null}
<TableBody
table={tableImpl}
dense={dense}
noHeader={noHeader}
conditionalRowStyles={conditionalRowStyles}
enableRowSelection={enableRowSelection}
lastSelected={lastSelected}
onChangeLastSelected={setLastSelected}
2024-06-07 20:17:03 +03:00
onRowClicked={onRowClicked}
onRowDoubleClicked={onRowDoubleClicked}
/>
{!noFooter ? <TableFooter table={tableImpl} /> : null}
</table>
{enablePagination && !isEmpty ? (
<PaginationTools
id={id ? `${id}__pagination` : undefined}
table={tableImpl}
paginationOptions={paginationOptions}
onChangePaginationOption={onChangePaginationOption}
/>
) : null}
{isEmpty ? noDataComponent ?? <DefaultNoData /> : null}
</div>
);
}
export default DataTable;