import { Table, type HeaderCellProps } from './Table'; import { type FC, type Key, type ReactElement, type ReactNode } from 'react'; import 'server-only'; import { DataTableClient, DataTableClientCell, DataTableClientColumn, DataTableClientColumnSelection, DataTableClientRow, DataTableClientRows, DataTableDynamicClientColumn } from './DataTable.client'; import { isDefined, isTruthy } from '@gw2treasures/helper/is'; import type { Comparable, ComparableProperties } from './comparable-properties'; import { sortBy } from './helper'; export type DataTableRowFilterComponentProps = { children: ReactNode, index: number }; export type DataTableRowFilterComponent = FC; // table export interface DataTableProps { children: Array | DynamicColumnsReactElement | FooterReactElement | false>, rowFilter?: DataTableRowFilterComponent, collapsed?: boolean | number, initialSortBy?: string, initialSortOrder?: 'asc' | 'desc', } type ColumnReactElement = ReactElement, FC>>; type DynamicColumnsReactElement = ReactElement, FC>>; type FooterReactElement = ReactElement<{ children: ReactNode }, FC<{ children: ReactNode }>>; export interface DataTableColumnProps extends Pick { id: string, title: ReactNode, children: ((row: T, index: number) => ReactNode), sort?: (a: T, b: T, aIndex: number, bIndex: number) => number, sortBy?: ComparableProperties | ((row: T) => Comparable), hidden?: boolean, fixed?: boolean, colSpan?: number, width?: number | string, } export interface DataTableDynamicColumnsProps { id: string, title: ReactNode, children: (row: T, index: number) => ReactNode, headers: ReactNode, hidden?: boolean, fixed?: boolean, } export interface DataTableColumnSelectionProps { children: ReactNode, reset: ReactNode, } export interface DataTableFooterProps { children: ReactNode, } export type DataTable = { Table: FC>, Column: FC>, DynamicColumns: FC>, ColumnSelection: FC, Footer: FC, }; export function createDataTable(rows: T[], getRowKey: (row: T, index: number) => Key): DataTable { const datatableId = crypto.randomUUID(); const Column: FC> = () => { throw new Error('Only use DataTable.Column inside of DataTable.Table'); }; const DynamicColumns: FC> = () => { throw new Error('Only use DataTable.DynamicColumns inside of DataTable.Table'); }; const ColumnSelection: FC = ({ children, reset }) => { return {children}; }; const Footer: FC = ({ children }) => { return {children}; }; function isStaticColumn(child: ReactElement): child is ColumnReactElement { return child.type === Column; } return { Table: function DataTable({ children, rowFilter, collapsed = false, ...props }: DataTableProps) { const columnsOrFooter = children.filter(isTruthy); const footer = columnsOrFooter.filter(({ type }) => type === Footer); const columns = columnsOrFooter.filter((c) => !footer.includes(c)) as Array | DynamicColumnsReactElement>; if(columns.some((child) => child.type !== Column && child.type !== DynamicColumns)) { throw new Error('Column and DynamicColumns are the only allowed children of DataTable'); } const rowsWithIndex = rows.map((row, index) => ({ row, index })); const sortableColumns = Object.fromEntries(columns .filter(isStaticColumn) .filter((column) => isDefined(column.props.sort) || isDefined(column.props.sortBy)) .map((column) => { const columnOrder = rowsWithIndex .toSorted((a, b) => { const sort = column.props.sort ?? sortBy(column.props.sortBy!); return sort(a.row, b.row, a.index, b.index); }) .map(({ index }) => index); return [column.props.id, columnOrder]; }) ); return ( ({ id: column.props.id, title: <>{column.props.title}, hidden: !!column.props.hidden, fixed: !!column.props.fixed }))} {...props}> {columns.map((column) => isStaticColumn(column) ? ( {column.props.title} ) : ( {column.props.headers} ))} {rows.map((row, index) => { return ( {columns.map((column) => isStaticColumn(column) ? ( {column.props.children(row, index)} ) : ( {column.props.children(row, index)} ))} ); })} {footer.length > 0 && {footer}}
); }, Column, DynamicColumns, ColumnSelection, Footer, }; }