Explorar el Código

feat(files): implement file deletion functionality in the file management table

vtugulan hace 6 meses
padre
commit
da19419397
Se han modificado 2 ficheros con 233 adiciones y 0 borrados
  1. 42 0
      app/api/files/[id]/route.ts
  2. 191 0
      app/components/filesTable.tsx

+ 42 - 0
app/api/files/[id]/route.ts

@@ -46,6 +46,48 @@ export const GET = async (
   }
 };
 
+export const DELETE = async (
+  req: NextRequest,
+  { params }: { params: Promise<{ id: string }> }
+) => {
+  const { id } = await params;
+  try {
+    const file = await prisma.file.findUnique({
+      where: { id },
+    });
+
+    if (!file) {
+      const response = NextResponse.json(
+        { error: "File not found" },
+        { status: 404 }
+      );
+      response.headers.set('Access-Control-Allow-Origin', '*');
+      return response;
+    }
+
+    await prisma.file.delete({
+      where: { id },
+    });
+
+    const response = NextResponse.json(
+      { message: "File deleted successfully" },
+      { status: 200 }
+    );
+    response.headers.set('Access-Control-Allow-Origin', '*');
+    return response;
+  } catch (error) {
+    console.error("Error deleting file:", error);
+    const response = NextResponse.json(
+      { error: "Failed to delete file" },
+      { status: 500 }
+    );
+    response.headers.set('Access-Control-Allow-Origin', '*');
+    return response;
+  } finally {
+    await prisma.$disconnect();
+  }
+};
+
 export const OPTIONS = async () => {
   const response = new NextResponse(null, { status: 200 });
   response.headers.set('Access-Control-Allow-Origin', '*');

+ 191 - 0
app/components/filesTable.tsx

@@ -122,6 +122,118 @@ export function FilesTable() {
     },
   });
 
+  const columns: ColumnDef<FileData>[] = [
+    {
+      id: "select",
+      header: ({ table }) => (
+        <div className="px-6 py-3">
+          <input
+            type="checkbox"
+            checked={table.getIsAllPageRowsSelected()}
+            onChange={(e) => table.toggleAllPageRowsSelected(!!e.target.checked)}
+            className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
+          />
+        </div>
+      ),
+      cell: ({ row }) => (
+        <div className="px-6 py-4">
+          <input
+            type="checkbox"
+            checked={row.getIsSelected()}
+            onChange={(e) => row.toggleSelected(!!e.target.checked)}
+            className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
+          />
+        </div>
+      ),
+      enableSorting: false,
+      enableHiding: false,
+    },
+    {
+      accessorKey: "filename",
+      header: "File Name",
+      cell: ({ row }) => (
+        <div className="font-medium text-gray-900">{row.getValue("filename")}</div>
+      ),
+    },
+    {
+      accessorKey: "mimetype",
+      header: "Type",
+      cell: ({ row }) => (
+        <div className="text-sm text-gray-600">{row.getValue("mimetype")}</div>
+      ),
+    },
+    {
+      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 }) => (
+        <div className="flex gap-2">
+          <button
+            onClick={() => handleDeleteFile(row.original.id, row.original.filename)}
+            className="text-red-600 hover:text-red-800 transition-colors"
+            title="Delete file"
+          >
+            <svg
+              className="w-5 h-5"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={2}
+                d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
+              />
+            </svg>
+          </button>
+        </div>
+      ),
+      enableSorting: false,
+    },
+  ];
+
   const table = useReactTable({
     data: data || [],
     columns,
@@ -169,6 +281,65 @@ export function FilesTable() {
     }
   };
 
+  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 deletePromises = selectedRows.map(async (row) => {
+        const response = await fetch(`/api/files/${row.original.id}`, {
+          method: 'DELETE',
+        });
+        
+        if (!response.ok) {
+          throw new Error(`Failed to delete file: ${row.original.filename}`);
+        }
+        
+        return row.original.id;
+      });
+
+      await Promise.all(deletePromises);
+      
+      // Clear selection and refresh the table
+      setRowSelection({});
+      refetch();
+      
+    } catch (error) {
+      console.error('Error deleting files:', error);
+      alert('Failed to delete some 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 (
       <div className="bg-white rounded-lg shadow-sm border border-gray-200">
@@ -246,6 +417,26 @@ export function FilesTable() {
               </svg>
               Download Selected
             </button>
+            <button
+              onClick={handleDeleteSelected}
+              disabled={table.getSelectedRowModel().rows.length === 0 || isLoading}
+              className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
+            >
+              <svg
+                className="w-4 h-4 mr-2"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
+                />
+              </svg>
+              Delete Selected
+            </button>
             <button
               onClick={handleRefresh}
               disabled={isLoading}