mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 06:15:39 -04:00
Rebuild the background jobs card as a sortable table showing each job's schedule, last run time (live-updating via a new useTimeAgo hook), last duration, and a manual trigger button backed by a new POST /api/admin/jobs/trigger route. Extract StatusDot into a shared component. Add cronToHuman() to display schedule patterns as readable strings (e.g. "Every 6h", "Daily at 03:00"). Replace static formatDistanceToNow calls throughout the health section with a LiveTimeAgo component that refreshes every 30 seconds. Also swap a handful of section icons for better visual matches across settings cards.
35 lines
1.0 KiB
TypeScript
35 lines
1.0 KiB
TypeScript
import { formatDistanceToNowStrict } from "date-fns";
|
|
import { useEffect, useEffectEvent, useState } from "react";
|
|
|
|
function toTimestamp(date: string | Date | null | undefined): number | null {
|
|
if (!date) return null;
|
|
const t = date instanceof Date ? date.getTime() : new Date(date).getTime();
|
|
return Number.isFinite(t) ? t : null;
|
|
}
|
|
|
|
export function useTimeAgo(
|
|
date: string | Date | null | undefined,
|
|
{ intervalMs = 1_000, addSuffix = true, fallback = "" } = {},
|
|
): string {
|
|
const ts = toTimestamp(date);
|
|
|
|
const [text, setText] = useState(() =>
|
|
ts === null ? fallback : formatDistanceToNowStrict(ts, { addSuffix }),
|
|
);
|
|
|
|
const tick = useEffectEvent(() => {
|
|
const next =
|
|
ts === null ? fallback : formatDistanceToNowStrict(ts, { addSuffix });
|
|
if (next !== text) setText(next);
|
|
});
|
|
|
|
useEffect(() => {
|
|
tick();
|
|
if (ts === null) return;
|
|
const id = setInterval(tick, intervalMs);
|
|
return () => clearInterval(id);
|
|
}, [ts, intervalMs]); // tick is NOT listed — that's the point
|
|
|
|
return text;
|
|
}
|