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:
@@ -12,17 +12,20 @@ export function TenantSwitcher() {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
api.get<TenantWithRole[]>("/tenants").then((data) => {
|
||||
setTenants(data);
|
||||
const savedId = localStorage.getItem("kanzlai_tenant_id");
|
||||
const match = data.find((t) => t.id === savedId) || data[0];
|
||||
if (match) {
|
||||
setCurrent(match);
|
||||
localStorage.setItem("kanzlai_tenant_id", match.id);
|
||||
}
|
||||
}).catch(() => {
|
||||
// Not authenticated or no tenants
|
||||
});
|
||||
api
|
||||
.get<TenantWithRole[]>("/tenants")
|
||||
.then((data) => {
|
||||
setTenants(data);
|
||||
const savedId = localStorage.getItem("kanzlai_tenant_id");
|
||||
const match = data.find((t) => t.id === savedId) || data[0];
|
||||
if (match) {
|
||||
setCurrent(match);
|
||||
localStorage.setItem("kanzlai_tenant_id", match.id);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Not authenticated or no tenants
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -48,14 +51,16 @@ export function TenantSwitcher() {
|
||||
<div ref={ref} className="relative">
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex items-center gap-1.5 rounded-md border border-neutral-200 bg-white px-2.5 py-1.5 text-sm text-neutral-700 hover:bg-neutral-50"
|
||||
className="flex items-center gap-1.5 rounded-md border border-neutral-200 bg-white px-2.5 py-1.5 text-sm text-neutral-700 transition-colors hover:bg-neutral-50"
|
||||
>
|
||||
<span className="max-w-[160px] truncate">{current.name}</span>
|
||||
<span className="max-w-[120px] truncate sm:max-w-[160px]">
|
||||
{current.name}
|
||||
</span>
|
||||
<ChevronsUpDown className="h-3.5 w-3.5 text-neutral-400" />
|
||||
</button>
|
||||
|
||||
{open && tenants.length > 1 && (
|
||||
<div className="absolute right-0 top-full z-50 mt-1 w-56 rounded-md border border-neutral-200 bg-white py-1 shadow-lg">
|
||||
<div className="animate-fade-in absolute right-0 top-full z-50 mt-1 w-56 rounded-md border border-neutral-200 bg-white py-1 shadow-lg">
|
||||
{tenants.map((tenant) => (
|
||||
<button
|
||||
key={tenant.id}
|
||||
|
||||
Reference in New Issue
Block a user