import type { CheckboxProps } from '@/components/ui/Checkbox' import Checkbox from '@/components/ui/Checkbox' import Pagination from '@/components/ui/Pagination' import Select from '@/components/ui/Select' import type { SkeletonProps } from '@/components/ui/Skeleton' import Table from '@/components/ui/Table' import { CellContext, ColumnDef, ColumnSort, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, Row, useReactTable, } from '@tanstack/react-table' import classNames from 'classnames' import type { ChangeEvent, ForwardedRef } from 'react' import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' import TableRowSkeleton from './loaders/TableRowSkeleton' import Loading from './Loading' export type OnSortParam = { order: 'asc' | 'desc' | ''; key: string | number } type DataTableProps = { columns: ColumnDef[] data?: unknown[] loading?: boolean onCheckBoxChange?: (checked: boolean, row: T) => void onIndeterminateCheckBoxChange?: (checked: boolean, rows: Row[]) => void onPaginationChange?: (page: number) => void onSelectChange?: (num: number) => void onSort?: (sort: OnSortParam) => void pageSizes?: number[] selectable?: boolean skeletonAvatarColumns?: number[] skeletonAvatarProps?: SkeletonProps pagingData?: { total: number pageIndex: number pageSize: number } } type CheckBoxChangeEvent = ChangeEvent interface IndeterminateCheckboxProps extends Omit { onChange: (event: CheckBoxChangeEvent) => void indeterminate: boolean onCheckBoxChange?: (event: CheckBoxChangeEvent) => void onIndeterminateCheckBoxChange?: (event: CheckBoxChangeEvent) => void } const { Tr, Th, Td, THead, TBody, Sorter } = Table const IndeterminateCheckbox = (props: IndeterminateCheckboxProps) => { const { indeterminate, onChange, onCheckBoxChange, onIndeterminateCheckBoxChange, ...rest } = props const ref = useRef(null) useEffect(() => { if (typeof indeterminate === 'boolean' && ref.current) { ref.current.indeterminate = !rest.checked && indeterminate } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ref, indeterminate]) const handleChange = (e: CheckBoxChangeEvent) => { onChange(e) onCheckBoxChange?.(e) onIndeterminateCheckBoxChange?.(e) } return handleChange(e)} {...rest} /> } export type DataTableResetHandle = { resetSorting: () => void resetSelected: () => void } function DataTableBase(props: DataTableProps, ref: ForwardedRef) { const { skeletonAvatarColumns, columns: columnsProp = [], data = [], loading = false, onCheckBoxChange, onIndeterminateCheckBoxChange, onPaginationChange, onSelectChange, onSort, pageSizes = [10, 25, 50, 100], selectable = false, skeletonAvatarProps, pagingData = { total: 0, pageIndex: 1, pageSize: 10, }, } = props const { pageSize, pageIndex, total } = pagingData const [sorting, setSorting] = useState(null) const pageSizeOption = useMemo( () => pageSizes.map((number) => ({ value: number, label: `${number} / page`, })), [pageSizes], ) const handleCheckBoxChange = (checked: boolean, row: T) => { if (!loading) { onCheckBoxChange?.(checked, row) } } const handleIndeterminateCheckBoxChange = (checked: boolean, rows: Row[]) => { if (!loading) { onIndeterminateCheckBoxChange?.(checked, rows) } } const handlePaginationChange = (page: number) => { if (!loading) { onPaginationChange?.(page) } } const handleSelectChange = (value?: number) => { if (!loading) { onSelectChange?.(Number(value)) } } useEffect(() => { if (Array.isArray(sorting)) { const sortOrder = sorting.length > 0 ? (sorting[0].desc ? 'desc' : 'asc') : '' const id = sorting.length > 0 ? sorting[0].id : '' onSort?.({ order: sortOrder, key: id }) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [sorting]) const finalColumns: ColumnDef[] = useMemo(() => { const columns = columnsProp if (selectable) { return [ { id: 'select', header: ({ table }) => ( { handleIndeterminateCheckBoxChange(e.target.checked, table.getRowModel().rows) }} /> ), cell: ({ row }) => ( handleCheckBoxChange(e.target.checked, row.original)} /> ), }, ...columns, ] } return columns // eslint-disable-next-line react-hooks/exhaustive-deps }, [columnsProp, selectable]) const table = useReactTable({ data, columns: finalColumns as ColumnDef[], getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), manualPagination: true, manualSorting: true, onSortingChange: (sorter) => { setSorting(sorter as ColumnSort[]) }, // Satir secimi tablonun kendi durumunda tutulur. Disaridan kontrol edilen bir // `rowSelection` degeri `onRowSelectionChange` olmadan degisimi yutuyor, onay kutusu // hicbir zaman isaretlenmiyordu; secilen satirlari cagiran taraf `onCheckBoxChange` // ile zaten kendi durumunda biriktiriyor. state: { sorting: sorting as ColumnSort[], }, }) const resetSorting = () => { table.resetSorting() } const resetSelected = () => { table.toggleAllRowsSelected(false) } useImperativeHandle(ref, () => ({ resetSorting, resetSelected, })) return ( {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( ) })} ))} {loading && data.length === 0 ? ( ).length} rows={pagingData.pageSize} avatarInColumns={skeletonAvatarColumns} avatarProps={skeletonAvatarProps} /> ) : ( {table .getRowModel() .rows.slice(0, pageSize) .map((row) => { return ( {row.getVisibleCells().map((cell) => { return ( ) })} ) })} )}
{header.isPlaceholder ? null : (
{flexRender(header.column.columnDef.header, header.getContext())} {header.column.getCanSort() && ( )}
)}
{flexRender(cell.column.columnDef.cell, cell.getContext())}