"use client"; import React, { useState, useEffect } from "react"; import { ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, SortingState, RowSelectionState, } from "@tanstack/react-table"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Skeleton } from "@/components/ui/skeleton"; import { AlertCircle, Download, Trash2, RefreshCw } from "lucide-react"; import { Input } from "@/components/ui/input"; interface FileData { id: string; filename: string; mimetype: string; size: number; createdAt: string; updatedAt: string; } export function FilesTable() { const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState({}); const [files, setFiles] = useState([]); const [isUploading, setIsUploading] = useState(false); const queryClient = useQueryClient(); const { data, isLoading, isError, error, refetch } = useQuery({ queryKey: ["files"], queryFn: async () => { const response = await fetch("/api/files"); if (!response.ok) { throw new Error("Failed to fetch files"); } const data = await response.json(); return data.files as FileData[]; }, }); // Update local files state when data changes useEffect(() => { if (data) { setFiles(data); } }, [data]); 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, }, { accessorKey: "filename", header: "File Name", cell: ({ row }) => (
{row.getValue("filename")}
), }, { accessorKey: "mimetype", header: "Type", cell: ({ row }) => (
{row.getValue("mimetype")}
), }, { accessorKey: "size", header: "Size", cell: ({ row }) => { const bytes = row.getValue("size") as number; if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; }, }, { accessorKey: "createdAt", header: "Created", cell: ({ row }) => { const date = new Date(row.getValue("createdAt")); return date.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); }, }, { accessorKey: "updatedAt", header: "Updated", cell: ({ row }) => { const date = new Date(row.getValue("updatedAt")); return date.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); }, }, { id: "actions", header: "Actions", cell: ({ row }) => (
), enableSorting: false, }, ]; const table = useReactTable({ data: files, columns, state: { sorting, rowSelection, }, onSortingChange: setSorting, onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), enableRowSelection: true, }); const handleFileUpload = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; setIsUploading(true); const formData = new FormData(); formData.append("file", file); try { const response = await fetch("/api/upload", { method: "POST", body: formData, }); const result = await response.json(); if (result.success) { // Add new file to the beginning of the list setFiles(prevFiles => [result.file, ...prevFiles]); // Invalidate and refetch to ensure consistency await queryClient.invalidateQueries({ queryKey: ["files"] }); // Reset file input event.target.value = ''; } else { alert(`Upload failed: ${result.error || 'Unknown error'}`); } } catch (error) { console.error("Upload error:", error); alert("Failed to upload file"); } finally { setIsUploading(false); } }; const handleRefresh = () => { refetch(); }; const handleDownload = (fileId: string, filename: string) => { const downloadUrl = `/api/files/${fileId}`; const link = document.createElement('a'); link.href = downloadUrl; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const handleDownloadSelected = () => { const selectedRows = table.getSelectedRowModel().rows; if (selectedRows.length === 0) return; if (selectedRows.length === 1) { const selected = selectedRows[0]; handleDownload(selected.original.id, selected.original.filename); } else { selectedRows.forEach((row, index) => { setTimeout(() => { handleDownload(row.original.id, row.original.filename); }, index * 500); }); } }; const handleDeleteSelected = async () => { const selectedRows = table.getSelectedRowModel().rows; if (selectedRows.length === 0) return; const confirmDelete = window.confirm( `Are you sure you want to delete ${selectedRows.length} file${selectedRows.length > 1 ? 's' : ''}? This action cannot be undone.` ); if (!confirmDelete) return; try { const results = []; for (const row of selectedRows) { try { const response = await fetch(`/api/files/${row.original.id}`, { method: 'DELETE', }); if (!response.ok) { const errorText = await response.text(); results.push({ id: row.original.id, success: false, error: errorText }); } else { results.push({ id: row.original.id, success: true }); } } catch (error) { results.push({ id: row.original.id, success: false, error: String(error) }); } } const successful = results.filter(r => r.success).length; const failed = results.filter(r => !r.success).length; if (failed > 0) { alert(`${successful} file(s) deleted successfully, ${failed} failed.`); } else { alert(`${successful} file(s) deleted successfully.`); } setRowSelection({}); refetch(); } catch (error) { console.error('Error in delete process:', error); alert('Failed to delete files. Please try again.'); } }; const handleDeleteFile = async (fileId: string, filename: string) => { const confirmDelete = window.confirm( `Are you sure you want to delete "${filename}"? This action cannot be undone.` ); if (!confirmDelete) return; try { const response = await fetch(`/api/files/${fileId}`, { method: 'DELETE', }); if (!response.ok) { throw new Error('Failed to delete file'); } refetch(); } catch (error) { console.error('Error deleting file:', error); alert('Failed to delete file. Please try again.'); } }; if (isLoading) { return ( Files in Database
{[...Array(5)].map((_, i) => (
))}
); } if (isError) { return (
Error: {error?.message || "Failed to load files"}
); } if (!data || data.length === 0) { return (

No files found

Upload some files to get started.

); } return (
Files
{isUploading && ( Uploading... )}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( {flexRender( header.column.columnDef.header, header.getContext() )} {header.column.getIsSorted() && ( {header.column.getIsSorted() === "asc" ? "↑" : "↓"} )} ))} ))} {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} ))}
{table.getSelectedRowModel().rows.length} of {files.length} row(s) selected
); }