UI: Fehler-States statt weißer Leerseiten bei Query-Fehlern
Dashboard/Projekt/Lead/Team/Historie rendern bei fehlgeschlagener Query nicht mehr ein stilles `return null` (leere weisse Seite), sondern einen Hinweis mit "Neu laden"-Button. - Neue Komponente ErrorState (Meldung + optionaler Retry) - 5 Routen: if (q.isError || !q.data) -> ErrorState mit q.refetch() Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
90caa282ee
commit
8b0df9cc4e
6 files changed
+64
-5
No files matched your search
@@ -0,0 +1,24 @@
|
||||
import { Button } from './Button';
|
||||
|
||||
/**
|
||||
* Einheitlicher Fehlerzustand für fehlgeschlagene Queries — ersetzt stilles
|
||||
* `return null` (weiße Leerseite) durch einen Hinweis + optionalen Retry.
|
||||
*/
|
||||
export function ErrorState({
|
||||
message = 'Konnte nicht geladen werden.',
|
||||
onRetry,
|
||||
}: {
|
||||
message?: string;
|
||||
onRetry?: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="mx-auto max-w-md rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-bg)] p-6 text-center">
|
||||
<p className="text-sm text-[var(--color-ink-muted)]">{message}</p>
|
||||
{onRetry && (
|
||||
<Button variant="secondary" size="sm" className="mt-3" onClick={onRetry}>
|
||||
Neu laden
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { voice } from '../lib/voice';
|
||||
import { Card } from '../components/ui/Card';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { SkeletonCard } from '../components/ui/SkeletonCard';
|
||||
import { ErrorState } from '../components/ui/ErrorState';
|
||||
import { TeamTimeSection } from '../components/time/TeamTimeSection';
|
||||
import { MODUS_META, type ProjectModus } from '../lib/project-meta';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
@@ -23,7 +24,13 @@ export function DashboardRoute() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!q.data) return null;
|
||||
if (q.isError || !q.data)
|
||||
return (
|
||||
<ErrorState
|
||||
message="Dashboard konnte nicht geladen werden."
|
||||
onRetry={() => void q.refetch()}
|
||||
/>
|
||||
);
|
||||
|
||||
const isCheckedIn =
|
||||
today.data?.entry?.status !== undefined && today.data.entry.status !== 'absent';
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { AppRouter } from '@cockpit/api/router';
|
||||
import { trpc } from '../lib/trpc';
|
||||
import { Card } from '../components/ui/Card';
|
||||
import { SkeletonCard } from '../components/ui/SkeletonCard';
|
||||
import { ErrorState } from '../components/ui/ErrorState';
|
||||
|
||||
type HistoryPerson = inferRouterOutputs<AppRouter>['dayEntry']['historyForDate']['entries'][number];
|
||||
|
||||
@@ -37,7 +38,13 @@ export function HistorieRoute() {
|
||||
const q = trpc.dayEntry.historyForDate.useQuery({ date });
|
||||
|
||||
if (q.isPending) return <SkeletonCard lines={6} />;
|
||||
if (!q.data) return null;
|
||||
if (q.isError || !q.data)
|
||||
return (
|
||||
<ErrorState
|
||||
message="Historie konnte nicht geladen werden."
|
||||
onRetry={() => void q.refetch()}
|
||||
/>
|
||||
);
|
||||
const { date: current, prevDate, nextDate, entries, ohneEintrag } = q.data;
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,6 +10,7 @@ import { voice } from '../lib/voice';
|
||||
import { Card } from '../components/ui/Card';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { SkeletonCard } from '../components/ui/SkeletonCard';
|
||||
import { ErrorState } from '../components/ui/ErrorState';
|
||||
import { useConfirm } from '../components/ui/ConfirmProvider';
|
||||
import {
|
||||
LEAD_STATUS_META,
|
||||
@@ -72,7 +73,13 @@ export function LeadRoute() {
|
||||
const q = trpc.lead.byId.useQuery({ id });
|
||||
|
||||
if (q.isPending) return <SkeletonCard lines={8} />;
|
||||
if (!q.data) return null;
|
||||
if (q.isError || !q.data)
|
||||
return (
|
||||
<ErrorState
|
||||
message="Lead konnte nicht geladen werden."
|
||||
onRetry={() => void q.refetch()}
|
||||
/>
|
||||
);
|
||||
const { lead, contacts, touchpoints } = q.data;
|
||||
|
||||
function goBack() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { trpc } from '../lib/trpc';
|
||||
import { Card } from '../components/ui/Card';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { SkeletonCard } from '../components/ui/SkeletonCard';
|
||||
import { ErrorState } from '../components/ui/ErrorState';
|
||||
import { useConfirm } from '../components/ui/ConfirmProvider';
|
||||
import { useCurrentUser } from '../hooks/useCurrentUser';
|
||||
import { voice } from '../lib/voice';
|
||||
@@ -71,7 +72,13 @@ export function ProjektRoute() {
|
||||
const q = trpc.project.byId.useQuery({ id });
|
||||
const [abschlussOpen, setAbschlussOpen] = useState(false);
|
||||
if (q.isPending) return <SkeletonCard lines={6} />;
|
||||
if (!q.data) return null;
|
||||
if (q.isError || !q.data)
|
||||
return (
|
||||
<ErrorState
|
||||
message="Projekt konnte nicht geladen werden."
|
||||
onRetry={() => void q.refetch()}
|
||||
/>
|
||||
);
|
||||
const { project: p, area: a, milestones } = q.data;
|
||||
const isP1 = me?.id === p.verantwortlicheUserId;
|
||||
const pflichtKeys: Array<{ key: string; label: string }> = [
|
||||
|
||||
@@ -5,6 +5,7 @@ import { trpc } from '../lib/trpc';
|
||||
import { Card } from '../components/ui/Card';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { SkeletonCard } from '../components/ui/SkeletonCard';
|
||||
import { ErrorState } from '../components/ui/ErrorState';
|
||||
import { formatDurationCompact } from '../lib/format-duration';
|
||||
import { TIME_CATEGORIES } from '../lib/time-categories';
|
||||
|
||||
@@ -124,7 +125,13 @@ export function TeamViewRoute() {
|
||||
}, [isToday]);
|
||||
|
||||
if (q.isPending) return <SkeletonCard lines={6} />;
|
||||
if (!q.data) return null;
|
||||
if (q.isError || !q.data)
|
||||
return (
|
||||
<ErrorState
|
||||
message="Team-Ansicht konnte nicht geladen werden."
|
||||
onRetry={() => void q.refetch()}
|
||||
/>
|
||||
);
|
||||
const lanes = q.data.lanes;
|
||||
const nowMs = Date.now();
|
||||
|
||||
|
||||
Reference in new issue
Block a user