import { useJobs, useToggleJob, useTriggerJob } from '../../hooks/useAdmin'; import { SkeletonTable } from '../ui/Skeleton'; function formatNextRun(iso: string | null): string { if (!iso) return '—'; const d = new Date(iso); const now = new Date(); const diffMs = d.getTime() - now.getTime(); if (diffMs < 0) return 'imminent'; const mins = Math.round(diffMs / 60_000); if (mins < 60) return `in ${mins}m`; const hrs = Math.round(mins / 60); return `in ${hrs}h`; } function formatAgo(iso: string | null | undefined): string { if (!iso) return ''; const mins = Math.floor((Date.now() - new Date(iso).getTime()) / 60_000); if (mins < 1) return 'just now'; if (mins < 60) return `${mins}m ago`; const hrs = Math.floor(mins / 60); if (hrs < 24) return `${hrs}h ago`; return `${Math.floor(hrs / 24)}d ago`; } function lastRunColor(status: string | null | undefined): string { if (status === 'error') return 'text-red-300'; if (status === 'rate_limited') return 'text-amber-300'; return 'text-gray-500'; } export function JobControls() { const { data: jobs, isLoading } = useJobs(); const toggleJob = useToggleJob(); const triggerJob = useTriggerJob(); const anyJobRunning = (jobs ?? []).some((job) => job.running); const runningJob = jobs?.find((job) => job.running); const pausedJob = jobs?.find((job) => !job.running && job.runtime_status === 'rate_limited'); const runningJobLabel = runningJob?.label; if (isLoading) return ; return (
{runningJob && (
Active job: {runningJob.label}
Manual triggers are blocked until this run finishes.
{runningJob.runtime_processed ?? 0} {typeof runningJob.runtime_total === 'number' ? ` / ${runningJob.runtime_total}` : ''}
{runningJob.runtime_current_ticker && (
Current: {runningJob.runtime_current_ticker}
)} {runningJob.runtime_message && (
{runningJob.runtime_message}
)}
)} {!runningJob && pausedJob && (
Last run paused: {pausedJob.label}
{pausedJob.runtime_message || 'Rate limit hit. The collector stopped early and will resume from last progress on the next run.'}
{pausedJob.runtime_processed ?? 0} {typeof pausedJob.runtime_total === 'number' ? ` / ${pausedJob.runtime_total}` : ''}
)} {jobs?.map((job) => (
{/* Status dot */}
{job.label}
{job.running ? 'Running' : job.runtime_status === 'rate_limited' ? 'Paused (rate-limited)' : job.runtime_status === 'error' ? 'Last run error' : job.enabled ? 'Active' : 'Inactive'} {job.enabled && job.next_run_at && ( Next run {formatNextRun(job.next_run_at)} )} {!job.registered && ( Not registered )}
{!job.running && job.runtime_finished_at && (
Last run {formatAgo(job.runtime_finished_at)} {job.runtime_status ? ` · ${job.runtime_status}` : ''} {job.runtime_message ? ` — ${job.runtime_message}` : ''}
)} {job.running && (
{job.runtime_processed ?? 0} {typeof job.runtime_total === 'number' ? ` / ${job.runtime_total}` : ''} {' '}processed {typeof job.runtime_progress_pct === 'number' && ( {Math.max(0, Math.min(100, job.runtime_progress_pct)).toFixed(0)}% )}
{job.runtime_current_ticker && (
Current: {job.runtime_current_ticker}
)}
)}
{anyJobRunning && !job.running && (
Manual trigger blocked while {runningJobLabel ?? 'another job'} is running.
)}
))}
); }