import { useQuery } from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'
import { Calendar, CheckCircle, Clock, Loader2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { EmptyState } from '@/components/shared/empty-state'
import { ErrorState } from '@/components/shared/error-state'
import { LoadingCards } from '@/components/shared/loading'
import { RefreshButton } from '@/components/shared/refresh-button'
import { StatusIndicator } from '@/components/shared/status-indicator'
import { Badge } from '@/components/ui/badge'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { api } from '@/lib/api-client'
interface SchedulerStatus {
running: boolean
total_tasks: number
active_tasks: number
max_concurrent: number
tasks: SchedulerTask[]
}
interface SchedulerTask {
id: string
description?: string
priority?: string // "High", "Medium", "Low"
status: string // "Queued", "Running", etc.
created_at?: string
error?: string | null
}
export const Route = createFileRoute('/scheduler')({ component: SchedulerPage })
function SchedulerPage() {
const { t } = useTranslation()
const { data, isLoading, isError, refetch, isFetching } = useQuery({
queryKey: ['scheduler'],
queryFn: async () => {
// Backend has separate /api/scheduler/stats + /api/scheduler/tasks
const [stats, tasksRes] = await Promise.all([
api.get<{
queued: number
running: number
max_concurrent: number
rate_limit_per_minute: number
rate_remaining: number
}>('/api/scheduler/stats'),
api.get<{ queued: SchedulerTask[]; running: SchedulerTask[] }>('/api/scheduler/tasks'),
])
return {
running: stats.running > 0,
total_tasks: stats.queued,
active_tasks: stats.running,
max_concurrent: stats.max_concurrent,
tasks: [
...(Array.isArray(tasksRes.queued) ? tasksRes.queued : []),
...(Array.isArray(tasksRes.running) ? tasksRes.running : []),
],
} as SchedulerStatus
},
refetchInterval: 5000,
})
if (isLoading) return <LoadingCards count={4} />
if (isError) return <ErrorState onRetry={() => refetch()} />
const tasks = Array.isArray(data?.tasks) ? data.tasks : []
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">{t('scheduler.title')}</h1>
<p className="text-muted-foreground">{t('scheduler.subtitle')}</p>
</div>
<RefreshButton onClick={() => refetch()} isFetching={isFetching} />
</div>
{/* Stats */}
<div className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm text-muted-foreground">{t('scheduler.status')}</CardTitle>
</CardHeader>
<CardContent>
<StatusIndicator status={data?.running ? 'running' : 'stopped'} />
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm text-muted-foreground">{t('scheduler.queued')}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{data?.total_tasks ?? 0}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm text-muted-foreground">{t('scheduler.active')}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{data?.active_tasks ?? 0}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm text-muted-foreground">
{t('scheduler.maxConcurrent')}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{data?.max_concurrent ?? '-'}</div>
</CardContent>
</Card>
</div>
{/* Task List */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Calendar className="h-4 w-4" /> {t('scheduler.taskQueue')}
</CardTitle>
</CardHeader>
<CardContent>
{tasks.length === 0 ? (
<EmptyState
icon={<Calendar className="h-8 w-8" />}
title={t('scheduler.noTasks')}
description={t('scheduler.noTasksDescription')}
className="py-6"
/>
) : (
<div className="space-y-2">
{tasks.map((task) => (
<div
key={task.id}
className="flex items-center justify-between rounded-lg border p-3"
>
<div className="flex items-center gap-3">
{task.status === 'Running' || task.status === 'running' ? (
<Loader2 className="h-4 w-4 animate-spin text-info" />
) : task.status === 'Completed' || task.status === 'completed' ? (
<CheckCircle className="h-4 w-4 text-success" />
) : task.status === 'Failed' || task.status === 'failed' ? (
<Clock className="h-4 w-4 text-error" />
) : (
<Clock className="h-4 w-4 text-warning" />
)}
<div>
<p className="font-medium text-sm">
{task.description ?? task.id.slice(0, 12)}...
</p>
{task.error && <p className="text-xs text-destructive">{task.error}</p>}
</div>
</div>
<div className="flex items-center gap-2">
<Badge variant="outline">{task.priority ?? '?'}</Badge>
<Badge
variant={
task.status === 'Running' || task.status === 'running'
? 'success'
: task.status === 'Failed' || task.status === 'failed'
? 'destructive'
: 'secondary'
}
>
{task.status}
</Badge>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
)
}