|
@@ -32,17 +32,41 @@ export default function TaskList({
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
const handleTaskUpdate = (event: CustomEvent) => {
|
|
const handleTaskUpdate = (event: CustomEvent) => {
|
|
|
const taskData = event.detail;
|
|
const taskData = event.detail;
|
|
|
- // Invalidate and refetch tasks when task updates occur
|
|
|
|
|
- queryClient.invalidateQueries({ queryKey: ["tasks"] });
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // For progress updates, update the cache directly instead of refetching
|
|
|
|
|
+ if (taskData.type === "progress" && taskData.taskId !== undefined) {
|
|
|
|
|
+ queryClient.setQueryData(["tasks"], (oldData: any) => {
|
|
|
|
|
+ if (!oldData) return oldData;
|
|
|
|
|
+ return oldData.map((task: any) =>
|
|
|
|
|
+ task.id === taskData.taskId
|
|
|
|
|
+ ? { ...task, progress: taskData.progress }
|
|
|
|
|
+ : task
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // For other task updates (created, completed, failed), do a full refetch
|
|
|
|
|
+ queryClient.refetchQueries({ queryKey: ["tasks"] });
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleFileUpdate = (event: CustomEvent) => {
|
|
|
|
|
+ const fileData = event.detail;
|
|
|
|
|
+ // Refetch tasks when file updates occur (e.g., new files detected)
|
|
|
|
|
+ queryClient.refetchQueries({ queryKey: ["tasks"] });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
window.addEventListener("taskUpdate", handleTaskUpdate as EventListener);
|
|
window.addEventListener("taskUpdate", handleTaskUpdate as EventListener);
|
|
|
|
|
+ window.addEventListener("fileUpdate", handleFileUpdate as EventListener);
|
|
|
|
|
|
|
|
return () => {
|
|
return () => {
|
|
|
window.removeEventListener(
|
|
window.removeEventListener(
|
|
|
"taskUpdate",
|
|
"taskUpdate",
|
|
|
handleTaskUpdate as EventListener
|
|
handleTaskUpdate as EventListener
|
|
|
);
|
|
);
|
|
|
|
|
+ window.removeEventListener(
|
|
|
|
|
+ "fileUpdate",
|
|
|
|
|
+ handleFileUpdate as EventListener
|
|
|
|
|
+ );
|
|
|
};
|
|
};
|
|
|
}, [queryClient]);
|
|
}, [queryClient]);
|
|
|
|
|
|
|
@@ -60,24 +84,103 @@ export default function TaskList({
|
|
|
// Expanded rows state
|
|
// Expanded rows state
|
|
|
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
|
|
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
|
|
|
|
|
|
|
|
- // Dataset and status filter state
|
|
|
|
|
- const [enabledDatasets, setEnabledDatasets] = useState<Set<string>>(
|
|
|
|
|
- new Set()
|
|
|
|
|
- );
|
|
|
|
|
- const [enabledStatuses, setEnabledStatuses] = useState<Set<string>>(
|
|
|
|
|
- new Set(["pending", "processing", "failed", "skipped"])
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // Dataset and status filter state - initialize from localStorage
|
|
|
|
|
+ const [enabledDatasets, setEnabledDatasets] = useState<Set<string>>(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ const saved = localStorage.getItem("taskList:enabledDatasets");
|
|
|
|
|
+ return saved ? new Set(JSON.parse(saved)) : new Set();
|
|
|
|
|
+ }
|
|
|
|
|
+ return new Set();
|
|
|
|
|
+ });
|
|
|
|
|
+ const [enabledStatuses, setEnabledStatuses] = useState<Set<string>>(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ const saved = localStorage.getItem("taskList:enabledStatuses");
|
|
|
|
|
+ return saved
|
|
|
|
|
+ ? new Set(JSON.parse(saved))
|
|
|
|
|
+ : new Set(["pending", "processing", "failed", "skipped"]);
|
|
|
|
|
+ }
|
|
|
|
|
+ return new Set(["pending", "processing", "failed", "skipped"]);
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- // State for filters and search
|
|
|
|
|
- const [searchTerm, setSearchTerm] = useState("");
|
|
|
|
|
|
|
+ // State for filters and search - initialize from localStorage
|
|
|
|
|
+ const [searchTerm, setSearchTerm] = useState(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ return localStorage.getItem("taskList:searchTerm") || "";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "";
|
|
|
|
|
+ });
|
|
|
const [sortField, setSortField] = useState<
|
|
const [sortField, setSortField] = useState<
|
|
|
"id" | "status" | "progress" | "dataset" | "updated_at"
|
|
"id" | "status" | "progress" | "dataset" | "updated_at"
|
|
|
- >("updated_at");
|
|
|
|
|
- const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");
|
|
|
|
|
|
|
+ >(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ const saved = localStorage.getItem("taskList:sortField");
|
|
|
|
|
+ return (
|
|
|
|
|
+ (saved as "id" | "status" | "progress" | "dataset" | "updated_at") ||
|
|
|
|
|
+ "updated_at"
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ return "updated_at";
|
|
|
|
|
+ });
|
|
|
|
|
+ const [sortDirection, setSortDirection] = useState<"asc" | "desc">(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ const saved = localStorage.getItem("taskList:sortDirection");
|
|
|
|
|
+ return (saved as "asc" | "desc") || "desc";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "desc";
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
// Pagination state
|
|
// Pagination state
|
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
|
- const [pageSize, setPageSize] = useState(25);
|
|
|
|
|
|
|
+ const [pageSize, setPageSize] = useState(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ const saved = localStorage.getItem("taskList:pageSize");
|
|
|
|
|
+ return saved ? parseInt(saved) : 25;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 25;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Save filters to localStorage
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ localStorage.setItem(
|
|
|
|
|
+ "taskList:enabledDatasets",
|
|
|
|
|
+ JSON.stringify(Array.from(enabledDatasets))
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [enabledDatasets]);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ localStorage.setItem(
|
|
|
|
|
+ "taskList:enabledStatuses",
|
|
|
|
|
+ JSON.stringify(Array.from(enabledStatuses))
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [enabledStatuses]);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ localStorage.setItem("taskList:searchTerm", searchTerm);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [searchTerm]);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ localStorage.setItem("taskList:sortField", sortField);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [sortField]);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ localStorage.setItem("taskList:sortDirection", sortDirection);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [sortDirection]);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (typeof window !== "undefined") {
|
|
|
|
|
+ localStorage.setItem("taskList:pageSize", pageSize.toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ }, [pageSize]);
|
|
|
|
|
|
|
|
const deleteMutation = useMutation({
|
|
const deleteMutation = useMutation({
|
|
|
mutationFn: (params: { task?: string; tasks?: any[] }) => {
|
|
mutationFn: (params: { task?: string; tasks?: any[] }) => {
|
|
@@ -93,7 +196,7 @@ export default function TaskList({
|
|
|
throw new Error("Invalid delete parameters");
|
|
throw new Error("Invalid delete parameters");
|
|
|
},
|
|
},
|
|
|
onSuccess: (_, params) => {
|
|
onSuccess: (_, params) => {
|
|
|
- queryClient.invalidateQueries({ queryKey: ["tasks"] });
|
|
|
|
|
|
|
+ queryClient.refetchQueries({ queryKey: ["tasks"] });
|
|
|
if (params.tasks) {
|
|
if (params.tasks) {
|
|
|
toast.success(`${params.tasks.length} tasks deleted successfully`);
|
|
toast.success(`${params.tasks.length} tasks deleted successfully`);
|
|
|
addNotification({
|
|
addNotification({
|
|
@@ -343,7 +446,7 @@ export default function TaskList({
|
|
|
There was an error loading the tasks data.
|
|
There was an error loading the tasks data.
|
|
|
</p>
|
|
</p>
|
|
|
<button
|
|
<button
|
|
|
- onClick={() => queryClient.invalidateQueries({ queryKey: ["tasks"] })}
|
|
|
|
|
|
|
+ onClick={() => queryClient.refetchQueries({ queryKey: ["tasks"] })}
|
|
|
className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
|
className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
|
|
>
|
|
>
|
|
|
Try Again
|
|
Try Again
|
|
@@ -629,7 +732,24 @@ export default function TaskList({
|
|
|
{truncateText(task.status)}
|
|
{truncateText(task.status)}
|
|
|
</td>
|
|
</td>
|
|
|
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
|
- {truncateText(task.progress ?? "-")}
|
|
|
|
|
|
|
+ {task.progress !== null &&
|
|
|
|
|
+ task.progress !== undefined ? (
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <div className="flex-1 min-w-[60px]">
|
|
|
|
|
+ <div className="h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="h-full bg-indigo-600 transition-all duration-300"
|
|
|
|
|
+ style={{ width: `${task.progress}%` }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span className="text-xs font-medium tabular-nums">
|
|
|
|
|
+ {task.progress}%
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ "-"
|
|
|
|
|
+ )}
|
|
|
</td>
|
|
</td>
|
|
|
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
|
{truncateText(task.dataset ?? "-")}
|
|
{truncateText(task.dataset ?? "-")}
|