"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "@/components/ui/command"; import { Search, LayoutDashboard, Activity, Calendar, Kanban, FolderKanban, FileText, Wrench, Target, ExternalLink, CheckCircle2, Clock, AlertCircle, Timer, Circle, } from "lucide-react"; // Search result type from API type SearchResultType = "task" | "project" | "document" | "sprint" | "activity"; interface SearchResult { id: string; type: SearchResultType; title: string; subtitle?: string; description?: string; metadata?: Record; status?: string; priority?: string; color?: string; url?: string; icon?: string; } const navItems = [ { name: "Dashboard", href: "/", icon: LayoutDashboard, shortcut: "D" }, { name: "Activity", href: "/activity", icon: Activity, shortcut: "A" }, { name: "Calendar", href: "/calendar", icon: Calendar, shortcut: "C" }, { name: "Tasks", href: "/tasks", icon: Kanban, shortcut: "T" }, { name: "Projects", href: "/projects", icon: FolderKanban, shortcut: "P" }, { name: "Documents", href: "/documents", icon: FileText, shortcut: "D" }, { name: "Tools", href: "/tools", icon: Wrench, shortcut: "O" }, { name: "Mission", href: "/mission", icon: Target, shortcut: "M" }, ]; const quickLinks = [ { name: "Gantt Board", url: "https://gantt-board.vercel.app", icon: ExternalLink }, { name: "Blog Backup", url: "https://blog-backup-two.vercel.app", icon: ExternalLink }, { name: "Gitea", url: "http://192.168.1.128:3000", icon: ExternalLink }, ]; // Icon mapping for search result types const typeIcons: Record = { task: Kanban, project: FolderKanban, document: FileText, sprint: Timer, activity: Activity, }; // Tag colors for documents const tagColors: Record = { infrastructure: "bg-blue-500/20 text-blue-400", monitoring: "bg-green-500/20 text-green-400", security: "bg-red-500/20 text-red-400", urgent: "bg-orange-500/20 text-orange-400", guide: "bg-purple-500/20 text-purple-400", }; // Type labels const typeLabels: Record = { task: "Task", project: "Project", document: "Document", sprint: "Sprint", activity: "Activity", }; function getStatusIcon(status?: string) { switch (status) { case "done": case "completed": return ; case "in-progress": return ; case "open": case "todo": return ; default: return ; } } function getPriorityColor(priority?: string): string { switch (priority) { case "urgent": return "text-red-500"; case "high": return "text-orange-500"; case "medium": return "text-yellow-500"; default: return "text-blue-500"; } } export function QuickSearch() { const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const router = useRouter(); // Debounced search useEffect(() => { if (!open || query.length < 2) { setResults([]); return; } setLoading(true); const timer = setTimeout(async () => { try { const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`); const data = await res.json(); setResults(data.results || []); } catch (err) { console.error("Search error:", err); setResults([]); } finally { setLoading(false); } }, 150); // 150ms debounce return () => clearTimeout(timer); }, [query, open]); // Keyboard shortcut useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); setOpen((open) => !open); } }; document.addEventListener("keydown", down); return () => document.removeEventListener("keydown", down); }, []); const runCommand = useCallback((command: () => void) => { setOpen(false); setQuery(""); command(); }, []); const handleResultSelect = (result: SearchResult) => { runCommand(() => { if (result.url?.startsWith("http")) { window.open(result.url, "_blank"); } else if (result.url) { router.push(result.url); } }); }; // Group results by type const groupedResults = results.reduce((acc, result) => { if (!acc[result.type]) acc[result.type] = []; acc[result.type].push(result); return acc; }, {} as Record); // Get ordered list of types that have results const activeTypes = (Object.keys(groupedResults) as SearchResultType[]).filter( (type) => groupedResults[type]?.length > 0 ); return ( <> {/* Search Button Trigger */} {/* Command Dialog */} {loading ? "Searching..." : query.length < 2 ? "Type to search..." : "No results found."} {/* Show search results when query exists */} {query.length >= 2 && activeTypes.length > 0 && ( <> {activeTypes.map((type) => { const Icon = typeIcons[type]; const typeResults = groupedResults[type]; return ( {typeResults.slice(0, 5).map((result) => ( handleResultSelect(result)} > {result.type === "task" ? ( getStatusIcon(result.status) ) : result.type === "project" && result.color ? (
) : ( )}
{result.title} {result.subtitle && ( {result.subtitle} )}
{/* Document tags */} {result.type === "document" && result.metadata?.tags && (
{result.metadata.tags.split(",").slice(0, 2).map((tag: string) => ( {tag.trim()} ))}
)} {result.priority && ( {result.priority} )} {result.status && result.type !== "task" && ( {result.status} )} ))} ); })} )} {/* Navigation */} {navItems.map((item) => { const Icon = item.icon; return ( runCommand(() => router.push(item.href))} > {item.name} ); })} {/* Quick Links */} {quickLinks.map((link) => { const Icon = link.icon; return ( runCommand(() => window.open(link.url, "_blank"))} > {link.name} ); })} ); }