175 lines
6.4 KiB
TypeScript
175 lines
6.4 KiB
TypeScript
'use client'
|
|
|
|
import {
|
|
LayoutDashboard,
|
|
Users,
|
|
Radar,
|
|
FolderKanban,
|
|
KeyRound,
|
|
Receipt,
|
|
Settings2,
|
|
Command,
|
|
Search,
|
|
Bot,
|
|
ScrollText,
|
|
FileSignature,
|
|
Wallet,
|
|
FileText,
|
|
LifeBuoy,
|
|
BarChart3,
|
|
Rocket,
|
|
BookOpen,
|
|
Webhook,
|
|
} from 'lucide-react'
|
|
import { useAos, type ViewId } from '@/store/aos'
|
|
import { cn } from '@/lib/utils'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
const NAV: {
|
|
group: string
|
|
items: { id: ViewId; label: string; icon: typeof LayoutDashboard; permission: string }[]
|
|
}[] = [
|
|
{
|
|
group: 'Übersicht',
|
|
items: [
|
|
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard, permission: '*' },
|
|
{ id: 'reporting', label: 'Reporting', icon: BarChart3, permission: '*' },
|
|
],
|
|
},
|
|
{
|
|
group: 'Vertrieb',
|
|
items: [
|
|
{ id: 'crm', label: 'CRM', icon: Users, permission: 'crm.company:read' },
|
|
{ id: 'leads', label: 'Leads Finder', icon: Radar, permission: 'leads:read' },
|
|
{ id: 'estimates', label: 'Angebote', icon: FileText, permission: 'estimates:read' },
|
|
],
|
|
},
|
|
{
|
|
group: 'Lieferung',
|
|
items: [
|
|
{ id: 'projects', label: 'Projekte', icon: FolderKanban, permission: 'projects:read' },
|
|
{ id: 'support', label: 'Support', icon: LifeBuoy, permission: 'support:read' },
|
|
],
|
|
},
|
|
{
|
|
group: 'Betrieb',
|
|
items: [
|
|
{ id: 'licenses', label: 'Lizenzen', icon: KeyRound, permission: 'licenses:read' },
|
|
{ id: 'contracts', label: 'Verträge', icon: FileSignature, permission: 'contracts:read' },
|
|
{ id: 'expenses', label: 'Ausgaben', icon: Wallet, permission: 'expenses:read' },
|
|
{ id: 'finance', label: 'Finanzen', icon: Receipt, permission: 'finance:read' },
|
|
{ id: 'accounting', label: 'Buchhaltung', icon: BookOpen, permission: 'accounting:read' },
|
|
{ id: 'audit', label: 'Audit-Protokoll', icon: ScrollText, permission: '*' },
|
|
],
|
|
},
|
|
{
|
|
group: 'System',
|
|
items: [
|
|
{ id: 'settings', label: 'Erweiterungen', icon: Settings2, permission: 'admin' },
|
|
{ id: 'api', label: 'API & Webhooks', icon: Webhook, permission: 'admin' },
|
|
{ id: 'setup', label: 'Setup-Wizard', icon: Rocket, permission: 'admin' },
|
|
],
|
|
},
|
|
]
|
|
|
|
export function SidebarNav() {
|
|
const { view, setView, setSidebarOpen, setCommandOpen } = useAos()
|
|
|
|
return (
|
|
<div className="flex h-full flex-col bg-sidebar text-sidebar-foreground">
|
|
{/* Brand */}
|
|
<div className="flex h-16 items-center gap-2.5 px-5 border-b border-sidebar-border">
|
|
<div className="flex size-9 items-center justify-center rounded-md bg-primary text-primary-foreground font-bold tracking-tight">
|
|
A
|
|
</div>
|
|
<div className="flex flex-col leading-tight">
|
|
<span className="text-sm font-semibold">AOS</span>
|
|
<span className="text-[11px] text-muted-foreground">Nordlicht Digital</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Search trigger */}
|
|
<div className="px-3 pt-3">
|
|
<button
|
|
onClick={() => setCommandOpen(true)}
|
|
className="flex w-full items-center gap-2 rounded-md border border-sidebar-border bg-background/50 px-3 py-2 text-sm text-muted-foreground transition hover:bg-background"
|
|
>
|
|
<Search className="size-3.5" />
|
|
<span>Suchen…</span>
|
|
<kbd className="ml-auto inline-flex items-center gap-0.5 rounded border border-border bg-muted px-1.5 py-0.5 text-[10px] font-mono">
|
|
<Command className="size-2.5" />K
|
|
</kbd>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Nav */}
|
|
<nav className="flex-1 overflow-y-auto px-2 py-3 [scrollbar-width:thin]">
|
|
{NAV.map((section) => (
|
|
<div key={section.group} className="mb-4">
|
|
<p className="px-3 pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70">
|
|
{section.group}
|
|
</p>
|
|
<div className="space-y-0.5">
|
|
{section.items.map((item) => {
|
|
const Icon = item.icon
|
|
const active = view === item.id
|
|
return (
|
|
<button
|
|
key={item.id}
|
|
onClick={() => {
|
|
setView(item.id)
|
|
setSidebarOpen(false)
|
|
}}
|
|
className={cn(
|
|
'group flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition',
|
|
active
|
|
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
: 'text-muted-foreground hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground'
|
|
)}
|
|
aria-current={active ? 'page' : undefined}
|
|
>
|
|
<Icon className={cn('size-4 shrink-0', active ? 'text-foreground' : 'text-muted-foreground group-hover:text-foreground')} />
|
|
<span className="truncate">{item.label}</span>
|
|
{item.id === 'leads' && (
|
|
<Badge variant="secondary" className="ml-auto h-5 px-1.5 text-[10px]">
|
|
AI
|
|
</Badge>
|
|
)}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</nav>
|
|
|
|
{/* AI status */}
|
|
<div className="mx-3 mb-3 rounded-md border border-sidebar-border bg-background/40 p-3">
|
|
<div className="flex items-center gap-2">
|
|
<div className="flex size-7 items-center justify-center rounded bg-muted">
|
|
<Bot className="size-3.5 text-muted-foreground" />
|
|
</div>
|
|
<div className="flex flex-col leading-tight">
|
|
<span className="text-xs font-medium">AI-Ebene</span>
|
|
<span className="text-[10px] text-muted-foreground">Optional · Daten im Server</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* User */}
|
|
<div className="border-t border-sidebar-border px-3 py-3">
|
|
<Button variant="ghost" className="h-auto w-full justify-start gap-3 px-2 py-2">
|
|
<div className="flex size-8 items-center justify-center rounded-full bg-primary text-primary-foreground text-xs font-semibold">
|
|
MB
|
|
</div>
|
|
<div className="flex flex-col items-start leading-tight">
|
|
<span className="text-sm font-medium">Max Brandt</span>
|
|
<span className="text-[11px] text-muted-foreground">Owner</span>
|
|
</div>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|