"use client"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect } from "react"; import toast from "react-hot-toast"; import { get, post } from "../../lib/api"; import ApiHealth from "./ApiHealth"; export default function StatsSection() { const queryClient = useQueryClient(); const { data: tasks, isLoading: _tasksLoading } = useQuery({ queryKey: ["tasks"], queryFn: () => get("/tasks"), }); const { data: filesSuccessful, isLoading: filesSuccessfulLoading } = useQuery( { queryKey: ["files-stats-successful"], queryFn: () => get("/files/stats/successful"), } ); const { data: filesProcessedTotal, isLoading: filesProcessedLoading } = useQuery({ queryKey: ["files-stats-processed"], queryFn: () => get("/files/stats/processed"), }); const { data: _datasets, isLoading: _datasetsLoading } = useQuery({ queryKey: ["datasets"], queryFn: () => get("/files"), }); const { data: settings, isLoading: settingsLoading } = useQuery({ queryKey: ["settings", "datasets"], queryFn: () => get("/config/settings/datasets"), }); const { data: watcherStatus, isLoading: watcherLoading } = useQuery({ queryKey: ["watcher", "status"], queryFn: () => get("/watcher/status"), }); const { data: taskProcessingStatus, isLoading: taskProcessingLoading } = useQuery({ queryKey: ["tasks", "processing-status"], queryFn: () => get("/tasks/processing-status"), }); const { data: queueStatus, isLoading: _queueLoading } = useQuery({ queryKey: ["tasks", "queue", "status"], queryFn: () => get("/tasks/queue/status"), }); const { data: apiHealth, isLoading: apiHealthLoading } = useQuery({ queryKey: ["api", "health"], queryFn: () => get("/health"), refetchInterval: 30000, }); // Mutations for controlling services const startWatcherMutation = useMutation({ mutationFn: () => post("/watcher/start"), onSuccess: () => { toast.success("File watcher started"); // Invalidate and refetch to ensure status updates immediately queryClient.invalidateQueries({ queryKey: ["watcher", "status"] }); setTimeout(() => { queryClient.refetchQueries({ queryKey: ["watcher", "status"] }); }, 100); }, onError: () => { toast.error("Failed to start file watcher"); }, }); const stopWatcherMutation = useMutation({ mutationFn: () => post("/watcher/stop"), onSuccess: () => { toast.success("File watcher stopped"); // Invalidate and refetch to ensure status updates immediately queryClient.invalidateQueries({ queryKey: ["watcher", "status"] }); setTimeout(() => { queryClient.refetchQueries({ queryKey: ["watcher", "status"] }); }, 100); }, onError: () => { toast.error("Failed to stop file watcher"); }, }); const startTaskProcessingMutation = useMutation({ mutationFn: () => post("/tasks/start-processing"), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["tasks", "processing-status"], }); queryClient.invalidateQueries({ queryKey: ["tasks", "queue", "status"] }); toast.success("Task processing started"); }, onError: () => { toast.error("Failed to start task processing"); }, }); const stopTaskProcessingMutation = useMutation({ mutationFn: () => post("/tasks/stop-processing"), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["tasks", "processing-status"], }); queryClient.invalidateQueries({ queryKey: ["tasks", "queue", "status"] }); toast.success("Task processing stopped"); }, onError: () => { toast.error("Failed to stop task processing"); }, }); const _tasksRunning = tasks?.length || 0; const filesProcessed = filesSuccessful || 0; const totalProcessed = filesProcessedTotal || 0; const successRate = totalProcessed > 0 ? Math.round((filesProcessed / totalProcessed) * 100) : 0; const activeWatchers = settings ? Object.values(settings).filter((dataset: any) => dataset.enabled === true) .length : 0; const isApiHealthy = apiHealth?.status === "healthy"; const isWatcherActive = watcherStatus?.isWatching; const isTaskProcessingActive = taskProcessingStatus?.isProcessing; // Listen for WebSocket updates to refresh stats useEffect(() => { const handleTaskUpdate = (event: CustomEvent) => { const taskData = event.detail; // Refresh task-related queries when tasks are updated if ( taskData.type === "progress" || taskData.type === "completed" || taskData.type === "failed" ) { queryClient.invalidateQueries({ queryKey: ["tasks"] }); queryClient.invalidateQueries({ queryKey: ["tasks", "processing-status"], }); queryClient.invalidateQueries({ queryKey: ["tasks", "queue", "status"], }); } }; const handleFileUpdate = (event: CustomEvent) => { const fileData = event.detail; // Refresh file stats when files are processed if (fileData.type === "processed" || fileData.type === "success") { queryClient.invalidateQueries({ queryKey: ["files-stats-successful"] }); queryClient.invalidateQueries({ queryKey: ["files-stats-processed"] }); } }; window.addEventListener("taskUpdate", handleTaskUpdate as EventListener); window.addEventListener("fileUpdate", handleFileUpdate as EventListener); return () => { window.removeEventListener( "taskUpdate", handleTaskUpdate as EventListener ); window.removeEventListener( "fileUpdate", handleFileUpdate as EventListener ); }; }, [queryClient]); return (
{/* API Health Widget */} {/* Stats Grid */}
{/* File Watcher */}
{watcherLoading ? (
) : ( {isWatcherActive ? "Active" : "Idle"} )}
File Watcher
{settingsLoading ? "..." : `${activeWatchers} datasets`}
{watcherStatus?.watches && watcherStatus.watches.length > 0 && (
Watching:
{watcherStatus.watches .slice(0, 3) .map((watch: any, index: number) => ( {typeof watch === "string" ? watch.split("/").pop() : watch.path?.split("/").pop() || "Unknown"} ))} {watcherStatus.watches.length > 3 && ( +{watcherStatus.watches.length - 3} more )}
)}
{/* Task Processing */}
{taskProcessingLoading ? (
) : ( {isTaskProcessingActive ? "Active" : "Idle"} )}
Task Processing
{filesSuccessfulLoading || filesProcessedLoading ? "..." : `${successRate}% success`}
{queueStatus && (
Pending
{queueStatus.pending || 0}
Active
{queueStatus.processing || 0}
Done
{queueStatus.completed || 0}
Failed
{queueStatus.failed || 0}
)}
{/* API Health */}
{apiHealthLoading ? (
) : ( {isApiHealthy ? "Healthy" : "Issues"} )}
API Health
{apiHealth?.datetime ? new Date(apiHealth.datetime).toLocaleTimeString() : "Checking..."}
{/* Files Processed */}
{filesSuccessfulLoading ? (
) : ( filesProcessed.toLocaleString() )}
Files Processed
{/* Current Task Progress */} {tasks && tasks.length > 0 && (
{(() => { const processingTask = tasks.find( (t: any) => t.status === "processing" ); if (!processingTask) return null; const progress = processingTask.progress || 0; const fileName = processingTask.input ? processingTask.input.split("/").pop() : "Unknown file"; return (
{fileName} Processing...
{progress}%
); })()}
)}
); }