mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
The dashboard snapshot fires the moment login succeeds instead of waiting for the page chunk (start ~670 to ~200 ms throttled). Guided follow-ups are personalised from the user's snapshot and an answer gets the coach's streamed reaction before the next question. Archived schedule eras can be corrected through a superseding revision that keeps the original as an audit trail, era writes serialise per medication, and duplicate dose times collapse in validation. A rolling medication whose next dose is tomorrow can no longer read as heavily overdue — the card status derives strictly from the served next-due — and the adherence bars keep a fixed geometry in every state. The server-wide assistant key gains its admin card, the one duplicated coach preference collapses onto a single owner, twenty-seven admin and settings card headers move onto the shared pattern with sixty containers normalised, and the remaining hydration warnings are gone.
140 lines
4.5 KiB
TypeScript
140 lines
4.5 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,
|
|
Globe,
|
|
Loader2,
|
|
Server,
|
|
Tag,
|
|
} from "lucide-react";
|
|
import { formatDateTime } from "@/lib/format";
|
|
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import { StatusItem, usePublicVersion, useSystemStatus } from "./_shared";
|
|
|
|
export function SystemStatusSummary() {
|
|
const { t } = useTranslations();
|
|
const { data: status, isError } = useSystemStatus();
|
|
const { data: version } = usePublicVersion();
|
|
|
|
return (
|
|
<section
|
|
aria-labelledby="admin-overview-snapshot-heading"
|
|
className="bg-card border-border rounded-xl border p-4 sm:p-6"
|
|
>
|
|
<SettingsCardHeader
|
|
icon={Server}
|
|
titleId="admin-overview-snapshot-heading"
|
|
title={t("admin.overview.snapshotTitle")}
|
|
/>
|
|
|
|
{status ? (
|
|
<div className="mt-4 grid gap-3 pl-7 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)}
|
|
/>
|
|
)}
|
|
{/* v1.4.27 R5 — surface the offline-geo state so the maintainer
|
|
spots the missing MAXMIND_LICENSE_KEY without crawling logs.
|
|
The field is undefined on legacy responses; the row only
|
|
renders when /api/version answers the new shape. */}
|
|
{version?.offlineGeoEnabled !== undefined && (
|
|
<StatusItem
|
|
icon={Globe}
|
|
label={t("admin.overview.snapshotOfflineGeo")}
|
|
value={
|
|
version.offlineGeoEnabled
|
|
? t("admin.overview.snapshotOfflineGeoOn")
|
|
: t("admin.overview.snapshotOfflineGeoOff")
|
|
}
|
|
className={
|
|
version.offlineGeoEnabled
|
|
? "text-dracula-green"
|
|
: "text-dracula-yellow"
|
|
}
|
|
/>
|
|
)}
|
|
</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>
|
|
);
|
|
}
|