feat: UI polish — responsive, loading/empty/error states, German fixes (Phase 3Q)
- Responsive sidebar: collapses on mobile with hamburger menu, slide-in animation - Skeleton loaders: dashboard cards, case table, case detail page - Empty states: friendly messages with icons for cases, deadlines, parties, documents - Error states: retry button on dashboard, proper error message on case not found - Form validation: inline error messages on case creation form - German language: fix all missing umlauts (Zurück, wählen, Anhängig, Verfügung, etc.) - Status labels: display German translations instead of raw status values - Transitions: fade-in animations on page load, hover/transition-colors on all interactive elements - Focus states: focus-visible ring for keyboard accessibility - Mobile layout: stacking for filters, forms, tabs; horizontal scroll for tables - Extraction results: card layout on mobile, table on desktop - Missing types: add DashboardData, DeadlineSummary, CaseSummary, ExtractedDeadline etc. - Fix QuickActions links to use correct routes (/cases/new, /ai/extract) - Consistent input focus styles across all forms
This commit is contained in:
@@ -8,35 +8,71 @@ import { CaseOverviewGrid } from "@/components/dashboard/CaseOverviewGrid";
|
||||
import { UpcomingTimeline } from "@/components/dashboard/UpcomingTimeline";
|
||||
import { AISummaryCard } from "@/components/dashboard/AISummaryCard";
|
||||
import { QuickActions } from "@/components/dashboard/QuickActions";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { Skeleton, SkeletonCard } from "@/components/ui/Skeleton";
|
||||
import { AlertTriangle, RefreshCw } from "lucide-react";
|
||||
|
||||
function DashboardSkeleton() {
|
||||
return (
|
||||
<div className="mx-auto max-w-6xl space-y-6">
|
||||
<div>
|
||||
<Skeleton className="h-5 w-28" />
|
||||
<Skeleton className="mt-2 h-3.5 w-52" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Skeleton key={i} className="h-28 rounded-xl" />
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<div className="lg:col-span-2">
|
||||
<SkeletonCard className="min-h-[200px]" />
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<SkeletonCard />
|
||||
<SkeletonCard />
|
||||
<SkeletonCard />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
const { data, isLoading, error, refetch } = useQuery({
|
||||
queryKey: ["dashboard"],
|
||||
queryFn: () => api.get<DashboardData>("/dashboard"),
|
||||
refetchInterval: 60_000,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-neutral-400" />
|
||||
</div>
|
||||
);
|
||||
return <DashboardSkeleton />;
|
||||
}
|
||||
|
||||
if (error || !data) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<p className="text-sm text-neutral-500">
|
||||
Dashboard konnte nicht geladen werden.
|
||||
<div className="mx-auto max-w-md py-16 text-center">
|
||||
<div className="mx-auto mb-3 rounded-xl bg-red-50 p-3 w-fit">
|
||||
<AlertTriangle className="h-6 w-6 text-red-500" />
|
||||
</div>
|
||||
<h2 className="text-sm font-medium text-neutral-900">
|
||||
Dashboard konnte nicht geladen werden
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-neutral-500">
|
||||
Bitte versuchen Sie es erneut oder prüfen Sie Ihre Verbindung.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => refetch()}
|
||||
className="mt-4 inline-flex items-center gap-1.5 rounded-md bg-neutral-900 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-neutral-800"
|
||||
>
|
||||
<RefreshCw className="h-3.5 w-3.5" />
|
||||
Erneut laden
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-6xl space-y-6">
|
||||
<div className="animate-fade-in mx-auto max-w-6xl space-y-6">
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-neutral-900">Dashboard</h1>
|
||||
<p className="mt-0.5 text-sm text-neutral-500">
|
||||
@@ -44,20 +80,15 @@ export default function DashboardPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Traffic Lights — the hero section */}
|
||||
<DeadlineTrafficLights data={data.deadline_summary} />
|
||||
|
||||
{/* Main content grid */}
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
{/* Left column: Timeline (takes 2 cols) */}
|
||||
<div className="lg:col-span-2">
|
||||
<UpcomingTimeline
|
||||
deadlines={data.upcoming_deadlines}
|
||||
appointments={data.upcoming_appointments}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Right column: Case overview, AI summary, Quick actions */}
|
||||
<div className="space-y-6">
|
||||
<CaseOverviewGrid data={data.case_summary} />
|
||||
<AISummaryCard data={data} />
|
||||
|
||||
Reference in New Issue
Block a user