"use client"; import { SearchIcon } from "lucide-react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useRef, useState } from "react"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Dialog, DialogContent } from "@/components/ui/dialog"; import { useDebounce } from "@/hooks/use-debounce"; type SearchResult = { type: string; id: number | string; title: string; subtitle: string; url: string; }; type SearchResponse = { ok: boolean; results: SearchResult[]; }; export function SearchDialog() { const t = useTranslations("pages.admin.search"); const router = useRouter(); const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const debouncedQuery = useDebounce(query, 250); const abortRef = useRef(null); const groupLabels: Record = { users: t("users"), articles: t("articles"), rooms: t("rooms"), guilds: t("guilds"), shop: t("shop"), rareValues: t("rareValues"), }; const fetchResults = useCallback(async (q: string) => { abortRef.current?.abort(); if (q.length < 2) { setResults([]); setLoading(false); return; } const controller = new AbortController(); abortRef.current = controller; setLoading(true); try { const res = await fetch(`/api/admin/search?q=${encodeURIComponent(q)}`, { signal: controller.signal, }); const data: SearchResponse = await res.json(); if (data.ok) setResults(data.results); } catch {} setLoading(false); }, []); useEffect(() => { fetchResults(debouncedQuery); }, [debouncedQuery, fetchResults]); useEffect(() => { function handleKeyDown(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); setOpen((prev) => !prev); } } document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, []); function handleSelect(url: string) { setOpen(false); setQuery(""); setResults([]); router.push(url); } const grouped = groupResults(results); return ( {loading && query.length >= 2 ? (
…
) : null} {t("noResults")} {Object.entries(grouped).map(([group, items]) => ( {items.map((item) => ( handleSelect(item.url)} className="flex flex-col items-start gap-0.5 py-2" > {item.title} {item.subtitle ? ( {item.subtitle} ) : null} ))} ))}
); } function groupResults(results: SearchResult[]) { const grouped: Record = {}; for (const r of results) { if (!grouped[r.type]) grouped[r.type] = []; grouped[r.type].push(r); } return grouped; }