"use client"; import { type ColumnDef, type ColumnFiltersState, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, type SortingState, useReactTable, type VisibilityState, } from "@tanstack/react-table"; import { ArrowUpDown, Calendar, FileText, Search } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { EnumConnectorName } from "@/contracts/enums/connector"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { Document, DocumentType } from "@/hooks/use-documents"; interface DocumentsDataTableProps { documents: Document[]; onSelectionChange: (documents: Document[]) => void; onDone: () => void; initialSelectedDocuments?: Document[]; pageIndex?: number; pageSize?: number; onPageIndexChange?: (pageIndex: number) => void; onPageSizeChange?: (pageSize: number) => void; canNext?: boolean; } // Combine EnumConnectorName with additional document types const DOCUMENT_TYPES: (string | "ALL")[] = [ "ALL", "FILE", "EXTENSION", "CRAWLED_URL", "YOUTUBE_VIDEO", ...Object.values(EnumConnectorName), ]; const columns: ColumnDef[] = [ { id: "select", header: ({ table }) => ( { table.toggleAllPageRowsSelected(!!value); }} aria-label="Select all" /> ), cell: ({ row }) => ( { row.toggleSelected(!!value); }} aria-label="Select row" /> ), enableSorting: false, enableHiding: false, size: 40, }, { accessorKey: "title", header: ({ column }) => ( ), cell: ({ row }) => { const title = row.getValue("title") as string; return (
{title}
); }, }, { accessorKey: "document_type", header: "Type", cell: ({ row }) => { const type = row.getValue("document_type") as DocumentType; return (
{getConnectorIcon(type)}
); }, size: 80, meta: { className: "hidden sm:table-cell", }, }, { accessorKey: "content", header: "Preview", cell: ({ row }) => { const content = row.getValue("content") as string; return (
{content.substring(0, 30)}... {content.substring(0, 100)}...
); }, enableSorting: false, meta: { className: "hidden md:table-cell", }, }, { accessorKey: "created_at", header: ({ column }) => ( ), cell: ({ row }) => { const date = new Date(row.getValue("created_at")); return (
{date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", })} {date.toLocaleDateString("en-US", { month: "numeric", day: "numeric", })}
); }, size: 80, }, ]; export function DocumentsDataTable({ documents, onSelectionChange, onDone, initialSelectedDocuments = [], pageIndex = 0, pageSize = 100, onPageIndexChange, onPageSizeChange, canNext = false, }: DocumentsDataTableProps) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); const [columnVisibility, setColumnVisibility] = useState({}); const [documentTypeFilter, setDocumentTypeFilter] = useState( "ALL" ); // Memoize initial row selection to prevent infinite loops const initialRowSelection = useMemo(() => { if (!documents.length || !initialSelectedDocuments.length) return {}; const selection: Record = {}; initialSelectedDocuments.forEach((selectedDoc) => { // Find the document in the current documents array to get the correct row ID const docInCurrentList = documents.find( (doc) => doc.id === selectedDoc.id ); if (docInCurrentList) { selection[docInCurrentList.id.toString()] = true; } }); return selection; }, [documents, initialSelectedDocuments]); const [rowSelection, setRowSelection] = useState>( () => initialRowSelection ); // Update row selection when initial selection changes useEffect(() => { setRowSelection(initialRowSelection); }, [initialRowSelection]); const filteredDocuments = useMemo(() => { if (documentTypeFilter === "ALL") return documents; return documents.filter((doc) => doc.document_type === documentTypeFilter); }, [documents, documentTypeFilter]); const table = useReactTable({ data: filteredDocuments, columns, getRowId: (row) => row.id.toString(), onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, onRowSelectionChange: setRowSelection, // Disable internal pagination since we're handling it at the parent level manualPagination: true, pageCount: -1, enableRowSelection: true, state: { sorting, columnFilters, columnVisibility, rowSelection }, }); useEffect(() => { const selectedRows = table.getFilteredSelectedRowModel().rows; const selectedDocuments = selectedRows.map((row) => row.original); onSelectionChange(selectedDocuments); }, [rowSelection, table]); const handleClearAll = () => setRowSelection({}); const handleSelectPage = () => { const currentPageRows = table.getRowModel().rows; const newSelection = { ...rowSelection }; currentPageRows.forEach((row) => { newSelection[row.id] = true; }); setRowSelection(newSelection); }; const handleSelectAllFiltered = () => { const allFilteredRows = table.getFilteredRowModel().rows; const newSelection: Record = {}; allFilteredRows.forEach((row) => { newSelection[row.id] = true; }); setRowSelection(newSelection); }; const selectedCount = table.getFilteredSelectedRowModel().rows.length; const totalFiltered = table.getFilteredRowModel().rows.length; return (
{/* Header Controls */}
{/* Search and Filter Row */}
table.getColumn("title")?.setFilterValue(event.target.value) } className="pl-10 text-sm" />
{/* Action Controls Row */}
{selectedCount} of {totalFiltered} selected
{/* Table Container */}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} ))} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext() )} ))} )) ) : ( No documents found. )}
{/* Footer Pagination */}
Showing {pageIndex * pageSize + 1} to{" "} {Math.min( (pageIndex + 1) * pageSize, pageIndex * pageSize + documents.length )}{" "} of{" "} {canNext ? `${(pageIndex + 1) * pageSize}+` : `${pageIndex * pageSize + documents.length}`}{" "} documents
Page {pageIndex + 1} {canNext && +}
); }