Portal/rsconcept/frontend/src/components/data-table/data-table.tsx

194 lines
5.3 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
2025-03-12 21:07:01 +03:00
import { useMemo, useState } from 'react';
2024-06-07 20:17:03 +03:00
import {
2025-02-20 20:22:05 +03:00
type ColumnSort,
2024-06-07 20:17:03 +03:00
createColumnHelper,
2025-02-20 20:22:05 +03:00
type RowData,
2024-06-07 20:17:03 +03:00
type RowSelectionState,
2025-02-20 20:22:05 +03:00
type TableOptions,
2024-06-07 20:17:03 +03:00
type VisibilityState
} from '@tanstack/react-table';
2025-02-22 14:03:13 +03:00
import { type Styling } from '../props';
2025-04-12 21:47:46 +03:00
import { cn } from '../utils';
2025-02-12 21:36:03 +03:00
2025-03-12 11:54:32 +03:00
import { DefaultNoData } from './default-no-data';
import { PaginationTools } from './pagination-tools';
import { TableBody } from './table-body';
import { TableFooter } from './table-footer';
import { TableHeader } from './table-header';
2025-03-12 21:07:01 +03:00
import { useDataTable } from './use-data-table';
2024-06-07 20:17:03 +03:00
2025-03-13 23:20:52 +03:00
export { 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>
2025-02-20 20:22:05 +03:00
extends Styling,
2024-06-07 20:17:03 +03:00
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. */
2025-02-22 14:03:13 +03:00
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element>) => void;
/** Callback to be called when a row is double clicked. */
2025-02-22 14:03:13 +03:00
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element>) => void;
2024-06-07 20:17:03 +03:00
/** 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
*/
2025-02-19 23:29:45 +03:00
export function DataTable<TData extends RowData>({
2024-06-07 20:17:03 +03:00
id,
style,
className,
dense,
rows,
contentHeight = '1.1875rem',
headPosition,
conditionalRowStyles,
noFooter,
noHeader,
onRowClicked,
onRowDoubleClicked,
noDataComponent,
onChangePaginationOption,
2025-03-13 13:30:31 +03:00
paginationPerPage,
2024-06-07 20:17:03 +03:00
paginationOptions = [10, 20, 30, 40, 50],
...restProps
}: DataTableProps<TData>) {
const [lastSelected, setLastSelected] = useState<string | null>(null);
2024-06-07 20:17:03 +03:00
2025-03-13 13:30:31 +03:00
const table = useDataTable({ paginationPerPage, ...restProps });
2025-03-12 21:07:01 +03:00
const isEmpty = table.getRowModel().rows.length === 0;
2024-06-07 20:17:03 +03:00
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(() => {
2025-03-12 21:07:01 +03:00
const headers = table.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;
2025-03-12 21:07:01 +03:00
}, [table]);
2024-06-07 20:17:03 +03:00
return (
2025-03-12 23:38:24 +03:00
<div
tabIndex={-1}
id={id}
2025-04-12 21:47:46 +03:00
className={cn('table-auto', className)}
2025-03-12 23:38:24 +03:00
style={{ minHeight: fixedSize, maxHeight: fixedSize, ...style }}
>
<table className='w-full' style={{ ...columnSizeVars }}>
2024-06-07 20:17:03 +03:00
{!noHeader ? (
2025-03-12 21:07:01 +03:00
<TableHeader table={table} headPosition={headPosition} resetLastSelected={() => setLastSelected(null)} />
2024-06-07 20:17:03 +03:00
) : null}
<TableBody
2025-03-12 21:07:01 +03:00
table={table}
2024-06-07 20:17:03 +03:00
dense={dense}
noHeader={noHeader}
conditionalRowStyles={conditionalRowStyles}
lastSelected={lastSelected}
onChangeLastSelected={setLastSelected}
2024-06-07 20:17:03 +03:00
onRowClicked={onRowClicked}
onRowDoubleClicked={onRowDoubleClicked}
/>
2025-03-12 21:07:01 +03:00
{!noFooter ? <TableFooter table={table} /> : null}
2024-06-07 20:17:03 +03:00
</table>
2025-03-13 13:30:31 +03:00
{!!paginationPerPage && !isEmpty ? (
2024-06-07 20:17:03 +03:00
<PaginationTools
id={id ? `${id}__pagination` : undefined}
2025-03-12 21:07:01 +03:00
table={table}
onChangePaginationOption={onChangePaginationOption}
2024-06-07 20:17:03 +03:00
paginationOptions={paginationOptions}
/>
) : null}
{isEmpty ? noDataComponent ?? <DefaultNoData /> : null}
</div>
);
}