diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7e32d28b..cf2e9ef1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -14,6 +14,7 @@ import { TodoDetailView } from './pages/TodoDetailView'; import { TaskBoard } from './pages/TaskBoard'; import { ErrorBoundary } from './components/ErrorBoundary'; import { MobileShell } from './components/MobileShell'; +import { DesktopShell } from './components/DesktopShell'; import { useIsDesktop } from './hooks/useMediaQuery'; function ProtectedRoute({ children }: { children: React.ReactNode }) { @@ -41,6 +42,12 @@ function ChatRoute() { return isDesktop ? : ; } +function PageRoute({ children }: { children: React.ReactNode }) { + const isDesktop = useIsDesktop(); + if (!isDesktop) return <>{children}; + return ; +} + function dismissKeyboard(e: React.MouseEvent | React.TouchEvent) { const target = e.target as HTMLElement; const tag = target.tagName; @@ -96,7 +103,9 @@ export function App() { path="/inbox" element={ - + + + } /> @@ -104,7 +113,9 @@ export function App() { path="/calendar" element={ - + + + } /> @@ -112,7 +123,9 @@ export function App() { path="/todos" element={ - + + + } /> @@ -120,7 +133,9 @@ export function App() { path="/todos/:id" element={ - + + + } /> @@ -128,7 +143,9 @@ export function App() { path="/tasks" element={ - + + + } /> @@ -137,7 +154,9 @@ export function App() { element={ - + + + } diff --git a/frontend/src/components/DesktopNav.tsx b/frontend/src/components/DesktopNav.tsx new file mode 100644 index 00000000..1eae98f2 --- /dev/null +++ b/frontend/src/components/DesktopNav.tsx @@ -0,0 +1,48 @@ +import { useLocation, useNavigate } from 'react-router-dom'; +import { useTabBadges } from '../hooks/useTabBadges'; + +interface NavItem { + label: string; + path: string; + match: (pathname: string) => boolean; + badge?: number; +} + +export function DesktopNav() { + const location = useLocation(); + const navigate = useNavigate(); + const { inboxCount, todoCount } = useTabBadges(); + + const items: NavItem[] = [ + { + label: 'Chat', + path: '/', + match: (p) => p === '/' || p === '/chat' || p.startsWith('/chat/'), + }, + { label: 'Calendar', path: '/calendar', match: (p) => p.startsWith('/calendar') }, + { label: 'Inbox', path: '/inbox', match: (p) => p === '/inbox', badge: inboxCount }, + { + label: 'Telos', + path: '/todos', + match: (p) => p === '/todos' || p.startsWith('/todos/'), + badge: todoCount, + }, + { label: 'Tasks', path: '/tasks', match: (p) => p.startsWith('/tasks') }, + { label: 'Files', path: '/files', match: (p) => p.startsWith('/files') }, + ]; + + return ( + + ); +} diff --git a/frontend/src/components/DesktopShell.tsx b/frontend/src/components/DesktopShell.tsx index 229bdd08..7ac0fcd5 100644 --- a/frontend/src/components/DesktopShell.tsx +++ b/frontend/src/components/DesktopShell.tsx @@ -1,9 +1,10 @@ import { useState, useCallback, type ReactNode } from 'react'; +import { DesktopNav } from './DesktopNav'; export interface DesktopShellProps { - left: ReactNode; + left?: ReactNode; center: ReactNode; - right: ReactNode; + right?: ReactNode; statusBar?: ReactNode; } @@ -55,25 +56,32 @@ export function DesktopShell({ left, center, right, statusBar }: DesktopShellPro - {!leftCollapsed && left} + {!leftCollapsed && ( + <> + + {left} + + )}
{center}
-
- - {!rightCollapsed && right} -
+ + {!rightCollapsed && right} + + )} {statusBar &&
{statusBar}
} diff --git a/frontend/src/components/__tests__/DesktopShell.test.tsx b/frontend/src/components/__tests__/DesktopShell.test.tsx index 42625613..3670b255 100644 --- a/frontend/src/components/__tests__/DesktopShell.test.tsx +++ b/frontend/src/components/__tests__/DesktopShell.test.tsx @@ -1,6 +1,11 @@ // @vitest-environment jsdom -import { describe, it, expect, afterEach, beforeEach } from 'vitest'; +import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest'; import { render, screen, cleanup, fireEvent } from '@testing-library/react'; + +vi.mock('../DesktopNav', () => ({ + DesktopNav: () => , +})); + import { DesktopShell } from '../DesktopShell'; beforeEach(() => { @@ -31,7 +36,7 @@ describe('DesktopShell', () => { right={
Right
} />, ); - const toggleBtn = screen.getByTitle('Hide sessions'); + const toggleBtn = screen.getByTitle('Hide sidebar'); fireEvent.click(toggleBtn); expect(screen.queryByText('Left Panel')).toBeNull(); expect(localStorage.getItem('mitzo-sidebar-left-collapsed')).toBe('1'); @@ -61,7 +66,7 @@ describe('DesktopShell', () => { />, ); expect(screen.queryByText('Left Panel')).toBeNull(); - expect(screen.getByTitle('Show sessions')).toBeTruthy(); + expect(screen.getByTitle('Show sidebar')).toBeTruthy(); }); it('expands collapsed sidebar on toggle', () => { @@ -73,7 +78,7 @@ describe('DesktopShell', () => { right={
Right
} />, ); - fireEvent.click(screen.getByTitle('Show sessions')); + fireEvent.click(screen.getByTitle('Show sidebar')); expect(screen.getByText('Left Panel')).toBeTruthy(); expect(localStorage.getItem('mitzo-sidebar-left-collapsed')).toBe('0'); }); diff --git a/frontend/src/pages/DesktopChatView.tsx b/frontend/src/pages/DesktopChatView.tsx index 11f23b5e..b520448a 100644 --- a/frontend/src/pages/DesktopChatView.tsx +++ b/frontend/src/pages/DesktopChatView.tsx @@ -202,7 +202,10 @@ export function DesktopChatView() { }, []); const handleSelectSession = useCallback((id: string) => navigate(`/chat/${id}`), [navigate]); - const handleNewChat = useCallback(() => navigate('/chat'), [navigate]); + const handleNewChat = useCallback(() => { + storeNewSession(); + navigate('/chat'); + }, [navigate, storeNewSession]); return (