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:
28
frontend/src/components/ui/EmptyState.tsx
Normal file
28
frontend/src/components/ui/EmptyState.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
interface EmptyStateProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
description?: string;
|
||||
action?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function EmptyState({
|
||||
icon: Icon,
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
}: EmptyStateProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-neutral-300 bg-white px-6 py-12 text-center">
|
||||
<div className="rounded-xl bg-neutral-100 p-3">
|
||||
<Icon className="h-6 w-6 text-neutral-400" />
|
||||
</div>
|
||||
<h3 className="mt-3 text-sm font-medium text-neutral-900">{title}</h3>
|
||||
{description && (
|
||||
<p className="mt-1 max-w-sm text-sm text-neutral-500">{description}</p>
|
||||
)}
|
||||
{action && <div className="mt-4">{action}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
43
frontend/src/components/ui/Skeleton.tsx
Normal file
43
frontend/src/components/ui/Skeleton.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
export function Skeleton({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`animate-pulse rounded-md bg-neutral-200/60 ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SkeletonCard({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`rounded-xl border border-neutral-200 bg-white p-5 ${className}`}
|
||||
>
|
||||
<Skeleton className="h-4 w-24" />
|
||||
<div className="mt-4 space-y-3">
|
||||
<Skeleton className="h-3 w-full" />
|
||||
<Skeleton className="h-3 w-3/4" />
|
||||
<Skeleton className="h-3 w-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SkeletonTable({ rows = 5 }: { rows?: number }) {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-md border border-neutral-200 bg-white">
|
||||
<div className="border-b border-neutral-100 px-4 py-3">
|
||||
<Skeleton className="h-3 w-full" />
|
||||
</div>
|
||||
{Array.from({ length: rows }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-4 border-b border-neutral-100 px-4 py-3 last:border-b-0"
|
||||
>
|
||||
<Skeleton className="h-3 w-20" />
|
||||
<Skeleton className="h-3 flex-1" />
|
||||
<Skeleton className="h-3 w-16" />
|
||||
<Skeleton className="h-3 w-12" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user