"use client"; import React, { useState, useEffect } from "react"; import { ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, SortingState, RowSelectionState, } from "@tanstack/react-table"; import { useQuery } 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"; import { getFiles, deleteFile } from "@/app/actions/files"; interface FileData { id: string; filename: string; mimetype: string; size: number; createdAt: Date; updatedAt: Date; } export function FilesTable() { const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState({}); const [files, setFiles] = useState([]); const [isUploading, setIsUploading] = useState(false); const { data, isLoading, isError, error, refetch } = useQuery({ queryKey: ["files"], queryFn: async () => { const files = await getFiles(); return files; }, }); // 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); try { // Import the server action const { uploadFile } = await import('../actions/file-upload'); const uploadedFile = await uploadFile(file); // Use the uploadedFile response to update the table immediately const newFile: FileData = { id: uploadedFile.id, filename: uploadedFile.filename, mimetype: uploadedFile.mimetype, size: uploadedFile.size, createdAt: new Date(uploadedFile.createdAt), updatedAt: new Date(uploadedFile.updatedAt), }; // Add the new file to the beginning of the files array setFiles(prevFiles => [newFile, ...prevFiles]); // Reset file input event.target.value = ''; } catch (error) { console.error("Upload error:", error); alert("Failed to upload file"); } finally { setIsUploading(false); } }; const handleRefresh = () => { refetch(); }; const handleDownload = async (fileId: string, filename: string) => { try { const { downloadFile } = await import('../actions/files'); const fileData = await downloadFile(fileId); if (!fileData) { alert('File not found'); return; } // Create blob and download const blob = new Blob([fileData.data], { type: fileData.mimetype }); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); } catch (error) { console.error('Error downloading file:', error); alert('Failed to download file'); } }; 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 success = await deleteFile(row.original.id); results.push({ id: row.original.id, success }); } 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 success = await deleteFile(fileId); if (success) { refetch(); } else { alert('File not found or could not be deleted.'); } } 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
); }