From 005cb32ea8e00b33617c8dcbffb277c00ac9c32a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 8 Oct 2026 18:53:24 +0200 Subject: [PATCH] Give every empty list one empty state and one add action EmptyState now has a single action shape: centred at its own width on the 44 px tap floor, never stretched edge to edge on some pages and compact on others; the per-call-site size switch is gone, and the dashed card takes the card radius. While a list is empty its empty state carries the add action, so the header drops its own add button there (measurements, mood, medications, labs, vaccinations, documents, illness, check-ups, visits, procedures). The dashboard medication tile of an account without medications offers 'Add a medication' instead of 'no data in the chosen range'. --- messages/de.json | 3 +- messages/en.json | 3 +- messages/es.json | 3 +- messages/fr.json | 3 +- messages/it.json | 3 +- messages/ko.json | 3 +- messages/pl.json | 3 +- src/app/insights/bmi/page.tsx | 1 - src/app/insights/medications/page.tsx | 1 - src/app/insights/page-client.tsx | 2 - src/app/labs/page.tsx | 10 ++++- src/app/measurements/page.tsx | 6 ++- src/app/medications/page-client.tsx | 6 ++- src/app/mood/page-client.tsx | 10 ++++- .../charts/medication-compliance-chart.tsx | 37 +++++++++++++++++++ src/components/documents/documents-view.tsx | 18 +++++---- .../encounters/procedures-section.tsx | 8 +++- src/components/encounters/visits-section.tsx | 5 ++- src/components/illness/illness-view.tsx | 5 ++- .../insights/metric-empty-state.tsx | 3 +- src/components/labs/lab-list.tsx | 16 +++++++- .../layout/module-disabled-notice.tsx | 1 - .../layout/shared-record-unavailable.tsx | 1 - .../vorsorge-section.tsx | 6 ++- .../measurements/measurement-list.tsx | 15 ++++++++ .../sections/inventory-section.tsx | 1 - src/components/mood/mood-list.tsx | 13 ++++++- .../practitioners/practitioner-list.tsx | 1 - src/components/ui/empty-state.tsx | 30 ++++----------- .../vaccinations/vaccinations-view.tsx | 6 ++- 30 files changed, 158 insertions(+), 65 deletions(-) diff --git a/messages/de.json b/messages/de.json index a85e3273a..e4a12f169 100644 --- a/messages/de.json +++ b/messages/de.json @@ -2502,7 +2502,8 @@ "moodHeatmapEmpty": "Stimmungs-Heatmap, noch keine Tage erfasst.", "complianceHeatmap": "Heatmap zur Medikamenteneinnahme von {start} bis {end}, {days} aktive Tage.", "complianceHeatmapEmpty": "Heatmap zur Medikamenteneinnahme, noch keine aktiven Tage." - } + }, + "medicationsEmptyAction": "Medikament anlegen" }, "chart": { "overlay": { diff --git a/messages/en.json b/messages/en.json index 86645a1e4..083e46de2 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2502,7 +2502,8 @@ "moodHeatmapEmpty": "Mood heatmap, no days logged yet.", "complianceHeatmap": "Medication compliance heatmap from {start} to {end}, {days} active days.", "complianceHeatmapEmpty": "Medication compliance heatmap, no active days yet." - } + }, + "medicationsEmptyAction": "Add a medication" }, "chart": { "overlay": { diff --git a/messages/es.json b/messages/es.json index 30a3741db..fc0f84eeb 100644 --- a/messages/es.json +++ b/messages/es.json @@ -2502,7 +2502,8 @@ "moodHeatmapEmpty": "Mapa de calor de estado de ánimo, aún no hay días registrados.", "complianceHeatmap": "Mapa de calor de cumplimiento de medicación de {start} a {end}, {days} días activos.", "complianceHeatmapEmpty": "Mapa de calor de cumplimiento de medicación, aún no hay días activos." - } + }, + "medicationsEmptyAction": "Añadir un medicamento" }, "chart": { "overlay": { diff --git a/messages/fr.json b/messages/fr.json index 8397776d8..d6f04c335 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -2502,7 +2502,8 @@ "moodHeatmapEmpty": "Carte thermique de l'humeur, aucun jour enregistré pour l'instant.", "complianceHeatmap": "Carte thermique d'observance des médicaments du {start} au {end}, {days} jours actifs.", "complianceHeatmapEmpty": "Carte thermique d'observance des médicaments, aucun jour actif pour l'instant." - } + }, + "medicationsEmptyAction": "Ajouter un médicament" }, "chart": { "overlay": { diff --git a/messages/it.json b/messages/it.json index 428691394..dacbf1728 100644 --- a/messages/it.json +++ b/messages/it.json @@ -2502,7 +2502,8 @@ "moodHeatmapEmpty": "Mappa di calore dell'umore, nessun giorno ancora registrato.", "complianceHeatmap": "Mappa di calore dell'aderenza ai farmaci dal {start} al {end}, {days} giorni attivi.", "complianceHeatmapEmpty": "Mappa di calore dell'aderenza ai farmaci, nessun giorno attivo." - } + }, + "medicationsEmptyAction": "Aggiungi un farmaco" }, "chart": { "overlay": { diff --git a/messages/ko.json b/messages/ko.json index 48f25e772..af007eb90 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -2502,7 +2502,8 @@ "days90Label": "90일", "days90Title": "최근 90일", "daysAllLabel": "전체", - "daysAllTitle": "전체 기간" + "daysAllTitle": "전체 기간", + "medicationsEmptyAction": "약 추가하기" }, "chart": { "overlay": { diff --git a/messages/pl.json b/messages/pl.json index 1f4892372..53e772597 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -2502,7 +2502,8 @@ "moodHeatmapEmpty": "Mapa cieplna nastroju, brak zarejestrowanych dni.", "complianceHeatmap": "Mapa cieplna przyjmowania leków od {start} do {end}, {days} aktywnych dni.", "complianceHeatmapEmpty": "Mapa cieplna przyjmowania leków, brak aktywnych dni." - } + }, + "medicationsEmptyAction": "Dodaj lek" }, "chart": { "overlay": { diff --git a/src/app/insights/bmi/page.tsx b/src/app/insights/bmi/page.tsx index cdc5ea3c6..ce216abdf 100644 --- a/src/app/insights/bmi/page.tsx +++ b/src/app/insights/bmi/page.tsx @@ -86,7 +86,6 @@ export default function InsightsBmiPage() { icon={} title={t("insights.bmiEmptyTitle")} description={t("insights.bmiEmptyDescription")} - ctaSize="lg" action={ + ) : undefined + } + /> ) : !hasData ? (
{t("charts.noData")} diff --git a/src/components/documents/documents-view.tsx b/src/components/documents/documents-view.tsx index ffe1b6457..dbecd0757 100644 --- a/src/components/documents/documents-view.tsx +++ b/src/components/documents/documents-view.tsx @@ -984,13 +984,16 @@ export function DocumentsView() { <> {/* Renders only with a connected Paperless-ngx or Papra. */} - + {/* An empty vault's empty state carries the upload. */} + {showEmpty && !isFiltered ? null : ( + + )} ) : null}
@@ -1072,7 +1075,6 @@ export function DocumentsView() { icon={} title={t("documents.empty.title")} description={t("documents.empty.description")} - ctaSize="lg" action={ canManageDocuments ? ( - {addButton} + {/* The empty state carries the add action while there is no + visit. */} + {!list.isPending && !list.isError && empty ? null : addButton} } /> @@ -113,7 +115,6 @@ export function VisitsSection({ enabled = true }: { enabled?: boolean }) { title={t("encounters.emptyTitle")} description={t("encounters.emptyDescription")} action={addButton ?? undefined} - ctaSize="lg" /> ) : (
diff --git a/src/components/illness/illness-view.tsx b/src/components/illness/illness-view.tsx index 0ef5b8f1f..4133f5ecf 100644 --- a/src/components/illness/illness-view.tsx +++ b/src/components/illness/illness-view.tsx @@ -432,7 +432,9 @@ export function IllnessView() { )} {/* v1.18.6 (MOD-02) — the add button reads "hinzufügen" like every other module, not the bespoke "neue Episode". */} - {canAddEpisode && ( + {/* Hidden while there is no episode: the empty state below + carries the add action then. */} + {canAddEpisode && (isLoading || isError || hasEpisodes) && ( ` @@ -64,7 +64,6 @@ export function MetricEmptyState({ icon={icon} title={title} description={description} - ctaSize="lg" action={cta} /> diff --git a/src/components/labs/lab-list.tsx b/src/components/labs/lab-list.tsx index 668ca4813..934bb138a 100644 --- a/src/components/labs/lab-list.tsx +++ b/src/components/labs/lab-list.tsx @@ -1,7 +1,7 @@ "use client"; import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; -import { useMemo } from "react"; +import { useMemo, useEffect } from "react"; import Link from "next/link"; import { useQuery } from "@tanstack/react-query"; import { ChevronRight, FlaskConical } from "lucide-react"; @@ -136,7 +136,14 @@ const MOBILE_CELL = { notLinked: "max-lg:col-span-full max-lg:row-start-3", } as const; -export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) { +export function LabList({ + onAddFirst, + onEmptyChange, +}: { + onAddFirst?: () => void; + /** Told whether the list is empty, so the page can drop its header add. */ + onEmptyChange?: (empty: boolean) => void; +} = {}) { const { t } = useTranslations(); const labNumber = useLabNumber(); const labDate = useLabDate(); @@ -196,6 +203,11 @@ export function LabList({ onAddFirst }: { onAddFirst?: () => void } = {}) { const shown = data?.results?.length ?? 0; const truncated = total > shown; + const listEmpty = !isLoading && !isError && groups.length === 0; + useEffect(() => { + onEmptyChange?.(listEmpty); + }, [listEmpty, onEmptyChange]); + if (isLoading) { return (
diff --git a/src/components/layout/module-disabled-notice.tsx b/src/components/layout/module-disabled-notice.tsx index 84e5a35aa..3fb4373e0 100644 --- a/src/components/layout/module-disabled-notice.tsx +++ b/src/components/layout/module-disabled-notice.tsx @@ -88,7 +88,6 @@ export function ModuleDisabledNotice({ icon={icon} title={t(copy.title, { module: moduleName })} description={t(copy.description, { module: moduleName })} - ctaSize="lg" action={state === "disabled" ? action : undefined} />
diff --git a/src/components/layout/shared-record-unavailable.tsx b/src/components/layout/shared-record-unavailable.tsx index aa9775faf..9e22ee107 100644 --- a/src/components/layout/shared-record-unavailable.tsx +++ b/src/components/layout/shared-record-unavailable.tsx @@ -36,7 +36,6 @@ export function SharedRecordUnavailable() { icon={} title={t("recordSharing.unavailable.title")} description={t("recordSharing.unavailable.description")} - ctaSize="lg" action={
@@ -135,7 +119,7 @@ function EmptyState({ role="status" aria-live="polite" className={cn( - "border-border bg-card rounded-lg border border-dashed", + "border-border bg-card rounded-xl border border-dashed", className, )} {...props} diff --git a/src/components/vaccinations/vaccinations-view.tsx b/src/components/vaccinations/vaccinations-view.tsx index 31e3493b8..af9c4ced8 100644 --- a/src/components/vaccinations/vaccinations-view.tsx +++ b/src/components/vaccinations/vaccinations-view.tsx @@ -104,7 +104,11 @@ export function VaccinationsView() { } description={t("vaccinations.subtitle")} - actions={addButton} + // The empty state carries the add action while the Impfpass is + // empty; the header offers it once there is a list to add to. + actions={ + !isLoading && !isError && records.length === 0 ? null : addButton + } />