mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
refactor(ui): move 62 hand-written empty states onto EmptyState
The admin sections, the cycle, illness, medication, mood, records and settings cards, the pickers and dialogs said nothing-here-yet in a hand-written muted paragraph in five sizes and three alignments. List and section states now render through EmptyState (plain, compact; the three with a next step carry it as the action), and the six chart bodies through ChartEmptyState at their former height. The thirteen that stay are a field without a value or a hint inside an editor, not a list state. The ratchet ceiling drops from 75 to 13.
This commit is contained in:
1 parent
fe021c0ffd
commit
e7ee416e19
56 files changed
+353
-224
No files matched your search
@@ -42,6 +42,7 @@ import type {
|
||||
MedicationEfficacyDTO,
|
||||
EfficacyTargetView,
|
||||
} from "@/lib/medications/efficacy/build-efficacy";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
// v1.30 UI audit (M8) — `EfficacyChart` mounts inside `TargetCard`'s own
|
||||
// `<Card>` as a bare 220px `ResponsiveContainer` with no chrome of its own.
|
||||
@@ -299,9 +300,11 @@ function TargetCard({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("medications.efficacy.noSeries")}
|
||||
</p>
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.efficacy.noSeries")}
|
||||
/>
|
||||
)}
|
||||
|
||||
<BeforeAfterCard target={target} minWeeks={dto.minWeeksPerSide} />
|
||||
|
||||
@@ -105,6 +105,7 @@ import { IntakeTrackingBody } from "@/components/medications/sections/intake-tra
|
||||
import { MedicationRelatedSection } from "@/components/medications/detail/medication-related-section";
|
||||
import type { RelatedLink } from "@/lib/links/related";
|
||||
import { InlineAction } from "@/components/ui/inline-action";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
// v1.15.20 — Recharts stays out of the detail page's initial bundle:
|
||||
// both chart bodies load through `next/dynamic` (client-only) with the
|
||||
@@ -677,9 +678,11 @@ export function MedicationDetailTabs({
|
||||
longDays={longDays}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("medications.detail.uebersicht.complianceEmpty")}
|
||||
</p>
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.detail.uebersicht.complianceEmpty")}
|
||||
/>
|
||||
)}
|
||||
</MedicationDetailSection>
|
||||
)}
|
||||
|
||||
@@ -115,6 +115,7 @@ import {
|
||||
import { apiDelete, apiGet, apiPost, apiPut } from "@/lib/api/api-fetch";
|
||||
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
|
||||
import { QueryErrorRow } from "@/components/ui/query-error-row";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
@@ -477,9 +478,11 @@ export function DoseHistoryLedger({
|
||||
)}
|
||||
|
||||
{data && !isLoading && groups.length === 0 && (
|
||||
<p className="text-muted-foreground py-6 text-sm">
|
||||
{t("medications.detail.verlauf.empty")}
|
||||
</p>
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.detail.verlauf.empty")}
|
||||
/>
|
||||
)}
|
||||
|
||||
{data &&
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type InjectionSiteKey,
|
||||
} from "@/lib/medications/injection-sites";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
/**
|
||||
* v1.8.5 — post-dose injection-site capture.
|
||||
@@ -111,9 +112,11 @@ export function LogInjectionSiteDialog({
|
||||
}
|
||||
>
|
||||
{allowed.length === 0 ? (
|
||||
<p className="text-muted-foreground py-4 text-center text-sm">
|
||||
{t("medications.logInjectionSiteNoneAvailable")}
|
||||
</p>
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.logInjectionSiteNoneAvailable")}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<InjectionSitePicker
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
} from "@/components/medications/use-medication-intake";
|
||||
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
|
||||
import { useMedicationDueThresholds } from "@/lib/queries/use-reminder-thresholds";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
interface LogIntakeSchedule extends ScheduleWindowInput {
|
||||
label: string | null;
|
||||
@@ -276,9 +277,11 @@ export function LogIntakeDialog({
|
||||
className="space-y-4"
|
||||
>
|
||||
{medications.length === 0 ? (
|
||||
<p className="text-muted-foreground py-4 text-sm">
|
||||
{t("medications.logIntake.noMedications")}
|
||||
</p>
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.logIntake.noMedications")}
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -26,6 +26,7 @@ import { prefersReducedMotion } from "@/lib/charts/reduced-motion";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { formatDose } from "@/lib/medications/format-dose";
|
||||
import { runSaveMedicationListOrder } from "@/lib/queries/use-medication-list-layout";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
/**
|
||||
* v1.16.10 — manual medication ordering, shared by BOTH /medications
|
||||
@@ -153,9 +154,11 @@ export function MedicationOrderEditor({
|
||||
// itself scrollable (second scrollbar — UI-STANDARDS §9 one-scroll-floor).
|
||||
<div className="relative space-y-4" data-slot="medication-order-editor">
|
||||
{medications.length === 0 && (
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("medications.emptyTitle")}
|
||||
</p>
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.emptyTitle")}
|
||||
/>
|
||||
)}
|
||||
{activeIds.length > 0 && (
|
||||
<ReorderSection
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
type NutrientCatalogEntryDto,
|
||||
type SupplementLabelProposal,
|
||||
} from "./use-composition";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
/** The categories the composition is offered for without being asked. */
|
||||
export const COMPOSITION_CATEGORIES: ReadonlySet<string> = new Set([
|
||||
@@ -520,9 +521,11 @@ export function CompositionSection({
|
||||
}
|
||||
>
|
||||
{ingredients.length === 0 ? (
|
||||
<p className="text-muted-foreground">
|
||||
{t("medications.composition.empty")}
|
||||
</p>
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.composition.empty")}
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<ul className="divide-border/60 divide-y" role="list">
|
||||
|
||||
@@ -37,6 +37,7 @@ import { ApiError, apiDelete, apiPatch, apiPost } from "@/lib/api/api-fetch";
|
||||
import { formatDate } from "@/lib/date-format";
|
||||
import { useDateFormatPreference, useTranslations } from "@/lib/i18n/context";
|
||||
import { invalidateMedicationReads } from "@/lib/query-keys";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
/** The refusal codes the course routes answer, mapped to their copy. */
|
||||
const REFUSAL_KEYS: Record<string, string> = {
|
||||
@@ -262,9 +263,11 @@ export function CoursesRow({
|
||||
</div>
|
||||
|
||||
{courses.length === 0 ? (
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{t("medications.course.none")}
|
||||
</p>
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.course.none")}
|
||||
/>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{courses.map((course, index) => (
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
import { MedicationDetailSection } from "@/components/medications/medication-detail-section";
|
||||
import { apiDelete, apiGetList, apiPost } from "@/lib/api/api-fetch";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
/**
|
||||
* v1.4.25 — GLP-1 side-effect section for the medication detail
|
||||
@@ -339,9 +340,11 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) {
|
||||
)}
|
||||
|
||||
{!isLoading && !isError && items.length === 0 && (
|
||||
<p className="text-muted-foreground py-1">
|
||||
{t("medications.sideEffects.emptyState")}
|
||||
</p>
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.sideEffects.emptyState")}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isLoading && !isError && items.length > 0 && (
|
||||
|
||||
@@ -31,6 +31,7 @@ import { queryKeys } from "@/lib/query-keys";
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { MedicationDetailSection } from "@/components/medications/medication-detail-section";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
|
||||
interface DoseChange {
|
||||
id: string;
|
||||
@@ -141,12 +142,12 @@ export function TitrationTimeline({
|
||||
dataSlot="titration-timeline"
|
||||
>
|
||||
{!hasPlan ? (
|
||||
<p
|
||||
className="text-muted-foreground bg-muted/40 rounded-md p-4 text-sm"
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
size="compact"
|
||||
title={t("medications.titration.empty")}
|
||||
data-slot="titration-timeline-empty"
|
||||
>
|
||||
{t("medications.titration.empty")}
|
||||
</p>
|
||||
/>
|
||||
) : (
|
||||
<ol className="relative space-y-0" data-slot="titration-timeline-list">
|
||||
{steps.map((step, i) => (
|
||||
|
||||
Reference in new issue
Block a user