|
|
@@ -3,10 +3,10 @@
|
|
|
import {
|
|
|
ChevronDownIcon,
|
|
|
ChevronRightIcon,
|
|
|
- TrashIcon
|
|
|
+ TrashIcon,
|
|
|
} from "@heroicons/react/24/outline";
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
-import { useEffect, useMemo, useState } from "react";
|
|
|
+import { Fragment, useEffect, useMemo, useState } from "react";
|
|
|
import toast from "react-hot-toast";
|
|
|
import { del, get, post } from "../../lib/api";
|
|
|
import ConfirmationDialog from "../components/ConfirmationDialog";
|
|
|
@@ -25,7 +25,7 @@ export default function FileList() {
|
|
|
const {
|
|
|
data: allFiles,
|
|
|
isLoading,
|
|
|
- error
|
|
|
+ error,
|
|
|
} = useQuery({
|
|
|
queryKey: ["all-files", datasets],
|
|
|
queryFn: async () => {
|
|
|
@@ -35,11 +35,11 @@ export default function FileList() {
|
|
|
);
|
|
|
const results = await Promise.all(allFilesPromises);
|
|
|
return results.flat().map((file: any) => ({
|
|
|
- ...file
|
|
|
+ ...file,
|
|
|
}));
|
|
|
},
|
|
|
enabled: !!datasets && datasets.length > 0,
|
|
|
- staleTime: 30000 // 30 seconds
|
|
|
+ staleTime: 30000, // 30 seconds
|
|
|
});
|
|
|
|
|
|
// State for filters and search - initialize from localStorage
|
|
|
@@ -166,7 +166,7 @@ export default function FileList() {
|
|
|
isBatch?: boolean;
|
|
|
selectedFiles?: any[];
|
|
|
}>({
|
|
|
- isOpen: false
|
|
|
+ isOpen: false,
|
|
|
});
|
|
|
|
|
|
// Batch selection state
|
|
|
@@ -204,14 +204,14 @@ export default function FileList() {
|
|
|
addNotification({
|
|
|
type: "success",
|
|
|
title: "Files Deleted",
|
|
|
- message: `${params.files.length} files have been deleted successfully.`
|
|
|
+ message: `${params.files.length} files have been deleted successfully.`,
|
|
|
});
|
|
|
} else {
|
|
|
toast.success("File deleted successfully");
|
|
|
addNotification({
|
|
|
type: "success",
|
|
|
title: "File Deleted",
|
|
|
- message: `File "${params.file}" has been deleted successfully.`
|
|
|
+ message: `File "${params.file}" has been deleted successfully.`,
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
@@ -221,9 +221,9 @@ export default function FileList() {
|
|
|
addNotification({
|
|
|
type: "error",
|
|
|
title: "Delete Failed",
|
|
|
- message: `Failed to delete file(s). Please try again.`
|
|
|
+ message: `Failed to delete file(s). Please try again.`,
|
|
|
});
|
|
|
- }
|
|
|
+ },
|
|
|
});
|
|
|
|
|
|
const requeueMutation = useMutation({
|
|
|
@@ -236,7 +236,7 @@ export default function FileList() {
|
|
|
addNotification({
|
|
|
type: "success",
|
|
|
title: "File Requeued",
|
|
|
- message: `File "${file}" has been requeued for processing.`
|
|
|
+ message: `File "${file}" has been requeued for processing.`,
|
|
|
});
|
|
|
},
|
|
|
onError: (error, { file }) => {
|
|
|
@@ -245,9 +245,9 @@ export default function FileList() {
|
|
|
addNotification({
|
|
|
type: "error",
|
|
|
title: "Requeue Failed",
|
|
|
- message: `Failed to requeue file "${file}". Please try again.`
|
|
|
+ message: `Failed to requeue file "${file}". Please try again.`,
|
|
|
});
|
|
|
- }
|
|
|
+ },
|
|
|
});
|
|
|
|
|
|
const handleEdit = (file: any) => {
|
|
|
@@ -263,7 +263,7 @@ export default function FileList() {
|
|
|
setDeleteConfirm({
|
|
|
isOpen: true,
|
|
|
file,
|
|
|
- isBatch: false
|
|
|
+ isBatch: false,
|
|
|
});
|
|
|
};
|
|
|
|
|
|
@@ -274,7 +274,7 @@ export default function FileList() {
|
|
|
setDeleteConfirm({
|
|
|
isOpen: true,
|
|
|
isBatch: true,
|
|
|
- selectedFiles: filesToDelete
|
|
|
+ selectedFiles: filesToDelete,
|
|
|
});
|
|
|
};
|
|
|
|
|
|
@@ -285,7 +285,7 @@ export default function FileList() {
|
|
|
filesToRequeue.forEach((file) => {
|
|
|
requeueMutation.mutate({
|
|
|
dataset: file.dataset,
|
|
|
- file: file.input
|
|
|
+ file: file.input,
|
|
|
});
|
|
|
});
|
|
|
setSelectedFiles(new Set()); // Clear selection after requeue
|
|
|
@@ -629,121 +629,115 @@ export default function FileList() {
|
|
|
const fileId = `${file.dataset}-${file.input}`;
|
|
|
const isExpanded = expandedRows.has(fileId);
|
|
|
return (
|
|
|
- <tr
|
|
|
- key={fileId}
|
|
|
- className="hover:bg-gray-50 dark:hover:bg-gray-700"
|
|
|
- >
|
|
|
- <td className="px-4 py-2 whitespace-nowrap">
|
|
|
- <button
|
|
|
- onClick={() => handleToggleExpanded(fileId)}
|
|
|
- className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
|
|
- >
|
|
|
- {isExpanded ? (
|
|
|
- <ChevronDownIcon className="h-4 w-4" />
|
|
|
- ) : (
|
|
|
- <ChevronRightIcon className="h-4 w-4" />
|
|
|
- )}
|
|
|
- </button>
|
|
|
- </td>
|
|
|
- <td className="px-4 py-2 whitespace-nowrap">
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- checked={selectedFiles.has(fileId)}
|
|
|
- onChange={() => handleSelectFile(file)}
|
|
|
- className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
|
|
|
- />
|
|
|
- </td>
|
|
|
- <td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
|
- <span className="px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded text-xs font-medium">
|
|
|
- {file.dataset}
|
|
|
- </span>
|
|
|
- </td>
|
|
|
- <td className="px-4 py-2 text-sm text-gray-900 dark:text-gray-100 max-w-xs">
|
|
|
- <div className="truncate" title={file.input}>
|
|
|
- {file.input}
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
|
- {formatDate(file.date)}
|
|
|
- </td>
|
|
|
- <td className="px-4 py-2 whitespace-nowrap text-sm text-right">
|
|
|
- <div className="flex items-center justify-end space-x-1">
|
|
|
+ <Fragment key={fileId}>
|
|
|
+ <tr
|
|
|
+ key={fileId}
|
|
|
+ className="hover:bg-gray-50 dark:hover:bg-gray-700"
|
|
|
+ >
|
|
|
+ <td className="px-4 py-2 whitespace-nowrap">
|
|
|
<button
|
|
|
- onClick={() =>
|
|
|
- requeueMutation.mutate({
|
|
|
- dataset: file.dataset,
|
|
|
- file: file.input
|
|
|
- })
|
|
|
- }
|
|
|
- className="inline-flex items-center px-3 py-1 text-xs font-medium text-white bg-blue-500 hover:bg-blue-600 rounded-l-md border border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
|
+ onClick={() => handleToggleExpanded(fileId)}
|
|
|
+ className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
|
|
>
|
|
|
- Requeue
|
|
|
+ {isExpanded ? (
|
|
|
+ <ChevronDownIcon className="h-4 w-4" />
|
|
|
+ ) : (
|
|
|
+ <ChevronRightIcon className="h-4 w-4" />
|
|
|
+ )}
|
|
|
</button>
|
|
|
- <div className="relative">
|
|
|
+ </td>
|
|
|
+ <td className="px-4 py-2 whitespace-nowrap">
|
|
|
+ <input
|
|
|
+ type="checkbox"
|
|
|
+ checked={selectedFiles.has(fileId)}
|
|
|
+ onChange={() => handleSelectFile(file)}
|
|
|
+ className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
|
|
|
+ />
|
|
|
+ </td>
|
|
|
+ <td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
|
+ <span className="px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded text-xs font-medium">
|
|
|
+ {file.dataset}
|
|
|
+ </span>
|
|
|
+ </td>
|
|
|
+ <td className="px-4 py-2 text-sm text-gray-900 dark:text-gray-100 max-w-xs">
|
|
|
+ <div className="truncate" title={file.input}>
|
|
|
+ {file.input}
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
|
+ {formatDate(file.date)}
|
|
|
+ </td>
|
|
|
+ <td className="px-4 py-2 whitespace-nowrap text-sm text-right">
|
|
|
+ <div className="flex items-center justify-end space-x-1">
|
|
|
<button
|
|
|
- onClick={() => handleToggleDropdown(fileId)}
|
|
|
- className="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-l-0 border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-r-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
|
+ onClick={() =>
|
|
|
+ requeueMutation.mutate({
|
|
|
+ dataset: file.dataset,
|
|
|
+ file: file.input,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ className="inline-flex items-center px-3 py-1 text-xs font-medium text-white bg-blue-500 hover:bg-blue-600 rounded-l-md border border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
|
>
|
|
|
- <ChevronDownIcon className="h-4 w-4" />
|
|
|
+ Requeue
|
|
|
</button>
|
|
|
- {openDropdown === fileId && (
|
|
|
- <div className="dropdown-container absolute right-0 mt-1 w-32 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-10">
|
|
|
- <div className="py-1">
|
|
|
- <button
|
|
|
- onClick={() =>
|
|
|
- handleDropdownAction("edit", file)
|
|
|
- }
|
|
|
- className="block w-full text-left px-3 py-2 text-xs text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
|
- >
|
|
|
- Edit
|
|
|
- </button>
|
|
|
- <button
|
|
|
- onClick={() =>
|
|
|
- handleDropdownAction("delete", file)
|
|
|
- }
|
|
|
- className="block w-full text-left px-3 py-2 text-xs text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
|
- >
|
|
|
- Delete
|
|
|
- </button>
|
|
|
+ <div className="relative">
|
|
|
+ <button
|
|
|
+ onClick={() => handleToggleDropdown(fileId)}
|
|
|
+ className="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-l-0 border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-r-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
|
+ >
|
|
|
+ <ChevronDownIcon className="h-4 w-4" />
|
|
|
+ </button>
|
|
|
+ {openDropdown === fileId && (
|
|
|
+ <div className="dropdown-container absolute right-0 mt-1 w-32 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-10">
|
|
|
+ <div className="py-1">
|
|
|
+ <button
|
|
|
+ onClick={() =>
|
|
|
+ handleDropdownAction("edit", file)
|
|
|
+ }
|
|
|
+ className="block w-full text-left px-3 py-2 text-xs text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
|
+ >
|
|
|
+ Edit
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ onClick={() =>
|
|
|
+ handleDropdownAction("delete", file)
|
|
|
+ }
|
|
|
+ className="block w-full text-left px-3 py-2 text-xs text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
|
+ >
|
|
|
+ Delete
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- </tr>
|
|
|
- );
|
|
|
- })}
|
|
|
- {/* Render expanded rows separately */}
|
|
|
- {displayFiles
|
|
|
- .filter((file: any) =>
|
|
|
- expandedRows.has(`${file.dataset}-${file.input}`)
|
|
|
- )
|
|
|
- .map((file: any) => {
|
|
|
- const fileId = `${file.dataset}-${file.input}`;
|
|
|
- return (
|
|
|
- <tr
|
|
|
- key={`${fileId}-expanded`}
|
|
|
- className="bg-gray-50 dark:bg-gray-800"
|
|
|
- >
|
|
|
- <td colSpan={5} className="px-4 py-3">
|
|
|
- <div className="text-sm">
|
|
|
- <div className="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
|
|
- Output
|
|
|
- </div>
|
|
|
- <div className="text-gray-700 dark:text-gray-300 font-mono text-xs bg-white dark:bg-gray-900 p-2 rounded border">
|
|
|
- {file.output || "No output available"}
|
|
|
+ )}
|
|
|
</div>
|
|
|
</div>
|
|
|
</td>
|
|
|
</tr>
|
|
|
- );
|
|
|
- })}
|
|
|
+ {isExpanded && (
|
|
|
+ <tr
|
|
|
+ key={`${fileId}-expanded`}
|
|
|
+ className="bg-gray-50 dark:bg-gray-800"
|
|
|
+ >
|
|
|
+ <td colSpan={6} className="px-4 py-3">
|
|
|
+ <div className="text-sm">
|
|
|
+ <div className="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
|
|
+ Output
|
|
|
+ </div>
|
|
|
+ <div className="text-gray-700 dark:text-gray-300 font-mono text-xs bg-white dark:bg-gray-900 p-2 rounded border">
|
|
|
+ {file.output || "No output available"}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ )}
|
|
|
+ </Fragment>
|
|
|
+ );
|
|
|
+ })}
|
|
|
</>
|
|
|
) : (
|
|
|
<tr>
|
|
|
<td
|
|
|
- colSpan={5}
|
|
|
+ colSpan={6}
|
|
|
className="px-4 py-4 text-center text-gray-500 dark:text-gray-400"
|
|
|
>
|
|
|
No files found matching your filters.
|