'use client'; import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { clsx } from 'clsx'; import { Search, Plus, MessageSquare, BarChart3, Settings, Users, Briefcase, ArrowRight, Clock, Inbox, LayoutDashboard, UserPlus, CheckSquare, Megaphone, } from 'lucide-react'; import { useI18n } from '@/i18n'; type CommandCategory = 'recent' | 'navigation' | 'actions' | 'contacts' | 'deals'; interface CommandItem { id: string; label: string; labelAr: string; category: CommandCategory; icon: typeof Search; keywords: string[]; onSelect?: () => void; } interface CommandPaletteProps { open: boolean; onClose: () => void; onSelect?: (item: CommandItem) => void; } const backdropVariants = { hidden: { opacity: 0 }, visible: { opacity: 1 }, }; const panelVariants = { hidden: { opacity: 0, scale: 0.96, y: -8 }, visible: { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.96, y: -8 }, }; function buildItems(t: (k: string) => string): CommandItem[] { return [ { id: 'nav-dashboard', label: 'Dashboard', labelAr: t('dashboard.tabs.overview'), category: 'navigation', icon: LayoutDashboard, keywords: ['home', 'لوحة', 'loha', 'dashboard'] }, { id: 'nav-pipeline', label: 'Pipeline', labelAr: t('dashboard.tabs.pipeline'), category: 'navigation', icon: Briefcase, keywords: ['deals', 'مسار', 'masar', 'pipeline', 'صفقات'] }, { id: 'nav-inbox', label: 'Inbox', labelAr: t('dashboard.tabs.inbox'), category: 'navigation', icon: Inbox, keywords: ['messages', 'صندوق', 'sandoq', 'inbox', 'رسائل'] }, { id: 'nav-analytics', label: 'Analytics', labelAr: t('dashboard.tabs.analytics'), category: 'navigation', icon: BarChart3, keywords: ['reports', 'تحليلات', 'tahlilat', 'analytics', 'تقارير'] }, { id: 'nav-leads', label: 'Leads', labelAr: t('dashboard.tabs.leads'), category: 'navigation', icon: Users, keywords: ['clients', 'عملاء', '3omala', 'leads'] }, { id: 'nav-settings', label: 'Settings', labelAr: t('dashboard.tabs.settings'), category: 'navigation', icon: Settings, keywords: ['config', 'إعدادات', 'e3dadat', 'settings'] }, { id: 'nav-marketers', label: 'Marketers', labelAr: t('commandPalette.actions.goToMarketers'), category: 'navigation', icon: Megaphone, keywords: ['affiliate', 'مسوقين', 'msawqin', 'marketers'] }, { id: 'act-new-deal', label: 'Create New Deal', labelAr: t('commandPalette.actions.newDeal'), category: 'actions', icon: Plus, keywords: ['new', 'deal', 'صفقة', 'safqa', 'جديد', 'jadid', 'create'] }, { id: 'act-new-contact', label: 'Add Contact', labelAr: t('commandPalette.actions.newContact'), category: 'actions', icon: UserPlus, keywords: ['contact', 'add', 'إضافة', 'edafa', 'جهة', 'jiha'] }, { id: 'act-new-task', label: 'Create Task', labelAr: t('commandPalette.actions.newTask'), category: 'actions', icon: CheckSquare, keywords: ['task', 'مهمة', 'muhimma', 'todo'] }, { id: 'act-send-msg', label: 'Send Message', labelAr: t('commandPalette.actions.sendMessage'), category: 'actions', icon: MessageSquare, keywords: ['message', 'رسالة', 'risala', 'whatsapp', 'واتساب'] }, ]; } function fuzzyMatch(query: string, item: CommandItem, isArabic: boolean): boolean { const q = query.toLowerCase(); const haystack = [ item.label.toLowerCase(), item.labelAr, ...item.keywords.map((k) => k.toLowerCase()), ].join(' '); return haystack.includes(q); } function CommandPalette({ open, onClose, onSelect }: CommandPaletteProps) { const { t, dir, isArabic } = useI18n(); const [query, setQuery] = useState(''); const [activeIndex, setActiveIndex] = useState(0); const inputRef = useRef(null); const listRef = useRef(null); const allItems = useMemo(() => buildItems(t), [t]); const filtered = useMemo(() => { if (!query.trim()) return allItems.slice(0, 8); return allItems.filter((item) => fuzzyMatch(query, item, isArabic)); }, [query, allItems, isArabic]); const grouped = useMemo(() => { const map = new Map(); for (const item of filtered) { const list = map.get(item.category) ?? []; list.push(item); map.set(item.category, list); } return map; }, [filtered]); const flatItems = useMemo(() => filtered, [filtered]); useEffect(() => { if (open) { setQuery(''); setActiveIndex(0); setTimeout(() => inputRef.current?.focus(), 50); } }, [open]); useEffect(() => { setActiveIndex(0); }, [query]); const handleSelect = useCallback( (item: CommandItem) => { onSelect?.(item); item.onSelect?.(); onClose(); }, [onSelect, onClose], ); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === 'ArrowDown') { e.preventDefault(); setActiveIndex((i) => (i + 1) % flatItems.length); } else if (e.key === 'ArrowUp') { e.preventDefault(); setActiveIndex((i) => (i - 1 + flatItems.length) % flatItems.length); } else if (e.key === 'Enter' && flatItems[activeIndex]) { e.preventDefault(); handleSelect(flatItems[activeIndex]); } else if (e.key === 'Escape') { e.preventDefault(); onClose(); } }, [flatItems, activeIndex, handleSelect, onClose], ); useEffect(() => { if (!open) return; const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [open, onClose]); const categoryLabel = (cat: CommandCategory) => t(`commandPalette.categories.${cat}`); return ( {open && (
)}
); } function useCommandPalette() { const [open, setOpen] = useState(false); useEffect(() => { const handler = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); setOpen((prev) => !prev); } }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, []); return { open, setOpen, onClose: () => setOpen(false) }; } export { CommandPalette, useCommandPalette }; export type { CommandPaletteProps, CommandItem };