"use client"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import type { SearchHit } from "@/lib/admin/search"; const TYPE_LABEL: Record = { carbet: "Carbet", user: "Utilisateur", booking: "Réservation", page: "Page", provider: "Prestataire", }; const TYPE_ACCENT: Record = { carbet: "bg-emerald-100 text-emerald-800", user: "bg-sky-100 text-sky-800", booking: "bg-amber-100 text-amber-800", page: "bg-violet-100 text-violet-800", provider: "bg-rose-100 text-rose-800", }; /** * Palette ⌘K minimaliste, sans dépendance externe. Server search via * /api/admin/search?q=…, navigation au clavier (↑/↓/Enter/Esc). */ export function CommandPalette() { const router = useRouter(); const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const [hits, setHits] = useState([]); const [selected, setSelected] = useState(0); const [loading, setLoading] = useState(false); const inputRef = useRef(null); const abortRef = useRef(null); // Ouvre la palette sur ⌘K / Ctrl+K. Esc ferme. useEffect(() => { function onKey(e: KeyboardEvent) { const cmd = e.metaKey || e.ctrlKey; if (cmd && e.key.toLowerCase() === "k") { e.preventDefault(); setOpen((v) => !v); } else if (e.key === "Escape") { setOpen(false); } } window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); useEffect(() => { if (open) { setQuery(""); setHits([]); setSelected(0); setTimeout(() => inputRef.current?.focus(), 50); } }, [open]); const runSearch = useCallback(async (q: string) => { if (q.trim().length < 2) { setHits([]); return; } abortRef.current?.abort(); const ac = new AbortController(); abortRef.current = ac; setLoading(true); try { const r = await fetch(`/api/admin/search?q=${encodeURIComponent(q)}`, { signal: ac.signal }); if (r.ok) { const j = await r.json(); setHits(j.hits ?? []); setSelected(0); } } catch { // aborted ou erreur silencieuse } finally { setLoading(false); } }, []); useEffect(() => { const id = setTimeout(() => runSearch(query), 150); return () => clearTimeout(id); }, [query, runSearch]); function onListKey(e: React.KeyboardEvent) { if (e.key === "ArrowDown") { e.preventDefault(); setSelected((s) => Math.min(s + 1, hits.length - 1)); } else if (e.key === "ArrowUp") { e.preventDefault(); setSelected((s) => Math.max(s - 1, 0)); } else if (e.key === "Enter") { e.preventDefault(); const hit = hits[selected]; if (hit) { setOpen(false); router.push(hit.href); } } } if (!open) return null; return (
setOpen(false)} >
e.stopPropagation()} >
setQuery(e.target.value)} onKeyDown={onListKey} className="flex-1 bg-transparent text-sm text-zinc-900 placeholder-zinc-400 focus:outline-none" /> ESC
{loading ? (
) : query.length >= 2 && hits.length === 0 ? (
Aucun résultat.
) : hits.length === 0 ? (
Tape au moins 2 caractères. Navigation : ↑ ↓ / Entrée.
) : (
    {hits.map((h, i) => (
  • setOpen(false)} onMouseEnter={() => setSelected(i)} className={`flex items-center justify-between gap-3 px-3 py-2 text-sm ${ i === selected ? "bg-zinc-100" : "hover:bg-zinc-50" }`} > {TYPE_LABEL[h.type]} {h.title} {h.subtitle ? ( {h.subtitle} ) : null}
  • ))}
)}
); }