diff --git a/src/app/medications/page-client.tsx b/src/app/medications/page-client.tsx index d7dc51dd4..0cc5afb05 100644 --- a/src/app/medications/page-client.tsx +++ b/src/app/medications/page-client.tsx @@ -33,6 +33,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { EmptyState } from "@/components/ui/empty-state"; +import { PageHeader } from "@/components/ui/page-header"; import { QueryErrorCard } from "@/components/ui/query-error-card"; import { Skeleton } from "@/components/ui/skeleton"; import { @@ -359,14 +360,10 @@ export default function MedicationsPageClient() { if (!isAuthenticated) { return (
-
-

- {t("medications.title")} -

-

- {t("medications.loginRequired")} -

-
+
); } @@ -407,90 +404,83 @@ export default function MedicationsPageClient() { return (
-
-
-

- {t("medications.title")} -

- {/* v1.4.34 IW-G — subtitle stays visible on mobile so the - H1 isn't an unframed label. */} -

- {t("medications.subtitle")} -

-
-
- {/* v1.16.11 (#316) — "Alle fälligen einnehmen". Contextual, not a + {t("medications.title")} + } + description={t("medications.subtitle")} + actions={ + <> + {/* v1.16.11 (#316) — "Alle fälligen einnehmen". Contextual, not a permanent fixture: it renders only while at least TWO medications are currently due (a single due dose is the card's own one-tap job). Calm outline variant so the primary Add button keeps the visual lead; same responsive 44-px mobile tap floor as its neighbours. */} - {canAdd && dueMeds.length >= 2 && ( - - )} - {/* v1.16.11 — the wrench is the one customize entry point: + {canAdd && dueMeds.length >= 2 && ( + + )} + {/* v1.16.11 — the wrench is the one customize entry point: it links to /settings/medications, which owns the view preference (cards ⇄ table) and the manual-order editor. Same glyph, slot (left of the add button) and responsive 44-px mobile tap floor as the dashboard and insights headers. */} - {canManage && ( - - )} - {/* v1.14.0 — the "Add" button is now a choice: log an intake + +
-
+ {canAdd && ( + + + + + + setLogIntakeOpen(true)} + disabled={activeMeds.length === 0} + > + + {t("medications.addChoice.logIntake")} + + + + {t("medications.addChoice.newMedication")} + + + + )} + + } + /> {isLoading || isLayoutLoading ? ( // v1.11.3 C4 — skeletons instead of a bare centred spinner so the diff --git a/src/components/admin/_stat-tile.tsx b/src/components/admin/_stat-tile.tsx new file mode 100644 index 000000000..de52cf4ed --- /dev/null +++ b/src/components/admin/_stat-tile.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +/** + * `` — the one label/value mini tile in the Admin console. + * + * Four hand-rolled variants of this box existed side by side: `StatusItem` + * (`bg-muted/50 p-3`), the encryption section's local `Stat` + * (`bg-muted/40 px-3 py-2`), the user-management inline panels + * (`bg-muted/80 p-4`), and the backups upload box (`bg-muted/30 p-3`). Four + * fills, three paddings, one role. This is the role. + * + * `icon` is optional — a tile with a glyph and one without still share the + * frame, so a grid can mix them without a step in tile height. + */ +export function StatTile({ + icon: Icon, + label, + value, + valueClassName, + className, +}: { + icon?: React.ComponentType<{ className?: string }>; + label: React.ReactNode; + value: React.ReactNode; + /** Extra classes on the value line — a status colour, or `font-mono`. */ + valueClassName?: string; + className?: string; +}) { + return ( +
+
+ {Icon ? : null} + {label} +
+

{value}

+
+ ); +} diff --git a/src/components/admin/admin-shell.tsx b/src/components/admin/admin-shell.tsx index 9ba3cebad..2961dbb7a 100644 --- a/src/components/admin/admin-shell.tsx +++ b/src/components/admin/admin-shell.tsx @@ -37,6 +37,7 @@ import { } from "lucide-react"; import { cn } from "@/lib/utils"; +import { PageHeader } from "@/components/ui/page-header"; import { SUB_SHELL_GRID_FLOOR } from "@/components/layout/shell-metrics"; import { useAuth } from "@/hooks/use-auth"; import { useTranslations } from "@/lib/i18n/context"; @@ -253,21 +254,15 @@ export function AdminShell({ active, children }: AdminShellProps) { ? t("admin.subtitle") : t(`admin.section.${activeSlug}.subtitle`); + // Same two-copy heading as the Settings shell (mobile above the chip strip, + // desktop inside the grid row) and the same primitive, so the two consoles + // cannot drift a step apart again. const headingBlock = (mobile = false) => ( -
- {mobile ? ( -
- {headingTitle} -
- ) : ( -

{headingTitle}

- )} -

{headingSubtitle}

-
+ ); // v1.4.25 W8 — AuthShell wraps the page in `px-4 py-6 md:px-6` diff --git a/src/components/admin/ai-quality-section.tsx b/src/components/admin/ai-quality-section.tsx index 98e31a7a7..57cb8adf4 100644 --- a/src/components/admin/ai-quality-section.tsx +++ b/src/components/admin/ai-quality-section.tsx @@ -76,7 +76,7 @@ export function AiQualitySection() { // resolves. Mirrors (header outside the // fetch-state branch). return ( - + -
+