Files
EventHubFrontAdmin/src/components/data-table/DataTable.tsx
T
aleksey fd2c1f10ad
CI / test (push) Failing after 3m9s
CI / push-image (push) Has been skipped
CI / ci-done (push) Failing after 0s
feat(admin): Control Center redesign, Explore dual-view и полный RU/EN i18n
Refs EventHub/EventHubFrontAdmin#32
2026-07-14 17:58:33 +03:00

159 lines
6.4 KiB
TypeScript

import {
ColumnDef,
flexRender,
getCoreRowModel,
SortingState,
useReactTable,
} from '@tanstack/react-table';
import { useTranslation } from 'react-i18next';
import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react';
import { Skeleton } from '@/components/ui/skeleton';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { TablePaginationBar } from '@/components/explore/TablePaginationBar';
import { DEFAULT_TABLE_PAGE_SIZE } from '@/utils/tablePagination';
export interface ServerTableParams {
offset?: number;
limit?: number;
sort?: string;
order?: 'asc' | 'desc';
}
interface DataTableProps<TData, TParams extends ServerTableParams> {
columns: ColumnDef<TData, unknown>[];
data: TData[];
total?: number;
isLoading?: boolean;
tableKey: string;
params: TParams;
onParamsChange: (params: TParams) => void;
emptyMessage?: string;
}
export function DataTable<TData, TParams extends ServerTableParams>({
columns,
data,
total,
isLoading,
tableKey,
params,
onParamsChange,
emptyMessage,
}: DataTableProps<TData, TParams>) {
const { t } = useTranslation();
const empty = emptyMessage ?? t('common.noData');
const sorting: SortingState =
params.sort && params.order ? [{ id: params.sort, desc: params.order === 'desc' }] : [];
const table = useReactTable({
data,
columns,
pageCount: total != null ? Math.ceil(total / (params.limit || DEFAULT_TABLE_PAGE_SIZE)) : -1,
state: {
sorting,
pagination: {
pageIndex: Math.floor((params.offset || 0) / (params.limit || DEFAULT_TABLE_PAGE_SIZE)),
pageSize: params.limit || DEFAULT_TABLE_PAGE_SIZE,
},
},
manualPagination: true,
manualSorting: true,
getCoreRowModel: getCoreRowModel(),
onSortingChange: (updater) => {
const next = typeof updater === 'function' ? updater(sorting) : updater;
const first = next[0];
onParamsChange({
...params,
sort: first?.id ?? params.sort,
order: first ? (first.desc ? 'desc' : 'asc') : params.order,
offset: 0,
});
},
});
return (
<div className="space-y-3">
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
const canSort = header.column.getCanSort();
const sorted = header.column.getIsSorted();
return (
<TableHead key={header.id} style={{ width: header.getSize() || undefined }}>
{header.isPlaceholder ? null : canSort ? (
<button
type="button"
className="inline-flex items-center gap-1 font-medium hover:text-foreground"
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{sorted === 'asc' ? (
<ArrowUp className="h-3.5 w-3.5" />
) : sorted === 'desc' ? (
<ArrowDown className="h-3.5 w-3.5" />
) : (
<ArrowUpDown className="h-3.5 w-3.5 opacity-40" />
)}
</button>
) : (
flexRender(header.column.columnDef.header, header.getContext())
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{isLoading ? (
Array.from({ length: 5 }).map((_, i) => (
<TableRow key={`sk-${i}`}>
{columns.map((_, j) => (
<TableCell key={j}>
<Skeleton className="h-4 w-full" />
</TableCell>
))}
</TableRow>
))
) : table.getRowModel().rows.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center text-muted-foreground">
{empty}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<TablePaginationBar
tableKey={tableKey}
params={params}
total={total}
dataLength={data.length}
onParamsChange={onParamsChange}
/>
</div>
);
}