Files
AOS/src/components/aos/mobile-bottom-nav.tsx
T
Z User fa8dd46571 refactor: professional code quality audit
- Fix all TypeScript compilation errors (0 errors in src/)
- Add try/catch error handling to ALL API routes (38 handlers)
- Fix empty catch blocks (api-auth.ts now logs warnings)
- Add aria-labels to all icon-only buttons (WCAG 2.1 AA)
- Fix Prisma type mismatches (OR filter, Person.companyId, Estimate.projectId, Ticket.createdAt)
- Fix webhook PATCH route (moved to [id]/route.ts)
- Fix mobile-bottom-nav type safety
- Fix KB route where-clause type
- Fix test-mail banner null vs undefined
- Fix task comment fetch: add cancellation + error toast
- ESLint: 0 errors, 0 warnings
- 175 files, 24.741 lines of code
2026-10-27 12:23:32 +00:00

64 lines
2.8 KiB
TypeScript
Executable File

'use client'
import { useAos, type ViewId } from '@/store/aos'
import { LayoutDashboard, Radar, FolderKanban, Bell, Menu } from 'lucide-react'
import { cn } from '@/lib/utils'
import { useFetch } from '@/hooks/use-fetch'
// Mobile bottom navigation bar (PRD §9: "Mobile: usable PWA for time tracking,
// approvals and quick CRM lookups"). Shows on mobile only (md:hidden).
// Provides 5 quick-access destinations optimized for touch.
export function MobileBottomNav() {
const { view, setView, setSidebarOpen, setCommandOpen } = useAos()
const { data } = useFetch<{ stats: { total: number; urgent: number } }>('/api/aos/notifications')
const urgent = data?.stats.urgent ?? 0
const total = data?.stats.total ?? 0
const items: { id: string; label: string; icon: typeof LayoutDashboard; badge?: number }[] = [
{ id: 'dashboard', label: 'Home', icon: LayoutDashboard },
{ id: 'leads', label: 'Leads', icon: Radar },
{ id: 'search', label: 'Suche', icon: Menu },
{ id: 'projects', label: 'Zeit', icon: FolderKanban },
{ id: 'notifications', label: 'Alerts', icon: Bell, badge: urgent > 0 ? urgent : total > 0 ? total : undefined },
]
const handleClick = (id: string) => {
if (id === 'search') setCommandOpen(true)
else if (id === 'notifications') setView('calendar')
else if (id === 'menu') setSidebarOpen(true)
else setView(id as ViewId)
}
return (
<nav className="fixed bottom-0 left-0 right-0 z-40 flex h-16 items-stretch border-t border-border bg-background/95 backdrop-blur-md md:hidden">
{items.map((item) => {
const Icon = item.icon
const active = view === (item.id === 'notifications' ? 'calendar' : (item.id === 'search' || item.id === 'menu') ? ('' as ViewId) : (item.id as ViewId))
return (
<button
key={item.id}
onClick={() => handleClick(item.id)}
className={cn(
'relative flex flex-1 flex-col items-center justify-center gap-0.5 text-[10px] font-medium transition',
active ? 'text-foreground' : 'text-muted-foreground'
)}
aria-label={item.label}
>
<div className="relative">
<Icon className={cn('size-5', active && 'text-foreground')} />
{item.badge !== undefined && item.badge > 0 && (
<span className="absolute -right-2 -top-1.5 flex size-4 items-center justify-center rounded-full bg-foreground text-[8px] font-bold text-background">
{item.badge > 9 ? '9+' : item.badge}
</span>
)}
</div>
<span>{item.label}</span>
{active && <span className="absolute bottom-0 left-1/2 h-0.5 w-8 -translate-x-1/2 rounded-full bg-foreground" />}
</button>
)
})}
</nav>
)
}