diff --git a/src/components/admin/AdminTasksPanel.tsx b/src/components/admin/AdminTasksPanel.tsx index e5a232c..ac21b87 100644 --- a/src/components/admin/AdminTasksPanel.tsx +++ b/src/components/admin/AdminTasksPanel.tsx @@ -3,8 +3,8 @@ import { useEffect, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import toast from 'react-hot-toast'; -import { Badge, Button, Input, Section, Switch } from '@/components/ui'; -import type { BadgeTone } from '@/components/ui/badge'; +import { Button, Card, Input, Section, Switch } from '@/components/ui'; +import { ClockIcon, RefreshIcon } from '@/components/icons/Icons'; type TaskRunStatus = 'idle' | 'running' | 'ok' | 'error'; @@ -31,12 +31,14 @@ async function fetchTasks(): Promise { return ((await res.json()) as { tasks: TaskView[] }).tasks; } -const STATUS_TONE: Record = { - idle: 'muted', - running: 'accent', - ok: 'foreground', - error: 'danger', -}; +function RunningDot() { + return ( + + + + + ); +} function formatRelative(ms: number | null): string { if (ms == null) return 'never'; @@ -115,6 +117,7 @@ export function AdminTasksPanel() { key={task.key} task={task} busy={patchTask.isPending || runTask.isPending} + runPending={runTask.isPending && runTask.variables === task.key} onToggle={(enabled) => patchTask.mutate({ key: task.key, patch: { enabled } })} onSaveInterval={(intervalMs) => patchTask.mutate({ key: task.key, patch: { intervalMs } })} onRun={() => runTask.mutate(task.key)} @@ -131,12 +134,14 @@ export function AdminTasksPanel() { function TaskRow({ task, busy, + runPending, onToggle, onSaveInterval, onRun, }: { task: TaskView; busy: boolean; + runPending: boolean; onToggle: (enabled: boolean) => void; onSaveInterval: (intervalMs: number) => void; onRun: () => void; @@ -152,60 +157,73 @@ function TaskRow({ const intervalDirty = Number.isFinite(parsedMinutes) && parsedMinutes > 0 && newIntervalMs !== task.intervalMs; + const running = task.running || runPending; + return ( -
-
-
-
- {task.name} - {task.lastStatus} + +
+
+
+
+ {running && } + {task.name} +
+ {task.description &&

{task.description}

}
- {task.description &&

{task.description}

} -
-
- - -
-
- -
- Last run: {formatRelative(task.lastRunAt)} - Duration: {formatDuration(task.lastDurationMs)} - Next run: {task.enabled ? formatRelative(task.nextRunAt) : 'disabled'} - - Every - setMinutes(e.target.value)} - aria-label={`${task.name} interval in minutes`} - /> - min - {intervalDirty && ( - - )} - -
+
+
- {task.lastStatus === 'error' && task.lastError && ( -

{task.lastError}

- )} - {task.lastStatus === 'ok' && task.lastResult && ( -

{task.lastResult}

- )} -
+
+
+ + + {formatRelative(task.lastRunAt)} + + {task.enabled && task.nextRunAt != null && ( + + + next {formatRelative(task.nextRunAt)} + + )} +
+ +
+ Every + setMinutes(e.target.value)} + aria-label={`${task.name} interval in minutes`} + /> + min + {intervalDirty && ( + + )} +
+
+ + {task.lastStatus === 'error' && task.lastError ? ( +

{task.lastError}

+ ) : ( + task.lastStatus === 'ok' && task.lastResult && ( +

{task.lastResult}

+ ) + )} +
+ ); }