ConceptPortal-public/rsconcept/frontend/src/components/ui/DataTable/DataTable.tsx

183 lines
5.0 KiB
TypeScript
Raw Normal View History

'use client';
import {
2023-12-17 20:19:28 +03:00
ColumnSort,
2023-12-28 14:04:44 +03:00
createColumnHelper,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
PaginationState,
RowData,
type RowSelectionState,
SortingState,
TableOptions,
useReactTable,
type VisibilityState
} from '@tanstack/react-table';
import { useMemo, useState } from 'react';
2024-03-20 15:27:32 +03:00
import { CProps } from '../../props';
import DefaultNoData from './DefaultNoData';
import PaginationTools from './PaginationTools';
2023-12-17 20:19:28 +03:00
import TableBody from './TableBody';
import TableFooter from './TableFooter';
import TableHeader from './TableHeader';
2024-08-06 14:39:00 +03:00
export { type ColumnSort, createColumnHelper, type RowSelectionState, type VisibilityState };
export interface IConditionalStyle<TData> {
2023-12-28 14:04:44 +03:00
when: (rowData: TData) => boolean;
style: React.CSSProperties;
}
export interface DataTableProps<TData extends RowData>
2023-12-28 14:04:44 +03:00
extends CProps.Styling,
Pick<TableOptions<TData>, 'data' | 'columns' | 'onRowSelectionChange' | 'onColumnVisibilityChange'> {
id?: string;
2023-12-28 14:04:44 +03:00
dense?: boolean;
rows?: number;
2024-03-20 15:03:53 +03:00
contentHeight?: string;
2023-12-28 14:04:44 +03:00
headPosition?: string;
noHeader?: boolean;
noFooter?: boolean;
conditionalRowStyles?: IConditionalStyle<TData>[];
noDataComponent?: React.ReactNode;
onRowClicked?: (rowData: TData, event: CProps.EventMouse) => void;
onRowDoubleClicked?: (rowData: TData, event: CProps.EventMouse) => void;
2023-12-28 14:04:44 +03:00
enableRowSelection?: boolean;
rowSelection?: RowSelectionState;
enableHiding?: boolean;
columnVisibility?: VisibilityState;
enablePagination?: boolean;
paginationPerPage?: number;
paginationOptions?: number[];
onChangePaginationOption?: (newValue: number) => void;
enableSorting?: boolean;
initialSorting?: ColumnSort;
}
/**
* UI element: data representation as a table.
2023-12-28 14:04:44 +03:00
*
* @param headPosition - Top position of sticky header (0 if no other sticky elements are present).
* No sticky header if omitted
2023-12-28 14:04:44 +03:00
*/
function DataTable<TData extends RowData>({
id,
2023-12-28 14:04:44 +03:00
style,
className,
dense,
rows,
2024-03-20 15:03:53 +03:00
contentHeight = '1.1875rem',
2023-12-28 14:04:44 +03:00
headPosition,
conditionalRowStyles,
noFooter,
noHeader,
onRowClicked,
onRowDoubleClicked,
noDataComponent,
enableRowSelection,
rowSelection,
enableHiding,
columnVisibility,
enableSorting,
initialSorting,
enablePagination,
2023-12-28 14:04:44 +03:00
paginationPerPage = 10,
paginationOptions = [10, 20, 30, 40, 50],
onChangePaginationOption,
...restProps
}: DataTableProps<TData>) {
const [sorting, setSorting] = useState<SortingState>(initialSorting ? [initialSorting] : []);
2024-03-25 23:10:29 +03:00
const [lastSelected, setLastSelected] = useState<string | undefined>(undefined);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
2023-12-28 14:04:44 +03:00
pageSize: paginationPerPage
});
2023-12-28 14:04:44 +03:00
const tableImpl = useReactTable({
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
getPaginationRowModel: enablePagination ? getPaginationRowModel() : undefined,
2023-12-28 14:04:44 +03:00
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) {
2024-03-20 15:03:53 +03:00
return `calc(2px + (2px + ${contentHeight} + 0.5rem)*${rows} + ${noHeader ? '0px' : '(2px + 2.1875rem)'})`;
} else {
2024-03-20 15:03:53 +03:00
return `calc(2px + (2px + ${contentHeight} + 1rem)*${rows + (noHeader ? 0 : 1)})`;
}
2024-03-20 15:03:53 +03:00
}, [rows, dense, noHeader, contentHeight]);
return (
2024-05-11 20:53:36 +03:00
<div tabIndex={-1} id={id} className={className} style={{ minHeight: fixedSize, maxHeight: fixedSize, ...style }}>
2023-12-28 14:04:44 +03:00
<table className='w-full'>
{!noHeader ? (
<TableHeader
table={tableImpl}
enableRowSelection={enableRowSelection}
enableSorting={enableSorting}
headPosition={headPosition}
2024-03-25 23:10:29 +03:00
setLastSelected={setLastSelected}
2023-12-28 14:04:44 +03:00
/>
) : null}
<TableBody
table={tableImpl}
dense={dense}
2024-05-02 21:19:23 +03:00
noHeader={noHeader}
2023-12-28 14:04:44 +03:00
conditionalRowStyles={conditionalRowStyles}
enableRowSelection={enableRowSelection}
2024-03-25 23:10:29 +03:00
lastSelected={lastSelected}
setLastSelected={setLastSelected}
2023-12-28 14:04:44 +03:00
onRowClicked={onRowClicked}
onRowDoubleClicked={onRowDoubleClicked}
/>
{!noFooter ? <TableFooter table={tableImpl} /> : null}
</table>
{enablePagination && !isEmpty ? (
<PaginationTools
id={id ? `${id}__pagination` : undefined}
2023-12-28 14:04:44 +03:00
table={tableImpl}
paginationOptions={paginationOptions}
onChangePaginationOption={onChangePaginationOption}
/>
) : null}
{isEmpty ? noDataComponent ?? <DefaultNoData /> : null}
</div>
);
}
2023-12-28 14:04:44 +03:00
export default DataTable;