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:
Marc-André Bombeck committed 2026-10-20 13:31:02 +02:00
1 parent fe021c0ffd
commit e7ee416e19
56 files changed
+353 -224

No files matched your search

+6 -3
View File
@@ -28,6 +28,7 @@ import { useTranslations, useFormatters } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
import { apiGet } from "@/lib/api/api-fetch";
import { helpfulRateColour } from "./_shared";
import { EmptyState } from "@/components/ui/empty-state";
interface FeedbackBucket {
severity: string;
@@ -110,9 +111,11 @@ export function AiQualitySection() {
onRetry={() => void query.refetch()}
/>
) : !hasData ? (
<p className="text-muted-foreground text-sm">
{t("admin.aiQuality.empty")}
</p>
<EmptyState
variant="plain"
size="compact"
title={t("admin.aiQuality.empty")}
/>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm" data-slot="ai-quality-table">
@@ -28,6 +28,7 @@ import { useTranslations, useFormatters } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
import { apiGet } from "@/lib/api/api-fetch";
import { helpfulRateColour } from "./_shared";
import { EmptyState } from "@/components/ui/empty-state";
interface CoachFeedbackBucket {
promptVersion: string;
@@ -105,9 +106,11 @@ export function CoachFeedbackSection() {
onRetry={() => void query.refetch()}
/>
) : !hasData ? (
<p className="text-muted-foreground text-sm">
{t("admin.coachFeedback.empty")}
</p>
<EmptyState
variant="plain"
size="compact"
title={t("admin.coachFeedback.empty")}
/>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm" data-slot="coach-feedback-table">
+6 -3
View File
@@ -53,6 +53,7 @@ import {
import { KeyBackupCard } from "./key-backup-card";
import { useKeyBackupStatus } from "./use-key-backup-status";
import { QueryErrorRow } from "@/components/ui/query-error-row";
import { EmptyState } from "@/components/ui/empty-state";
interface ColumnScan {
model: string;
@@ -446,9 +447,11 @@ export function ColumnCoverage({
{open ? (
<>
{shown.length === 0 ? (
<p className="text-muted-foreground text-sm">
{t("admin.section.encryption.columnsNoneWithRows")}
</p>
<EmptyState
variant="plain"
size="compact"
title={t("admin.section.encryption.columnsNoneWithRows")}
/>
) : (
/* Focusable, named scroll region: on a phone the table scrolls
sideways, and a keyboard user has to be able to reach it
+6 -3
View File
@@ -48,6 +48,7 @@ import { SettingsCardHeader } from "@/components/settings/_card-header";
import { apiGet } from "@/lib/api/api-fetch";
import { readStoredTimezone } from "@/lib/timezone-mirror";
import { resolveIntlLocale } from "@/lib/format-locale";
import { EmptyState } from "@/components/ui/empty-state";
interface HostMetricsApiSample {
capturedAt: string;
@@ -227,9 +228,11 @@ export function HostMetricsChart() {
</span>
}
/>
<p className="text-muted-foreground text-xs">
{t("admin.hostMetrics.empty")}
</p>
<EmptyState
variant="plain"
size="compact"
title={t("admin.hostMetrics.empty")}
/>
</SettingsCard>
);
}
@@ -11,6 +11,7 @@ import { queryKeys } from "@/lib/query-keys";
import { apiGet } from "@/lib/api/api-fetch";
import { formatDateTime } from "@/lib/format";
import { QueryErrorRow } from "@/components/ui/query-error-row";
import { EmptyState } from "@/components/ui/empty-state";
/**
* v1.37.31 — the delivery-health readout the `provider_health` ledger never
@@ -143,9 +144,11 @@ export function ProviderHealthSection() {
onRetry={() => void refetch()}
/>
) : providers.length === 0 ? (
<p className="text-muted-foreground text-sm">
{t("admin.providerHealth.empty")}
</p>
<EmptyState
variant="plain"
size="compact"
title={t("admin.providerHealth.empty")}
/>
) : (
<ul className="flex flex-col gap-3" data-testid="provider-health-list">
{providers.map((p) => (
+6 -3
View File
@@ -27,6 +27,7 @@ import {
describeChannelTestFailures,
type ChannelTestResult,
} from "./notification-test-failures";
import { EmptyState } from "@/components/ui/empty-state";
interface NotificationHealth {
windowHours: number;
@@ -73,9 +74,11 @@ function NotificationHealthPanel() {
)}
{data && data.channels.length === 0 && (
<p className="text-muted-foreground text-sm">
{t("admin.notificationHealth.empty")}
</p>
<EmptyState
variant="plain"
size="compact"
title={t("admin.notificationHealth.empty")}
/>
)}
{data && data.channels.length > 0 && (
@@ -29,6 +29,7 @@ import {
useSystemStatus,
type FailingQueue,
} from "./_shared";
import { EmptyState } from "@/components/ui/empty-state";
// v1.4.16: Recharts is ~108 KiB Brotli — defer-load the host
// metrics chart so the system-status page only ships it when an admin
@@ -89,12 +90,14 @@ export function FailingJobsCard({
description={t("admin.failingJobs.description")}
/>
{failingJobs.queues.length === 0 ? (
<p
<EmptyState
variant="plain"
size="compact"
title={t("admin.failingJobs.none", {
hours: failingJobs.windowHours,
})}
data-testid="failing-jobs-none"
className="text-muted-foreground text-sm"
>
{t("admin.failingJobs.none", { hours: failingJobs.windowHours })}
</p>
/>
) : (
<ul data-testid="failing-jobs-list" className="space-y-3">
{failingJobs.queues.map((queue) => (