Spaces:
Paused
Paused
| import { useState } from 'react'; | |
| import { | |
| ColumnFiltersState, | |
| RowData, | |
| SortingState, | |
| VisibilityState, | |
| flexRender, | |
| getCoreRowModel, | |
| getFacetedRowModel, | |
| getFacetedUniqueValues, | |
| getFilteredRowModel, | |
| getSortedRowModel, | |
| useReactTable, | |
| } from '@tanstack/react-table'; | |
| import { motion, AnimatePresence } from 'framer-motion'; | |
| import { useTranslation } from 'react-i18next'; | |
| import { useAnimatedList } from '@/hooks/useAnimatedList'; | |
| import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; | |
| import { TableSkeleton } from '@/components/ui/table-skeleton'; | |
| import { ServerSidePagination } from '@/components/server-side-pagination'; | |
| import type { DateTimeRangeValue } from '@/utils/date-range'; | |
| import { Thread, ThreadConnection } from '../data/schema'; | |
| import { ThreadsTableToolbar } from './data-table-toolbar'; | |
| import { useThreadsColumns } from './threads-columns'; | |
| const MotionTableRow = motion(TableRow); | |
| declare module '@tanstack/react-table' { | |
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | |
| interface ColumnMeta<TData extends RowData, TValue> { | |
| className: string; | |
| } | |
| } | |
| interface ThreadsTableProps { | |
| data: Thread[]; | |
| loading?: boolean; | |
| pageInfo?: ThreadConnection['pageInfo']; | |
| pageSize: number; | |
| totalCount?: number; | |
| dateRange?: DateTimeRangeValue; | |
| threadIdFilter: string; | |
| onNextPage: () => void; | |
| onPreviousPage: () => void; | |
| onPageSizeChange: (pageSize: number) => void; | |
| onDateRangeChange: (range: DateTimeRangeValue | undefined) => void; | |
| onThreadIdFilterChange: (threadId: string) => void; | |
| onRefresh: () => void; | |
| showRefresh: boolean; | |
| autoRefresh?: boolean; | |
| onAutoRefreshChange?: (enabled: boolean) => void; | |
| } | |
| export function ThreadsTable({ | |
| data, | |
| loading, | |
| pageInfo, | |
| totalCount, | |
| pageSize, | |
| dateRange, | |
| threadIdFilter, | |
| onNextPage, | |
| onPreviousPage, | |
| onPageSizeChange, | |
| onDateRangeChange, | |
| onThreadIdFilterChange, | |
| onRefresh, | |
| showRefresh, | |
| autoRefresh = false, | |
| onAutoRefreshChange, | |
| }: ThreadsTableProps) { | |
| const { t } = useTranslation(); | |
| const threadsColumns = useThreadsColumns(); | |
| const [sorting, setSorting] = useState<SortingState>([]); | |
| const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]); | |
| const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({}); | |
| const [rowSelection, setRowSelection] = useState({}); | |
| const displayedData = useAnimatedList(data, autoRefresh); | |
| const table = useReactTable({ | |
| data: displayedData, | |
| getRowId: (row) => row.id, | |
| columns: threadsColumns, | |
| state: { | |
| sorting, | |
| columnFilters, | |
| columnVisibility, | |
| rowSelection, | |
| }, | |
| enableRowSelection: true, | |
| onRowSelectionChange: setRowSelection, | |
| onSortingChange: setSorting, | |
| onColumnFiltersChange: setColumnFilters, | |
| onColumnVisibilityChange: setColumnVisibility, | |
| getCoreRowModel: getCoreRowModel(), | |
| getFilteredRowModel: getFilteredRowModel(), | |
| getSortedRowModel: getSortedRowModel(), | |
| getFacetedRowModel: getFacetedRowModel(), | |
| getFacetedUniqueValues: getFacetedUniqueValues(), | |
| manualPagination: true, | |
| manualFiltering: true, | |
| }); | |
| return ( | |
| <div className='flex flex-1 flex-col overflow-hidden'> | |
| <ThreadsTableToolbar | |
| table={table} | |
| dateRange={dateRange} | |
| onDateRangeChange={onDateRangeChange} | |
| threadIdFilter={threadIdFilter} | |
| onThreadIdFilterChange={onThreadIdFilterChange} | |
| onRefresh={onRefresh} | |
| showRefresh={showRefresh} | |
| autoRefresh={autoRefresh} | |
| onAutoRefreshChange={onAutoRefreshChange} | |
| /> | |
| <div className='shadow-soft relative mt-4 flex-1 overflow-auto overflow-x-hidden rounded-2xl border border-[var(--table-border)]'> | |
| <Table data-testid='threads-table' className='border-separate border-spacing-0 rounded-2xl bg-[var(--table-background)]'> | |
| <TableHeader className='sticky top-0 z-20 bg-[var(--table-header)] shadow-sm'> | |
| {table.getHeaderGroups().map((headerGroup) => ( | |
| <TableRow key={headerGroup.id} className='group/row border-0'> | |
| {headerGroup.headers.map((header) => ( | |
| <TableHead | |
| key={header.id} | |
| colSpan={header.colSpan} | |
| className={`${header.column.columnDef.meta?.className ?? ''} text-muted-foreground border-0 text-xs font-semibold tracking-wider uppercase`} | |
| > | |
| {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} | |
| </TableHead> | |
| ))} | |
| </TableRow> | |
| ))} | |
| </TableHeader> | |
| <TableBody className='space-y-1 !bg-[var(--table-background)] p-2'> | |
| {loading ? ( | |
| <TableSkeleton rows={pageSize} columns={threadsColumns.length} /> | |
| ) : table.getRowModel().rows?.length ? ( | |
| <AnimatePresence initial={false} mode='popLayout'> | |
| {table.getRowModel().rows.map((row) => ( | |
| <MotionTableRow | |
| key={row.id} | |
| data-state={row.getIsSelected() && 'selected'} | |
| initial={{ opacity: 0, y: -20, height: 0 }} | |
| animate={{ opacity: 1, y: 0, height: 'auto' }} | |
| exit={{ opacity: 0, height: 0 }} | |
| transition={{ | |
| type: 'spring', | |
| stiffness: 500, | |
| damping: 30, | |
| mass: 1, | |
| opacity: { duration: 0.2 }, | |
| }} | |
| layout | |
| className='group/row hover:bg-muted/50 data-[state=selected]:bg-muted' | |
| > | |
| {row.getVisibleCells().map((cell) => ( | |
| <TableCell | |
| key={cell.id} | |
| className={`${cell.column.columnDef.meta?.className ?? ''} border-b border-[var(--table-border)] py-3 group-last/row:border-0`} | |
| > | |
| {flexRender(cell.column.columnDef.cell, cell.getContext())} | |
| </TableCell> | |
| ))} | |
| </MotionTableRow> | |
| ))} | |
| </AnimatePresence> | |
| ) : ( | |
| <TableRow className='!bg-[var(--table-background)]'> | |
| <TableCell colSpan={threadsColumns.length} className='h-24 !bg-[var(--table-background)] text-center'> | |
| {t('common.noData')} | |
| </TableCell> | |
| </TableRow> | |
| )} | |
| </TableBody> | |
| </Table> | |
| </div> | |
| <div className='mt-4 flex-shrink-0'> | |
| <ServerSidePagination | |
| pageInfo={pageInfo} | |
| pageSize={pageSize} | |
| dataLength={data.length} | |
| totalCount={totalCount} | |
| selectedRows={table.getFilteredSelectedRowModel().rows.length} | |
| onNextPage={onNextPage} | |
| onPreviousPage={onPreviousPage} | |
| onPageSizeChange={onPageSizeChange} | |
| /> | |
| </div> | |
| </div> | |
| ); | |
| } | |