mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
The two consoles used the same primitives and still read as two systems, because everything without a primitive had drifted: the header-to-body step existed in three competing techniques at once (`mt-4` on the first body child, `space-y-4` on the card, `mb-4` on the header), the body started either at the card edge or 28 px in, and both shells hand-rolled the page title instead of calling `PageHeader`. Move the decisions into the primitives so a call site cannot re-open them: - `SettingsCard` is gap-based like `Card` (`flex flex-col gap-4`). The header-to-body step is the container's, not a margin somebody remembers. A dense card still overrides the gap on the card itself. - The body starts at the card edge. `pl-7` is gone — off the spacing scale, and it mis-aligned action rows against their own forms wherever two neighbouring cards disagreed about it. - The card header's description slot loses its `space-y-1`. That was the licence call sites used to stack explainer paragraphs into a muted `text-xs` slot; the slot holds one sentence. - New `SettingsCardActions`: one action row, trailing edge, last thing in the card. `align="start"` spells the one exception, a card whose only actions are links out. - New `StatTile` replaces four hand-rolled label/value boxes in Admin. - `SectionHeading` moves from `insights/` to `ui/`. It is the heading above a group of cards, which is what Settings needs; Settings had been using a card header with no card, which reads as a card that failed to paint. Both shells now render their heading through `PageHeader`, which gained `headingAs="div"` for the second copy each shell paints (mobile above the nav strip, desktop inside the grid) so only one is a real `<h1>`. The page explainer is muted everywhere: the primitive said foreground, the standards and every hand-rolled copy said muted, so pages disagreed with each other. Medications, Vorsorge, the labs / custom-metric / illness detail pages and the clinician view drop their hand-rolled title blocks too, including the mid-word truncation on the medications subtitle. Tests pin the parts that can be checked exactly: the card's gap, the description slot's missing rhythm, the action row's two alignments, the `<h1>`-free shells, and a sweep asserting `pl-7` stays gone.
79 lines
3.1 KiB
TypeScript
79 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { ShieldAlert, Users } from "lucide-react";
|
|
|
|
import { SettingsCard } from "@/components/settings/settings-card";
|
|
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
|
import { AllergyManager } from "@/components/records/allergy-manager";
|
|
import { FamilyHistoryManager } from "@/components/records/family-history-manager";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
|
|
/**
|
|
* v1.37.0 — Settings → Anamnese, inside somebody else's record.
|
|
*
|
|
* ## Why a second component instead of the personal one
|
|
*
|
|
* `AnamnesisSection` renders five managers, and only two of them belong to the
|
|
* record. The other three write through `/api/coach/about-me` and
|
|
* `/api/anamnesis/facts`, both of which resolve a bare `requireAuth()` and
|
|
* therefore refuse outright under a switch. Mounting the personal section here
|
|
* would put three forms on screen that answer 403 on submit — the precise
|
|
* failure `useRecordCapabilities` was written to end ("a control that appeared
|
|
* before its route answered"). So this surface is the intersection of the
|
|
* section and what the record routes admit, and it is a separate component so
|
|
* that intersection is visible rather than expressed as four conditionals
|
|
* inside the personal one.
|
|
*
|
|
* ## What is here
|
|
*
|
|
* Allergies and family history. Both post to routes that resolve
|
|
* `requireRecordAuth("manage", "profile")`, so both land under the record
|
|
* owner with the delegate named as actor, and a READ or WRITE delegate is
|
|
* refused by the route as well as never reaching this page.
|
|
*
|
|
* Family history is the one admitted write where third-party health
|
|
* information is present by design — the row describes the record subject's
|
|
* relatives, and often enough the delegate IS that relative, asserting a
|
|
* condition about themselves into somebody else's record. The route's own
|
|
* comment named that discomfort and left it for the surface to say out loud,
|
|
* because no route comment can. The sentence under the card is where it gets
|
|
* said.
|
|
*
|
|
* The cache is not a concern here and the reason is worth naming: switching
|
|
* records clears the whole query client, so the allergy and family-history
|
|
* lists cannot carry one record's rows into another's view.
|
|
*/
|
|
export function RecordAnamnesisSection() {
|
|
const { t } = useTranslations();
|
|
|
|
return (
|
|
<div className="space-y-6" data-slot="record-anamnesis-section">
|
|
<SettingsCard>
|
|
<SettingsCardHeader
|
|
icon={ShieldAlert}
|
|
title={t("records.allergies.cardTitle")}
|
|
description={t("records.allergies.cardDescription")}
|
|
/>
|
|
<AllergyManager />
|
|
</SettingsCard>
|
|
|
|
<SettingsCard>
|
|
<SettingsCardHeader
|
|
icon={Users}
|
|
title={t("records.family.cardTitle")}
|
|
description={t("records.family.cardDescription")}
|
|
/>
|
|
<FamilyHistoryManager />
|
|
<p
|
|
data-slot="record-anamnesis-relative-note"
|
|
className="text-foreground text-sm"
|
|
>
|
|
{t("recordSharing.anamnesis.relativeNote")}
|
|
</p>
|
|
</SettingsCard>
|
|
|
|
<p className="text-muted-foreground text-xs">{t("records.disclaimer")}</p>
|
|
</div>
|
|
);
|
|
}
|