|
@@ -11,7 +11,7 @@ import {
|
|
|
SortingState,
|
|
SortingState,
|
|
|
RowSelectionState,
|
|
RowSelectionState,
|
|
|
} from "@tanstack/react-table";
|
|
} from "@tanstack/react-table";
|
|
|
-import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
|
|
|
|
+import { useQuery } from "@tanstack/react-query";
|
|
|
import { Button } from "@/components/ui/button";
|
|
import { Button } from "@/components/ui/button";
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
@@ -26,14 +26,15 @@ import {
|
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
|
import { AlertCircle, Download, Trash2, RefreshCw } from "lucide-react";
|
|
import { AlertCircle, Download, Trash2, RefreshCw } from "lucide-react";
|
|
|
import { Input } from "@/components/ui/input";
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
+import { getFiles, deleteFile } from "@/app/actions/files";
|
|
|
|
|
|
|
|
interface FileData {
|
|
interface FileData {
|
|
|
id: string;
|
|
id: string;
|
|
|
filename: string;
|
|
filename: string;
|
|
|
mimetype: string;
|
|
mimetype: string;
|
|
|
size: number;
|
|
size: number;
|
|
|
- createdAt: string;
|
|
|
|
|
- updatedAt: string;
|
|
|
|
|
|
|
+ createdAt: Date;
|
|
|
|
|
+ updatedAt: Date;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export function FilesTable() {
|
|
export function FilesTable() {
|
|
@@ -41,17 +42,12 @@ export function FilesTable() {
|
|
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
|
|
const [files, setFiles] = useState<FileData[]>([]);
|
|
const [files, setFiles] = useState<FileData[]>([]);
|
|
|
const [isUploading, setIsUploading] = useState(false);
|
|
const [isUploading, setIsUploading] = useState(false);
|
|
|
- const queryClient = useQueryClient();
|
|
|
|
|
|
|
|
|
|
const { data, isLoading, isError, error, refetch } = useQuery({
|
|
const { data, isLoading, isError, error, refetch } = useQuery({
|
|
|
queryKey: ["files"],
|
|
queryKey: ["files"],
|
|
|
queryFn: async () => {
|
|
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[];
|
|
|
|
|
|
|
+ const files = await getFiles();
|
|
|
|
|
+ return files;
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -105,11 +101,11 @@ export function FilesTable() {
|
|
|
cell: ({ row }) => {
|
|
cell: ({ row }) => {
|
|
|
const bytes = row.getValue("size") as number;
|
|
const bytes = row.getValue("size") as number;
|
|
|
if (bytes === 0) return "0 Bytes";
|
|
if (bytes === 0) return "0 Bytes";
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
const k = 1024;
|
|
const k = 1024;
|
|
|
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
const sizes = ["Bytes", "KB", "MB", "GB"];
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
@@ -189,29 +185,27 @@ export function FilesTable() {
|
|
|
|
|
|
|
|
setIsUploading(true);
|
|
setIsUploading(true);
|
|
|
|
|
|
|
|
- const formData = new FormData();
|
|
|
|
|
- formData.append("file", file);
|
|
|
|
|
-
|
|
|
|
|
try {
|
|
try {
|
|
|
- const response = await fetch("/api/upload", {
|
|
|
|
|
- method: "POST",
|
|
|
|
|
- body: formData,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ // Import the server action
|
|
|
|
|
+ const { uploadFile } = await import('../actions/file-upload');
|
|
|
|
|
|
|
|
- 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'}`);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ 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) {
|
|
} catch (error) {
|
|
|
console.error("Upload error:", error);
|
|
console.error("Upload error:", error);
|
|
|
alert("Failed to upload file");
|
|
alert("Failed to upload file");
|
|
@@ -224,20 +218,36 @@ export function FilesTable() {
|
|
|
refetch();
|
|
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 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 handleDownloadSelected = () => {
|
|
|
const selectedRows = table.getSelectedRowModel().rows;
|
|
const selectedRows = table.getSelectedRowModel().rows;
|
|
|
if (selectedRows.length === 0) return;
|
|
if (selectedRows.length === 0) return;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (selectedRows.length === 1) {
|
|
if (selectedRows.length === 1) {
|
|
|
const selected = selectedRows[0];
|
|
const selected = selectedRows[0];
|
|
|
handleDownload(selected.original.id, selected.original.filename);
|
|
handleDownload(selected.original.id, selected.original.filename);
|
|
@@ -257,23 +267,15 @@ export function FilesTable() {
|
|
|
const confirmDelete = window.confirm(
|
|
const confirmDelete = window.confirm(
|
|
|
`Are you sure you want to delete ${selectedRows.length} file${selectedRows.length > 1 ? 's' : ''}? This action cannot be undone.`
|
|
`Are you sure you want to delete ${selectedRows.length} file${selectedRows.length > 1 ? 's' : ''}? This action cannot be undone.`
|
|
|
);
|
|
);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (!confirmDelete) return;
|
|
if (!confirmDelete) return;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const results = [];
|
|
const results = [];
|
|
|
for (const row of selectedRows) {
|
|
for (const row of selectedRows) {
|
|
|
try {
|
|
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 });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const success = await deleteFile(row.original.id);
|
|
|
|
|
+ results.push({ id: row.original.id, success });
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
results.push({ id: row.original.id, success: false, error: String(error) });
|
|
results.push({ id: row.original.id, success: false, error: String(error) });
|
|
|
}
|
|
}
|
|
@@ -281,16 +283,16 @@ export function FilesTable() {
|
|
|
|
|
|
|
|
const successful = results.filter(r => r.success).length;
|
|
const successful = results.filter(r => r.success).length;
|
|
|
const failed = results.filter(r => !r.success).length;
|
|
const failed = results.filter(r => !r.success).length;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (failed > 0) {
|
|
if (failed > 0) {
|
|
|
alert(`${successful} file(s) deleted successfully, ${failed} failed.`);
|
|
alert(`${successful} file(s) deleted successfully, ${failed} failed.`);
|
|
|
} else {
|
|
} else {
|
|
|
alert(`${successful} file(s) deleted successfully.`);
|
|
alert(`${successful} file(s) deleted successfully.`);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
setRowSelection({});
|
|
setRowSelection({});
|
|
|
refetch();
|
|
refetch();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('Error in delete process:', error);
|
|
console.error('Error in delete process:', error);
|
|
|
alert('Failed to delete files. Please try again.');
|
|
alert('Failed to delete files. Please try again.');
|
|
@@ -301,20 +303,18 @@ export function FilesTable() {
|
|
|
const confirmDelete = window.confirm(
|
|
const confirmDelete = window.confirm(
|
|
|
`Are you sure you want to delete "${filename}"? This action cannot be undone.`
|
|
`Are you sure you want to delete "${filename}"? This action cannot be undone.`
|
|
|
);
|
|
);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (!confirmDelete) return;
|
|
if (!confirmDelete) return;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- const response = await fetch(`/api/files/${fileId}`, {
|
|
|
|
|
- method: 'DELETE',
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (!response.ok) {
|
|
|
|
|
- throw new Error('Failed to delete file');
|
|
|
|
|
|
|
+ const success = await deleteFile(fileId);
|
|
|
|
|
+
|
|
|
|
|
+ if (success) {
|
|
|
|
|
+ refetch();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ alert('File not found or could not be deleted.');
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- refetch();
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('Error deleting file:', error);
|
|
console.error('Error deleting file:', error);
|
|
|
alert('Failed to delete file. Please try again.');
|
|
alert('Failed to delete file. Please try again.');
|