Files
HealthLog/src/components/admin/system-status-summary.tsx
T
Marc-André Bombeck 32a023dfff chore(ui): apply M7 + L1 + L3 + L4 polish across medication form, motion-reduce, details aria, therapy timeline
W10 design-review carry-over items. M7 lifts the medication-form
day-of-week buttons, the more-horizontal trigger, and the schedule
label-input gaps to the 44 px / space-y-2 standard used by every
other settings surface. L1 pairs every animate-spin/pulse/bounce
with motion-reduce:animate-none across the component tree so users
with prefers-reduced-motion get a still surface. L3 adds the
aria-controls + panel-id pairing to the inventory + coach-evidence
<details> blocks for parity with the Health Score provenance
accordion. L4 inserts a sr-only h4 per therapy-timeline entry so
screen-reader users can jump between medication blocks.
2026-05-15 17:13:42 +02:00

141 lines
4.2 KiB
TypeScript

"use client";
/**
* `<SystemStatusSummary>` — compact at-a-glance system snapshot for the
* `/admin` overview landing page.
*
* The full breakdown lives in `<SystemStatusSection>` at
* `/admin/system-status`; this component only surfaces the handful of
* facts an admin wants on the overview screen: app version, database
* up/down, worker running, image build SHA + timestamp, and the
* server-process start time. Same `useSystemStatus()` data source so we
* don't re-fetch.
*/
import {
Clock,
Cog,
Database,
GitCommit,
Loader2,
Server,
Tag,
} from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { formatDateTime } from "@/lib/format";
import { useTranslations } from "@/lib/i18n/context";
import { StatusItem, useSystemStatus } from "./_shared";
interface VersionResponse {
version: string;
buildSha: string | null;
builtAt: string | null;
}
function useVersion() {
return useQuery({
queryKey: ["public", "version"],
queryFn: async () => {
const res = await fetch("/api/version");
if (!res.ok) throw new Error("Failed to load version");
return (await res.json()).data as VersionResponse;
},
staleTime: 5 * 60_000,
});
}
export function SystemStatusSummary() {
const { t } = useTranslations();
const { data: status, isError } = useSystemStatus();
const { data: version } = useVersion();
return (
<section
aria-labelledby="admin-overview-snapshot-heading"
className="bg-card border-border rounded-xl border p-6"
>
<div className="flex items-center gap-2">
<Server className="text-primary h-5 w-5" aria-hidden="true" />
<h2
id="admin-overview-snapshot-heading"
className="text-lg font-semibold"
>
{t("admin.overview.snapshotTitle")}
</h2>
</div>
{status ? (
<div className="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
<StatusItem
icon={Tag}
label={t("admin.overview.snapshotVersion")}
value={version?.version ?? status.version}
/>
<StatusItem
icon={Database}
label={t("admin.overview.snapshotDatabase")}
value={
status.database === "connected"
? t("admin.databaseConnected")
: t("admin.databaseError")
}
className={
status.database === "connected"
? "text-dracula-green"
: "text-destructive"
}
/>
<StatusItem
icon={Cog}
label={t("admin.overview.snapshotWorker")}
value={
status.worker.running
? t("admin.workerRunning")
: t("admin.workerStopped")
}
className={
status.worker.running ? "text-dracula-green" : "text-destructive"
}
/>
<StatusItem
icon={Clock}
label={t("admin.overview.snapshotStarted")}
value={formatDateTime(status.startTime)}
/>
{(version?.buildSha ?? status.gitCommit) && (
<StatusItem
icon={GitCommit}
label={t("admin.overview.snapshotBuildSha")}
value={version?.buildSha ?? status.gitCommit}
/>
)}
{version?.builtAt && (
<StatusItem
icon={Clock}
label={t("admin.overview.snapshotBuiltAt")}
value={formatDateTime(version.builtAt)}
/>
)}
</div>
) : isError ? (
<div
role="alert"
className="text-destructive bg-destructive/10 border-destructive/30 mt-4 rounded-md border px-3 py-2 text-sm"
>
{t("admin.overview.snapshotLoadError")}
</div>
) : (
<div className="mt-4 flex items-center gap-2">
<Loader2
className="text-muted-foreground h-4 w-4 animate-spin motion-reduce:animate-none"
aria-hidden="true"
/>
<span className="text-muted-foreground text-sm">
{t("admin.overview.snapshotLoading")}
</span>
</div>
)}
</section>
);
}