fix(settings): finish the D0-D8 settings/admin frame cleanup

- Drop the leading blurb paragraph from the Channels + Integrations
  connection panels so each body starts at its first card (D0 alignment).
- Correct three stale CORE/always-on comments: medications has been a
  fail-open toggleable module since D3; CORE is now weight/BP/pulse only.
- Remove the dead variant/isSubtab branch from SourcesSection — no callers
  after the D4 split; render the standalone header unconditionally.
- Render the admin AI-quality card frame + header once and branch only the
  body across loading/error/empty/data, matching the coach-feedback card.
- Rename the Coach activation card's section id from the legacy
  settings-ai-* namespace to settings-coach-activate-* after the S5 move.
- Gate the medication low-stock reminder card on the medications module so
  it hides when the account turns medications off, mirroring mood/coach.
This commit is contained in:
Marc-André Bombeck committed 2026-06-16 23:12:36 +02:00
1 parent a3217ff767
commit dadb6e2d34
8 files changed
+99 -122

No files matched your search

+43 -50
View File
@@ -65,64 +65,56 @@ export function AiQualitySection() {
},
});
if (query.isLoading) {
return (
<div className="bg-card border-border flex items-center gap-2 rounded-xl border p-4 sm:p-6">
<Loader2 className="h-4 w-4 animate-spin motion-reduce:animate-none" />
<span className="text-muted-foreground text-sm">
{t("admin.aiQuality.loading")}
</span>
</div>
);
}
if (query.isError) {
return (
<div
role="alert"
className="text-destructive bg-destructive/10 border-destructive/30 rounded-xl border p-4 text-sm"
>
{t("admin.aiQuality.loadError")}
</div>
);
}
const summary = query.data;
if (!summary || summary.buckets.length === 0) {
return (
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
<SettingsCardHeader
icon={Sparkles}
title={t("admin.aiQuality.title")}
/>
<p className="text-muted-foreground mt-3 pl-7 text-sm">
{t("admin.aiQuality.empty")}
</p>
</div>
);
}
const summary = query.data ?? null;
const buckets = summary?.buckets ?? [];
const hasData = !!summary && buckets.length > 0;
// v1.18.1 — keep the outer card + header rendered across every query
// state (loading / error / empty / data) so the heading baseline stays at
// a constant Y-offset and the section never snaps downward when the data
// resolves. Mirrors <CoachFeedbackSection> (header outside the
// fetch-state branch).
return (
<div className="bg-card border-border space-y-4 rounded-xl border p-4 sm:p-6">
<SettingsCardHeader
icon={Sparkles}
title={t("admin.aiQuality.title")}
status={
<p className="text-muted-foreground text-xs">
{t("admin.aiQuality.windowLabel", {
days: String(summary.windowDays),
})}
{" · "}
{t("admin.aiQuality.generatedAtLabel")}{" "}
{fmt.dateTime(summary.generatedAt)}
</p>
hasData && summary ? (
<p className="text-muted-foreground text-xs">
{t("admin.aiQuality.windowLabel", {
days: String(summary.windowDays),
})}
{" · "}
{t("admin.aiQuality.generatedAtLabel")}{" "}
{fmt.dateTime(summary.generatedAt)}
</p>
) : null
}
/>
<div className="overflow-x-auto">
<table className="w-full text-sm" data-slot="ai-quality-table">
<thead>
{query.isLoading ? (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin motion-reduce:animate-none" />
<span className="text-muted-foreground text-sm">
{t("admin.aiQuality.loading")}
</span>
</div>
) : query.isError ? (
<div
role="alert"
className="text-destructive bg-destructive/10 border-destructive/30 rounded-md border p-3 text-sm"
>
{t("admin.aiQuality.loadError")}
</div>
) : !hasData ? (
<p className="text-muted-foreground text-sm">
{t("admin.aiQuality.empty")}
</p>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm" data-slot="ai-quality-table">
<thead>
<tr className="text-muted-foreground border-border border-b text-left text-xs tracking-wider uppercase">
<th className="py-2 pr-3 font-medium">
{t("admin.aiQuality.colSeverity")}
@@ -148,7 +140,7 @@ export function AiQualitySection() {
</tr>
</thead>
<tbody>
{summary.buckets.map((bucket, idx) => {
{buckets.map((bucket, idx) => {
const ratePct = Math.round(bucket.helpfulRate * 100);
return (
<tr
@@ -184,7 +176,8 @@ export function AiQualitySection() {
})}
</tbody>
</table>
</div>
</div>
)}
</div>
);
}