b6167bcb-7363-4695-9481-addd81de4367
This commit is contained in:
1 parent
fdc3b82d10
commit
d226dabba3
136 files changed
+13904
-2
No files matched your search
@@ -0,0 +1,98 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Users,
|
||||
Radar,
|
||||
FolderKanban,
|
||||
KeyRound,
|
||||
Receipt,
|
||||
Settings2,
|
||||
RotateCcw,
|
||||
} from 'lucide-react'
|
||||
import { useAos, type ViewId } from '@/store/aos'
|
||||
import {
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from '@/components/ui/command'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const VIEWS: { id: ViewId; label: string; icon: typeof LayoutDashboard; hint: string }[] = [
|
||||
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard, hint: 'Übersicht & KPIs' },
|
||||
{ id: 'crm', label: 'CRM', icon: Users, hint: 'Firmen & Pipeline' },
|
||||
{ id: 'leads', label: 'Leads Finder', icon: Radar, hint: 'Review-Queue' },
|
||||
{ id: 'projects', label: 'Projekte', icon: FolderKanban, hint: 'Aufgaben & Zeit' },
|
||||
{ id: 'licenses', label: 'Lizenzen', icon: KeyRound, hint: 'Ausgestellt & Erneuerungen' },
|
||||
{ id: 'finance', label: 'Finanzen', icon: Receipt, hint: 'Rechnungen & E-Rechnung' },
|
||||
{ id: 'settings', label: 'Erweiterungen', icon: Settings2, hint: 'Module & Compliance' },
|
||||
]
|
||||
|
||||
export function CommandMenu() {
|
||||
const { commandOpen, setCommandOpen, setView } = useAos()
|
||||
const router = useRouter()
|
||||
|
||||
// keyboard shortcut cmd+k
|
||||
useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault()
|
||||
setCommandOpen(!commandOpen)
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', handler)
|
||||
return () => window.removeEventListener('keydown', handler)
|
||||
}, [commandOpen, setCommandOpen])
|
||||
|
||||
const go = (v: ViewId) => {
|
||||
setView(v)
|
||||
setCommandOpen(false)
|
||||
}
|
||||
|
||||
const reseed = async () => {
|
||||
setCommandOpen(false)
|
||||
const t = toast.loading('Datenbank wird zurückgesetzt…')
|
||||
try {
|
||||
const r = await fetch('/api/aos/seed', { method: 'POST' })
|
||||
if (!r.ok) throw new Error('reseed failed')
|
||||
toast.success('Datenbank neu befüllt', { id: t })
|
||||
setTimeout(() => router.refresh(), 400)
|
||||
} catch {
|
||||
toast.error('Reseed fehlgeschlagen', { id: t })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandDialog open={commandOpen} onOpenChange={setCommandOpen}>
|
||||
<CommandInput placeholder="Modul, Aktion oder Seite suchen…" />
|
||||
<CommandList>
|
||||
<CommandEmpty>Keine Treffer.</CommandEmpty>
|
||||
<CommandGroup heading="Navigation">
|
||||
{VIEWS.map((v) => {
|
||||
const Icon = v.icon
|
||||
return (
|
||||
<CommandItem key={v.id} value={`${v.label} ${v.hint}`} onSelect={() => go(v.id)}>
|
||||
<Icon className="size-4" />
|
||||
<span>{v.label}</span>
|
||||
<span className="ml-auto text-xs text-muted-foreground">{v.hint}</span>
|
||||
</CommandItem>
|
||||
)
|
||||
})}
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Aktionen">
|
||||
<CommandItem onSelect={reseed} value="demo daten zurücksetzen reseed reset">
|
||||
<RotateCcw className="size-4" />
|
||||
<span>Demo-Daten zurücksetzen</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user