mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(ui): route bare read-failure branches through QueryErrorCard
Eight data surfaces still rendered a read failure as a bare text-destructive paragraph with no retry: the biomarker manager, the lab values list, the three record managers (allergies, family history, conditions), the illness episode detail, and the intake history table; the lab biomarker detail carried an inline near-duplicate of the primitive from before it existed. Replace all of them with QueryErrorCard wired to the query's refetch, keeping each surface's own error string. Inline role="alert" text remains the pattern for mutation errors inside forms.
This commit is contained in:
1 parent
21b65788ae
commit
9a35cfb3e9
8 files changed
+54
-41
No files matched your search
@@ -13,6 +13,7 @@ import { useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useTranslations, useFormatters } from "@/lib/i18n/context";
|
||||
|
||||
@@ -35,7 +36,12 @@ function todayLocal(): string {
|
||||
export function IllnessEpisodeDetail({ episodeId }: { episodeId: string }) {
|
||||
const { t } = useTranslations();
|
||||
const fmt = useFormatters();
|
||||
const { data: episode, isLoading, isError } = useIllnessEpisode(episodeId);
|
||||
const {
|
||||
data: episode,
|
||||
isLoading,
|
||||
isError,
|
||||
refetch,
|
||||
} = useIllnessEpisode(episodeId);
|
||||
const resolve = useResolveEpisode();
|
||||
|
||||
const [logOpen, setLogOpen] = useState(false);
|
||||
@@ -44,9 +50,10 @@ export function IllnessEpisodeDetail({ episodeId }: { episodeId: string }) {
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<p className="text-destructive py-8 text-center text-sm">
|
||||
{t("illness.loadError")}
|
||||
</p>
|
||||
<QueryErrorCard
|
||||
title={t("illness.loadError")}
|
||||
onRetry={() => void refetch()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { toast } from "sonner";
|
||||
import { DeleteButton } from "@/components/data-list";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { ResponsiveSheet } from "@/components/ui/responsive-sheet";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { apiDelete, apiGet, apiPut } from "@/lib/api/api-fetch";
|
||||
@@ -40,7 +41,7 @@ export function BiomarkerManager() {
|
||||
const [editing, setEditing] = useState<BiomarkerDto | null>(null);
|
||||
const [formFooterEl, setFormFooterEl] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
const { data, isLoading, isError, refetch } = useQuery({
|
||||
queryKey: queryKeys.biomarkers(),
|
||||
queryFn: () => apiGet<BiomarkerListResponse>("/api/biomarkers"),
|
||||
});
|
||||
@@ -174,9 +175,10 @@ export function BiomarkerManager() {
|
||||
))}
|
||||
</div>
|
||||
) : isError ? (
|
||||
<p className="text-destructive py-6 text-center text-sm">
|
||||
{t("labs.biomarker.loadError")}
|
||||
</p>
|
||||
<QueryErrorCard
|
||||
title={t("labs.biomarker.loadError")}
|
||||
onRetry={() => void refetch()}
|
||||
/>
|
||||
) : markers.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={<FlaskConical className="size-6" />}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { DeleteButton } from "@/components/data-list";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { ResponsiveSheet } from "@/components/ui/responsive-sheet";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { InsightStatusCard } from "@/components/insights/insight-status-card";
|
||||
@@ -248,21 +249,13 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) {
|
||||
{description}
|
||||
</p>
|
||||
</header>
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center gap-3 py-8 text-center">
|
||||
<p className="text-destructive text-sm">{t("labs.loadError")}</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (markerError) void refetchMarker();
|
||||
if (listError) void refetchList();
|
||||
}}
|
||||
>
|
||||
{t("common.retry")}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<QueryErrorCard
|
||||
title={t("labs.loadError")}
|
||||
onRetry={() => {
|
||||
if (markerError) void refetchMarker();
|
||||
if (listError) void refetchList();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
@@ -33,6 +34,7 @@ export function LabValuesList({ biomarkerId }: { biomarkerId: string }) {
|
||||
data: list,
|
||||
isLoading,
|
||||
isError,
|
||||
refetch,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
@@ -60,9 +62,10 @@ export function LabValuesList({ biomarkerId }: { biomarkerId: string }) {
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<p className="text-destructive py-8 text-center text-sm">
|
||||
{t("labs.loadError")}
|
||||
</p>
|
||||
<QueryErrorCard
|
||||
title={t("labs.loadError")}
|
||||
onRetry={() => void refetch()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -172,7 +173,7 @@ export function IntakeHistoryListV2({
|
||||
|
||||
const offset = page * pageSize;
|
||||
|
||||
const { data, isLoading, isError } = useQuery<IntakeListResponse>({
|
||||
const { data, isLoading, isError, refetch } = useQuery<IntakeListResponse>({
|
||||
queryKey: queryKeys.medicationIntakeList(medicationId, {
|
||||
sortBy,
|
||||
sortDir,
|
||||
@@ -243,9 +244,10 @@ export function IntakeHistoryListV2({
|
||||
<Loader2 className="text-muted-foreground h-5 w-5 animate-spin motion-reduce:animate-none" />
|
||||
</div>
|
||||
) : isError ? (
|
||||
<p className="text-destructive text-sm">
|
||||
{t("medications.loadFailed")}
|
||||
</p>
|
||||
<QueryErrorCard
|
||||
title={t("medications.loadFailed")}
|
||||
onRetry={() => void refetch()}
|
||||
/>
|
||||
) : events.length === 0 ? (
|
||||
<EmptyState
|
||||
variant="plain"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DeleteButton } from "@/components/data-list";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { ResponsiveSheet } from "@/components/ui/responsive-sheet";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { apiDelete, apiGet } from "@/lib/api/api-fetch";
|
||||
@@ -30,7 +31,7 @@ export function AllergyManager() {
|
||||
const [editing, setEditing] = useState<AllergyDTO | null>(null);
|
||||
const [formFooterEl, setFormFooterEl] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
const { data, isLoading, isError, refetch } = useQuery({
|
||||
queryKey: queryKeys.allergyList(true),
|
||||
queryFn: () => apiGet<AllergyDTO[]>("/api/allergies"),
|
||||
});
|
||||
@@ -135,9 +136,10 @@ export function AllergyManager() {
|
||||
))}
|
||||
</div>
|
||||
) : isError ? (
|
||||
<p className="text-destructive py-6 text-center text-sm">
|
||||
{t("records.allergies.loadError")}
|
||||
</p>
|
||||
<QueryErrorCard
|
||||
title={t("records.allergies.loadError")}
|
||||
onRetry={() => void refetch()}
|
||||
/>
|
||||
) : rows.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={<ShieldAlert className="size-6" />}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -126,9 +127,10 @@ export function ConditionsManager() {
|
||||
return (
|
||||
<div className="space-y-4" data-slot="conditions-manager">
|
||||
{query.isError ? (
|
||||
<p role="alert" className="text-destructive text-sm">
|
||||
{t("records.conditions.loadError")}
|
||||
</p>
|
||||
<QueryErrorCard
|
||||
title={t("records.conditions.loadError")}
|
||||
onRetry={() => void query.refetch()}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{field(
|
||||
|
||||
@@ -8,6 +8,7 @@ import { toast } from "sonner";
|
||||
import { DeleteButton } from "@/components/data-list";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EmptyState } from "@/components/ui/empty-state";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { ResponsiveSheet } from "@/components/ui/responsive-sheet";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { apiDelete, apiGet } from "@/lib/api/api-fetch";
|
||||
@@ -30,7 +31,7 @@ export function FamilyHistoryManager() {
|
||||
const [editing, setEditing] = useState<FamilyHistoryEntryDTO | null>(null);
|
||||
const [formFooterEl, setFormFooterEl] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
const { data, isLoading, isError, refetch } = useQuery({
|
||||
queryKey: queryKeys.familyHistoryList(),
|
||||
queryFn: () => apiGet<FamilyHistoryEntryDTO[]>("/api/family-history"),
|
||||
});
|
||||
@@ -120,9 +121,10 @@ export function FamilyHistoryManager() {
|
||||
))}
|
||||
</div>
|
||||
) : isError ? (
|
||||
<p className="text-destructive py-6 text-center text-sm">
|
||||
{t("records.family.loadError")}
|
||||
</p>
|
||||
<QueryErrorCard
|
||||
title={t("records.family.loadError")}
|
||||
onRetry={() => void refetch()}
|
||||
/>
|
||||
) : rows.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={<Users className="size-6" />}
|
||||
|
||||
Reference in new issue
Block a user