79ecf134-0188-4613-b691-50e43114247e

This commit is contained in:
Z User committed 2026-10-27 00:54:10 +00:00
1 parent 9b5477020c
commit 8fa25577c0
10 files changed
+117 -1

No files matched your search

+7
View File
@@ -29,8 +29,15 @@ export const metadata: Metadata = {
"GoBD",
],
authors: [{ name: "Nordlicht Digital GmbH" }],
manifest: "/manifest.json",
appleWebApp: {
capable: true,
title: "AOS",
statusBarStyle: "black",
},
icons: {
icon: "https://z-cdn.chatglm.cn/z-ai/static/logo.svg",
apple: "https://z-cdn.chatglm.cn/z-ai/static/logo.svg",
},
openGraph: {
title: "AOS — Agency Operating System",
+3 -1
View File
@@ -28,6 +28,7 @@ import { GlobalSettingsView } from './views/global-settings'
import { SetupWizardView } from './views/setup-wizard'
import { PortalShell } from './portal-shell'
import { OnboardingOverlay } from './onboarding-overlay'
import { MobileBottomNav } from './mobile-bottom-nav'
export function AppShell() {
const { view, sidebarOpen, setSidebarOpen, portalMode } = useAos()
@@ -55,7 +56,7 @@ export function AppShell() {
{/* Main */}
<div className="flex min-w-0 flex-1 flex-col">
<TopBar />
<main className="flex-1">
<main className="flex-1 pb-16 md:pb-0">
{view === 'dashboard' && <DashboardView />}
{view === 'calendar' && <CalendarView />}
{view === 'reporting' && <ReportingView />}
@@ -82,6 +83,7 @@ export function AppShell() {
<CommandMenu />
<CompanyDrawer />
<OnboardingOverlay />
<MobileBottomNav />
</div>
)
}
+63
View File
@@ -0,0 +1,63 @@
'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: ViewId | 'search' | 'menu'; 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: typeof items[number]['id']) => {
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' ? '' : item.id)
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>
)
}