diff --git a/apps/web/src/components/ui/NavDrawer.tsx b/apps/web/src/components/ui/NavDrawer.tsx
new file mode 100644
index 0000000..890ff2b
--- /dev/null
+++ b/apps/web/src/components/ui/NavDrawer.tsx
@@ -0,0 +1,45 @@
+import { useEffect, type ReactNode } from 'react';
+import { cn } from '@/lib/cn';
+
+interface Props {
+ open: boolean;
+ onClose: () => void;
+ children: ReactNode;
+ panelClassName?: string;
+}
+
+export function NavDrawer({ open, onClose, children, panelClassName }: Props) {
+ useEffect(() => {
+ if (!open) return;
+ const onKey = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') onClose();
+ };
+ window.addEventListener('keydown', onKey);
+ document.body.style.overflow = 'hidden';
+ return () => {
+ window.removeEventListener('keydown', onKey);
+ document.body.style.overflow = '';
+ };
+ }, [open, onClose]);
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/pages/app/DashboardPage.tsx b/apps/web/src/pages/app/DashboardPage.tsx
index 6158e9f..c94bf39 100644
--- a/apps/web/src/pages/app/DashboardPage.tsx
+++ b/apps/web/src/pages/app/DashboardPage.tsx
@@ -1,5 +1,5 @@
import { Link } from 'react-router-dom';
-import { Briefcase, Clock, Receipt, Users, ArrowUpRight } from 'lucide-react';
+import { AlertTriangle, Briefcase, Clock, Receipt, Users, ArrowUpRight } from 'lucide-react';
import { PageHeader } from '@/components/app/AppLayout';
import { Card, CardBody, CardHeader, EmptyState } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
@@ -19,6 +19,8 @@ export default function DashboardPage() {
const openCases = cases.data?.items.filter((c) => c.status === 'open') ?? [];
const totalMinutes = cases.data?.items.reduce((acc, c) => acc + (c.billedMinutes ?? 0), 0) ?? 0;
+ const failed = [cases, clients, summary].filter((q) => q.isError);
+
return (
+ {failed.length > 0 && (
+
+
+
+ Some dashboard data couldn't be loaded.
+
+
+
+ )}
+
}
label="Active cases"
- value={String(openCases.length)}
+ value={cases.isError ? '—' : String(openCases.length)}
to="/app/cases"
/>
}
label="Clients"
- value={String(clients.data?.total ?? 0)}
+ value={clients.isError ? '—' : String(clients.data?.total ?? 0)}
to="/app/clients"
/>
}
label="Tracked hours"
- value={formatHours(totalMinutes)}
+ value={cases.isError ? '—' : formatHours(totalMinutes)}
to="/app/time"
/>
}
label="Outstanding"
- value={formatMoney(summary.data?.outstanding ?? 0)}
+ value={summary.isError ? '—' : formatMoney(summary.data?.outstanding ?? 0)}
to="/app/invoices"
/>
@@ -69,7 +89,18 @@ export default function DashboardPage() {
}
/>
- {cases.data?.items.length ? (
+ {cases.isError ? (
+
}
+ title="Couldn't load cases"
+ description="Check your connection and try again."
+ action={
+
+ }
+ />
+ ) : cases.data?.items.length ? (
{cases.data.items.slice(0, 6).map((c) => (