Paramjit Singh commited on
Commit
758f79c
Β·
unverified Β·
2 Parent(s): d2b3700bdff89f

Merge pull request #203 from vaishnavijha12/fix/mobile-responsiveness-130

Browse files
frontend/src/app/dashboard/page.tsx CHANGED
@@ -4,17 +4,41 @@ import { useEffect, useState, useCallback } from "react";
4
  import dynamic from "next/dynamic";
5
  import { useRouter } from "next/navigation";
6
  import { useAuth } from "@/lib/auth";
7
- import {
8
- api,
9
- CONNECTION_ERROR_BANNER_MESSAGE,
10
- CONNECTION_ERROR_MESSAGE,
11
- } from "@/lib/api";
12
-
13
  import Header from "@/components/layout/Header";
14
  import DocumentSidebar from "@/components/document/DocumentSidebar";
15
  import ChatPanel from "@/components/chat/ChatPanel";
16
- import PDFViewer from "@/components/document/PDFViewer";
17
- import { Skeleton } from "@/components/ui/skeleton";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  export interface DocInfo {
20
  summary: string;
@@ -28,23 +52,6 @@ export interface DocInfo {
28
  uploaded_at: string;
29
  }
30
 
31
- function DocumentSkeleton() {
32
- return (
33
- <div className="w-72 flex-shrink-0 border-r border-border/50 p-4 space-y-4">
34
- {[1, 2, 3, 4].map((item) => (
35
- <div
36
- key={item}
37
- className="rounded-lg border border-border/50 p-4 space-y-3"
38
- >
39
- <Skeleton className="h-4 w-[180px]" />
40
- <Skeleton className="h-3 w-[120px]" />
41
- <Skeleton className="h-3 w-[90px]" />
42
- </div>
43
- ))}
44
- </div>
45
- );
46
- }
47
-
48
  export default function DashboardPage() {
49
  const { user, loading } = useAuth();
50
  const router = useRouter();
@@ -55,7 +62,6 @@ export default function DashboardPage() {
55
  const [sidebarOpen, setSidebarOpen] = useState(true);
56
  const [viewerOpen, setViewerOpen] = useState(true);
57
  const [connectionError, setConnectionError] = useState("");
58
- const [documentsLoading, setDocumentsLoading] = useState(true);
59
 
60
  // Auth guard
61
  useEffect(() => {
@@ -77,31 +83,23 @@ export default function DashboardPage() {
77
  // Load documents
78
  const loadDocuments = useCallback(async () => {
79
  try {
80
- setDocumentsLoading(true);
81
-
82
  const data = await api.get<{ documents?: DocInfo[]; items?: DocInfo[] }>(
83
  "/api/v1/documents/"
84
  );
85
-
86
  setDocuments(data?.documents ?? data?.items ?? []);
87
  setConnectionError("");
88
  } catch (err) {
89
- const message =
90
- err instanceof Error ? err.message : CONNECTION_ERROR_MESSAGE;
91
-
92
  setConnectionError(
93
  message === CONNECTION_ERROR_MESSAGE
94
  ? CONNECTION_ERROR_BANNER_MESSAGE
95
  : `⚠️ ${message}`
96
  );
97
- } finally {
98
- setDocumentsLoading(false);
99
  }
100
  }, []);
101
 
102
  useEffect(() => {
103
  if (!user) return;
104
-
105
  void (async () => {
106
  await loadDocuments();
107
  })();
@@ -112,11 +110,9 @@ export default function DashboardPage() {
112
  const hasPending = (documents || []).some(
113
  (d) => d.status === "pending" || d.status === "processing"
114
  );
115
-
116
  if (!hasPending) return;
117
 
118
  const interval = setInterval(loadDocuments, 3000);
119
-
120
  return () => clearInterval(interval);
121
  }, [documents, loadDocuments]);
122
 
@@ -128,6 +124,19 @@ export default function DashboardPage() {
128
  );
129
  }
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  return (
132
  <div className="h-screen flex flex-col overflow-hidden">
133
  <Header
@@ -135,6 +144,7 @@ export default function DashboardPage() {
135
  onToggleSidebar={() => setSidebarOpen(!sidebarOpen)}
136
  viewerOpen={viewerOpen}
137
  onToggleViewer={() => setViewerOpen(!viewerOpen)}
 
138
  />
139
 
140
  {connectionError && (
@@ -147,49 +157,35 @@ export default function DashboardPage() {
147
  )}
148
 
149
  <div className="flex-1 flex overflow-hidden">
150
- {/* ── Left: Document Sidebar / Skeleton ──────────────── */}
151
- {sidebarOpen &&
152
- (documentsLoading ? (
153
- <DocumentSkeleton />
154
- ) : (
155
- <div className="w-72 flex-shrink-0 border-r border-border/50 overflow-hidden animate-fade-in-up">
156
- <DocumentSidebar
157
- documents={documents}
158
- activeDoc={activeDoc}
159
- onSelectDoc={(doc) => {
160
- setActiveDoc(doc);
161
- setPdfPage(1);
162
- }}
163
- onDocumentsChange={loadDocuments}
164
- />
165
- </div>
166
- ))}
167
-
168
- {/* ── Center: Chat Panel ─────────────────── */}
169
  <div className="flex-1 min-w-0 flex flex-col">
170
  <ChatPanel
171
  activeDoc={activeDoc}
172
  onCitationClick={(page) => {
173
  setPdfPage(page);
174
-
175
  if (!viewerOpen) setViewerOpen(true);
176
  }}
177
  />
178
  </div>
179
 
180
- {/* ── Right: PDF Viewer ──────────────────── */}
181
- {viewerOpen &&
182
- activeDoc &&
183
- activeDoc.original_name.endsWith(".pdf") && (
184
- <div className="w-[480px] flex-shrink-0 border-l border-border/50 overflow-hidden animate-fade-in-up">
185
- <PDFViewer
186
- documentId={activeDoc.id}
187
- currentPage={pdfPage}
188
- onPageChange={setPdfPage}
189
- totalPages={activeDoc.page_count}
190
- />
191
- </div>
192
- )}
193
  </div>
194
  </div>
195
  );
 
4
  import dynamic from "next/dynamic";
5
  import { useRouter } from "next/navigation";
6
  import { useAuth } from "@/lib/auth";
7
+ import { api, CONNECTION_ERROR_BANNER_MESSAGE, CONNECTION_ERROR_MESSAGE } from "@/lib/api";
 
 
 
 
 
8
  import Header from "@/components/layout/Header";
9
  import DocumentSidebar from "@/components/document/DocumentSidebar";
10
  import ChatPanel from "@/components/chat/ChatPanel";
11
+
12
+ function PDFViewerSkeleton() {
13
+ return (
14
+ <div
15
+ className="h-full flex flex-col bg-background"
16
+ aria-busy="true"
17
+ aria-label="Loading PDF viewer"
18
+ >
19
+ <div className="flex items-center justify-between px-3 py-2 border-b border-border/50 bg-card/50 shrink-0">
20
+ <div className="flex items-center gap-2">
21
+ <div className="h-7 w-7 rounded-md bg-muted/70 animate-pulse" />
22
+ <div className="h-7 w-20 rounded-md bg-muted/70 animate-pulse" />
23
+ <div className="h-7 w-7 rounded-md bg-muted/70 animate-pulse" />
24
+ </div>
25
+ <div className="flex items-center gap-2">
26
+ <div className="h-7 w-7 rounded-md bg-muted/70 animate-pulse" />
27
+ <div className="h-4 w-10 rounded bg-muted/70 animate-pulse" />
28
+ <div className="h-7 w-7 rounded-md bg-muted/70 animate-pulse" />
29
+ </div>
30
+ </div>
31
+ <div className="flex-1 p-4">
32
+ <div className="h-full rounded-lg border border-border/50 bg-muted/40 animate-pulse" />
33
+ </div>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ const PDFViewer = dynamic(() => import("@/components/document/PDFViewer"), {
39
+ ssr: false,
40
+ loading: () => <PDFViewerSkeleton />,
41
+ });
42
 
43
  export interface DocInfo {
44
  summary: string;
 
52
  uploaded_at: string;
53
  }
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  export default function DashboardPage() {
56
  const { user, loading } = useAuth();
57
  const router = useRouter();
 
62
  const [sidebarOpen, setSidebarOpen] = useState(true);
63
  const [viewerOpen, setViewerOpen] = useState(true);
64
  const [connectionError, setConnectionError] = useState("");
 
65
 
66
  // Auth guard
67
  useEffect(() => {
 
83
  // Load documents
84
  const loadDocuments = useCallback(async () => {
85
  try {
 
 
86
  const data = await api.get<{ documents?: DocInfo[]; items?: DocInfo[] }>(
87
  "/api/v1/documents/"
88
  );
 
89
  setDocuments(data?.documents ?? data?.items ?? []);
90
  setConnectionError("");
91
  } catch (err) {
92
+ const message = err instanceof Error ? err.message : CONNECTION_ERROR_MESSAGE;
 
 
93
  setConnectionError(
94
  message === CONNECTION_ERROR_MESSAGE
95
  ? CONNECTION_ERROR_BANNER_MESSAGE
96
  : `⚠️ ${message}`
97
  );
 
 
98
  }
99
  }, []);
100
 
101
  useEffect(() => {
102
  if (!user) return;
 
103
  void (async () => {
104
  await loadDocuments();
105
  })();
 
110
  const hasPending = (documents || []).some(
111
  (d) => d.status === "pending" || d.status === "processing"
112
  );
 
113
  if (!hasPending) return;
114
 
115
  const interval = setInterval(loadDocuments, 3000);
 
116
  return () => clearInterval(interval);
117
  }, [documents, loadDocuments]);
118
 
 
124
  );
125
  }
126
 
127
+ // Shared sidebar content β€” used by both desktop panel and mobile sheet
128
+ const sidebarContent = (
129
+ <DocumentSidebar
130
+ documents={documents}
131
+ activeDoc={activeDoc}
132
+ onSelectDoc={(doc) => {
133
+ setActiveDoc(doc);
134
+ setPdfPage(1);
135
+ }}
136
+ onDocumentsChange={loadDocuments}
137
+ />
138
+ );
139
+
140
  return (
141
  <div className="h-screen flex flex-col overflow-hidden">
142
  <Header
 
144
  onToggleSidebar={() => setSidebarOpen(!sidebarOpen)}
145
  viewerOpen={viewerOpen}
146
  onToggleViewer={() => setViewerOpen(!viewerOpen)}
147
+ mobileSheetContent={sidebarContent}
148
  />
149
 
150
  {connectionError && (
 
157
  )}
158
 
159
  <div className="flex-1 flex overflow-hidden">
160
+ {/* ── Left: Document Sidebar β€” desktop only (md+) ─────────── */}
161
+ {sidebarOpen && (
162
+ <div className="hidden md:block w-72 flex-shrink-0 border-r border-border/50 overflow-hidden animate-fade-in-up">
163
+ {sidebarContent}
164
+ </div>
165
+ )}
166
+
167
+ {/* ── Center: Chat Panel ──────────────────────────────────── */}
 
 
 
 
 
 
 
 
 
 
 
168
  <div className="flex-1 min-w-0 flex flex-col">
169
  <ChatPanel
170
  activeDoc={activeDoc}
171
  onCitationClick={(page) => {
172
  setPdfPage(page);
 
173
  if (!viewerOpen) setViewerOpen(true);
174
  }}
175
  />
176
  </div>
177
 
178
+ {/* ── Right: PDF Viewer β€” hidden on mobile ────────────────── */}
179
+ {viewerOpen && activeDoc && activeDoc.original_name.endsWith(".pdf") && (
180
+ <div className="hidden md:block w-[480px] flex-shrink-0 border-l border-border/50 overflow-hidden animate-fade-in-up">
181
+ <PDFViewer
182
+ documentId={activeDoc.id}
183
+ currentPage={pdfPage}
184
+ onPageChange={setPdfPage}
185
+ totalPages={activeDoc.page_count}
186
+ />
187
+ </div>
188
+ )}
 
 
189
  </div>
190
  </div>
191
  );
frontend/src/components/layout/Header.tsx CHANGED
@@ -1,5 +1,6 @@
1
  "use client";
2
 
 
3
  import { useAuth } from "@/lib/auth";
4
  import { useTranslation } from "react-i18next";
5
  import { useRouter } from "next/navigation";
@@ -22,29 +23,38 @@ import {
22
  Moon,
23
  Shield,
24
  Sun,
 
 
25
  } from "lucide-react";
26
- import { useSyncExternalStore } from "react";
27
  import { useTheme } from "next-themes";
28
- import ApiKeyManager from "@/components/auth/ApiKeyManager";
29
-
30
 
31
  interface HeaderProps {
32
  sidebarOpen: boolean;
33
  onToggleSidebar: () => void;
34
  viewerOpen: boolean;
35
  onToggleViewer: () => void;
 
 
36
  }
37
 
38
  const subscribe = () => () => {};
39
  const getSnapshot = () => true;
40
  const getServerSnapshot = () => false;
41
 
42
- export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onToggleViewer }: HeaderProps) {
 
 
 
 
 
 
43
  const { user, logout } = useAuth();
44
  const { t, i18n } = useTranslation();
45
  const router = useRouter();
46
  const { theme, setTheme } = useTheme();
47
- const mounted = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); // ← replaces useState + useEffect
 
48
 
49
  const isDark = theme === "dark";
50
  const toggleTheme = () => setTheme(isDark ? "light" : "dark");
@@ -68,79 +78,147 @@ export default function Header({ sidebarOpen, onToggleSidebar, viewerOpen, onTog
68
  };
69
 
70
  return (
71
- <header className="h-14 flex items-center justify-between px-4 border-b border-border/50 bg-card/50 backdrop-blur-md flex-shrink-0 z-50">
72
- {/* Left */}
73
- <div className="flex items-center gap-3">
74
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleSidebar} title={sidebarOpen ? t("header.closeSidebar") : t("header.openSidebar")}>
75
- {sidebarOpen ? <PanelLeftClose className="w-4 h-4" /> : <PanelLeftOpen className="w-4 h-4" />}
76
- </Button>
 
 
 
 
 
 
 
 
77
 
78
- <div className="flex items-center gap-2">
79
- <div className="w-7 h-7 rounded-lg bg-primary/15 flex items-center justify-center">
80
- <Brain className="w-4 h-4 text-primary" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  </div>
82
- <span className="font-semibold text-sm hidden sm:inline">{t("common.appName")}</span>
83
  </div>
84
- </div>
85
-
86
- {/* Right */}
87
- <div className="flex items-center gap-2">
88
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleViewer} title={viewerOpen ? t("header.closeViewer") : t("header.openViewer")}>
89
- {viewerOpen ? <PanelRightClose className="w-4 h-4" /> : <PanelRightOpen className="w-4 h-4" />}
90
- </Button>
91
 
92
- {mounted && (
93
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={toggleTheme} title={isDark ? t("header.lightMode") : t("header.darkMode")}>
94
- {isDark ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
 
 
 
 
 
 
 
 
 
 
 
95
  </Button>
96
- )}
97
-
98
- <select
99
- aria-label={t("common.language")}
100
- value={i18n.resolvedLanguage || "en"}
101
- onChange={(e) => void i18n.changeLanguage(e.target.value)}
102
- className="h-8 rounded-md border border-border bg-background px-2 text-xs text-foreground"
103
- >
104
- <option value="en">{languageLabel("en")}</option>
105
- <option value="hi">{languageLabel("hi")}</option>
106
- <option value="es">{languageLabel("es")}</option>
107
- <option value="fr">{languageLabel("fr")}</option>
108
- </select>
109
-
110
- <DropdownMenu>
111
- <DropdownMenuTrigger
112
- render={
113
- <button className="flex items-center h-8 gap-2 px-2 rounded-md hover:bg-accent transition-colors cursor-pointer">
114
- <Avatar className="w-6 h-6">
115
- <AvatarFallback className="text-[10px] bg-primary/20 text-primary">
116
- {user?.username?.slice(0, 2).toUpperCase() || "U"}
117
- </AvatarFallback>
118
- </Avatar>
119
- <span className="text-sm hidden sm:inline">{user?.username}</span>
120
- </button>
121
- }
122
- />
123
-
124
- <DropdownMenuContent align="end" className="w-56">
125
- <div className="px-3 py-2">
126
- <p className="text-sm font-medium">{user?.username}</p>
127
- <p className="text-xs text-muted-foreground truncate">{user?.email}</p>
128
- </div>
129
- <DropdownMenuSeparator />
130
- {user?.is_admin && (
131
- <DropdownMenuItem className="cursor-pointer" onClick={() => router.push("/admin")}>
132
- <Shield className="w-4 h-4 mr-2" />
133
- Admin metrics
134
  </DropdownMenuItem>
135
- )}
136
- {user?.is_admin && <DropdownMenuSeparator />}
137
- <DropdownMenuItem className="text-destructive cursor-pointer" onClick={handleLogout}>
138
- <LogOut className="w-4 h-4 mr-2" />
139
- {t("header.signOut")}
140
- </DropdownMenuItem>
141
- </DropdownMenuContent>
142
- </DropdownMenu>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  </div>
144
- </header>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  );
146
  }
 
1
  "use client";
2
 
3
+ import { useState } from "react";
4
  import { useAuth } from "@/lib/auth";
5
  import { useTranslation } from "react-i18next";
6
  import { useRouter } from "next/navigation";
 
23
  Moon,
24
  Shield,
25
  Sun,
26
+ Menu,
27
+ X,
28
  } from "lucide-react";
 
29
  import { useTheme } from "next-themes";
30
+ import { useSyncExternalStore } from "react";
 
31
 
32
  interface HeaderProps {
33
  sidebarOpen: boolean;
34
  onToggleSidebar: () => void;
35
  viewerOpen: boolean;
36
  onToggleViewer: () => void;
37
+ /** Pass DocumentSidebar JSX so the mobile sheet can render it */
38
+ mobileSheetContent?: React.ReactNode;
39
  }
40
 
41
  const subscribe = () => () => {};
42
  const getSnapshot = () => true;
43
  const getServerSnapshot = () => false;
44
 
45
+ export default function Header({
46
+ sidebarOpen,
47
+ onToggleSidebar,
48
+ viewerOpen,
49
+ onToggleViewer,
50
+ mobileSheetContent,
51
+ }: HeaderProps) {
52
  const { user, logout } = useAuth();
53
  const { t, i18n } = useTranslation();
54
  const router = useRouter();
55
  const { theme, setTheme } = useTheme();
56
+ const mounted = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
57
+ const [sheetOpen, setSheetOpen] = useState(false);
58
 
59
  const isDark = theme === "dark";
60
  const toggleTheme = () => setTheme(isDark ? "light" : "dark");
 
78
  };
79
 
80
  return (
81
+ <>
82
+ <header className="h-14 flex items-center justify-between px-4 border-b border-border/50 bg-card/50 backdrop-blur-md flex-shrink-0 z-50">
83
+ {/* Left */}
84
+ <div className="flex items-center gap-3">
85
+ {/* Hamburger β€” mobile only */}
86
+ <Button
87
+ variant="ghost"
88
+ size="icon"
89
+ className="h-8 w-8 md:hidden"
90
+ onClick={() => setSheetOpen(true)}
91
+ title="Open sidebar"
92
+ >
93
+ <Menu className="w-4 h-4" />
94
+ </Button>
95
 
96
+ {/* Desktop sidebar toggle β€” hidden on mobile */}
97
+ <Button
98
+ variant="ghost"
99
+ size="icon"
100
+ className="h-8 w-8 hidden md:inline-flex"
101
+ onClick={onToggleSidebar}
102
+ title={sidebarOpen ? "Close sidebar" : "Open sidebar"}
103
+ >
104
+ {sidebarOpen ? (
105
+ <PanelLeftClose className="w-4 h-4" />
106
+ ) : (
107
+ <PanelLeftOpen className="w-4 h-4" />
108
+ )}
109
+ </Button>
110
+
111
+ <div className="flex items-center gap-2">
112
+ <div className="w-7 h-7 rounded-lg bg-primary/15 flex items-center justify-center">
113
+ <Brain className="w-4 h-4 text-primary" />
114
+ </div>
115
+ <span className="font-semibold text-sm hidden sm:inline">
116
+ Document AI Analyst
117
+ </span>
118
  </div>
 
119
  </div>
 
 
 
 
 
 
 
120
 
121
+ {/* Right */}
122
+ <div className="flex items-center gap-2">
123
+ <Button
124
+ variant="ghost"
125
+ size="icon"
126
+ className="h-8 w-8"
127
+ onClick={onToggleViewer}
128
+ title={viewerOpen ? "Close viewer" : "Open viewer"}
129
+ >
130
+ {viewerOpen ? (
131
+ <PanelRightClose className="w-4 h-4" />
132
+ ) : (
133
+ <PanelRightOpen className="w-4 h-4" />
134
+ )}
135
  </Button>
136
+
137
+ {mounted && (
138
+ <Button
139
+ variant="ghost"
140
+ size="icon"
141
+ className="h-8 w-8"
142
+ onClick={toggleTheme}
143
+ title={isDark ? "Light mode" : "Dark mode"}
144
+ >
145
+ {isDark ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
146
+ </Button>
147
+ )}
148
+
149
+ <DropdownMenu>
150
+ <DropdownMenuTrigger className="flex items-center h-8 gap-2 px-2 rounded-md hover:bg-accent transition-colors cursor-pointer">
151
+ <Avatar className="w-6 h-6">
152
+ <AvatarFallback className="text-[10px] bg-primary/20 text-primary">
153
+ {user?.username?.slice(0, 2).toUpperCase() || "U"}
154
+ </AvatarFallback>
155
+ </Avatar>
156
+ <span className="text-sm hidden sm:inline">{user?.username}</span>
157
+ </DropdownMenuTrigger>
158
+ <DropdownMenuContent align="end" className="w-48">
159
+ <div className="px-3 py-2">
160
+ <p className="text-sm font-medium">{user?.username}</p>
161
+ <p className="text-xs text-muted-foreground truncate">{user?.email}</p>
162
+ </div>
163
+ <DropdownMenuSeparator />
164
+ <DropdownMenuItem
165
+ className="text-destructive cursor-pointer"
166
+ onClick={handleLogout}
167
+ >
168
+ <LogOut className="w-4 h-4 mr-2" />
169
+ Sign out
 
 
 
 
170
  </DropdownMenuItem>
171
+ </DropdownMenuContent>
172
+ </DropdownMenu>
173
+ </div>
174
+ </header>
175
+
176
+ {/* ── Mobile Navigation Sheet ──────────────────────────────────── */}
177
+ {/* Backdrop */}
178
+ {sheetOpen && (
179
+ <div
180
+ className="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm md:hidden"
181
+ onClick={() => setSheetOpen(false)}
182
+ aria-hidden="true"
183
+ />
184
+ )}
185
+
186
+ {/* Slide-in panel */}
187
+ <aside
188
+ className={[
189
+ "fixed inset-y-0 left-0 z-50 w-72 flex flex-col",
190
+ "bg-sidebar border-r border-sidebar-border",
191
+ "transform transition-transform duration-300 ease-in-out md:hidden",
192
+ sheetOpen ? "translate-x-0" : "-translate-x-full",
193
+ ].join(" ")}
194
+ aria-label="Mobile navigation"
195
+ aria-hidden={!sheetOpen}
196
+ inert={!sheetOpen ? true : undefined}
197
+ >
198
+ {/* Sheet header */}
199
+ <div className="h-14 flex items-center justify-between px-4 border-b border-sidebar-border flex-shrink-0">
200
+ <div className="flex items-center gap-2">
201
+ <div className="w-7 h-7 rounded-lg bg-primary/15 flex items-center justify-center">
202
+ <Brain className="w-4 h-4 text-primary" />
203
  </div>
204
+ <span className="font-semibold text-sm">Document AI Analyst</span>
205
+ </div>
206
+ <Button
207
+ variant="ghost"
208
+ size="icon"
209
+ className="h-8 w-8"
210
+ onClick={() => setSheetOpen(false)}
211
+ aria-label="Close navigation"
212
+ >
213
+ <X className="w-4 h-4" />
214
+ </Button>
215
+ </div>
216
+
217
+ {/* Sidebar content */}
218
+ <div className="flex-1 overflow-hidden">
219
+ {sheetOpen ? mobileSheetContent : null}
220
+ </div>
221
+ </aside>
222
+ </>
223
  );
224
  }