"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 { Badge } from "@/components/ui/badge"; 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 type { Document, DocumentType } from "@/hooks/use-documents"; interface DocumentsDataTableProps { documents: Document[]; onSelectionChange: (documents: Document[]) => void; onDone: () => void; initialSelectedDocuments?: Document[]; } const DOCUMENT_TYPES: (DocumentType | "ALL")[] = [ "ALL", "FILE", "EXTENSION", "CRAWLED_URL", "YOUTUBE_VIDEO", "SLACK_CONNECTOR", "NOTION_CONNECTOR", "GITHUB_CONNECTOR", "LINEAR_CONNECTOR", "DISCORD_CONNECTOR", "JIRA_CONNECTOR", "CONFLUENCE_CONNECTOR", ]; const getDocumentTypeColor = (type: DocumentType) => { const colors = { FILE: "bg-blue-50 text-blue-700 border-blue-200", EXTENSION: "bg-green-50 text-green-700 border-green-200", CRAWLED_URL: "bg-purple-50 text-purple-700 border-purple-200", YOUTUBE_VIDEO: "bg-red-50 text-red-700 border-red-200", SLACK_CONNECTOR: "bg-yellow-50 text-yellow-700 border-yellow-200", NOTION_CONNECTOR: "bg-indigo-50 text-indigo-700 border-indigo-200", GITHUB_CONNECTOR: "bg-gray-50 text-gray-700 border-gray-200", LINEAR_CONNECTOR: "bg-pink-50 text-pink-700 border-pink-200", DISCORD_CONNECTOR: "bg-violet-50 text-violet-700 border-violet-200", JIRA_CONNECTOR: "bg-orange-50 text-orange-700 border-orange-200", CONFLUENCE_CONNECTOR: "bg-teal-50 text-teal-700 border-teal-200", }; return colors[type] || "bg-gray-50 text-gray-700 border-gray-200"; }; 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 ( {type.replace(/_/g, " ")} {type.split("_")[0]} ); }, 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 = [], }: 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) => { selection[selectedDoc.id] = true; }); return selection; }, [documents, initialSelectedDocuments]); const [rowSelection, setRowSelection] = useState>({}); // Only update row selection when initialRowSelection actually changes and is not empty useEffect(() => { const hasChanges = JSON.stringify(rowSelection) !== JSON.stringify(initialRowSelection); if (hasChanges && Object.keys(initialRowSelection).length > 0) { setRowSelection(initialRowSelection); } }, [initialRowSelection]); // Initialize row selection on mount useEffect(() => { if (Object.keys(rowSelection).length === 0 && Object.keys(initialRowSelection).length > 0) { setRowSelection(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(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, onRowSelectionChange: setRowSelection, initialState: { pagination: { pageSize: 10 } }, state: { sorting, columnFilters, columnVisibility, rowSelection }, }); useEffect(() => { const selectedRows = table.getFilteredSelectedRowModel().rows; const selectedDocuments = selectedRows.map((row) => row.original); onSelectionChange(selectedDocuments); }, [rowSelection, onSelectionChange, 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 {table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}{" "} to{" "} {Math.min( (table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize, table.getFilteredRowModel().rows.length )}{" "} of {table.getFilteredRowModel().rows.length} documents
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
); }