"use client"; import { searchItems } from "@/lib/search"; import Image from "next/image"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; import { SearchIcon, UsersIcon } from "lucide-react"; import { useMemo } from "react"; import { Card, CardAction, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Empty, EmptyDescription, EmptyHeader, EmptyTitle, } from "@/components/ui/empty"; import { InputGroup, InputGroupAddon, InputGroupInput, } from "@/components/ui/input-group"; import { useVisitorPresence } from "@/hooks/use-presence"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; type GuideCard = { id: string; slug: string; name: string; overview: string; coverUrl: string; version: number; aliases: string[]; element: string | null; weaponType: string | null; rarity: number | null; updatedAt: Date; publishedAt: Date | null; publishedCatalogVersion: string | null; }; const elementLabels: Record = { Pyro: "ไฟ", Hydro: "น้ำ", Anemo: "ลม", Electro: "ไฟฟ้า", Dendro: "ไม้", Cryo: "น้ำแข็ง", Geo: "หิน", }; const weaponTypeLabels: Record = { WEAPON_SWORD_ONE_HAND: "ดาบ", WEAPON_CLAYMORE: "ดาบใหญ่", WEAPON_POLE: "หอก", WEAPON_BOW: "ธนู", WEAPON_CATALYST: "สื่อเวท", }; export function StructuredDirectory({ guides }: { guides: GuideCard[] }) { const searchParams = useSearchParams(); const query = searchParams.get("q") ?? ""; const presence = useVisitorPresence(null); const elements = useMemo(() => [...new Set(guides.flatMap((guide) => guide.element ? [guide.element] : []))].sort(), [guides]); const weapons = useMemo(() => [...new Set(guides.flatMap((guide) => guide.weaponType ? [guide.weaponType] : []))].sort(), [guides]); const rarities = useMemo(() => [...new Set(guides.flatMap((guide) => guide.rarity ? [guide.rarity] : []))].sort((a, b) => b - a), [guides]); const elementParam = searchParams.get("element"); const weaponParam = searchParams.get("weapon"); const rarityParam = searchParams.get("rarity"); const element = elementParam && elements.includes(elementParam) ? elementParam : "all"; const weapon = weaponParam && weapons.includes(weaponParam) ? weaponParam : "all"; const rarity = rarityParam && rarities.includes(Number(rarityParam)) ? rarityParam : "all"; const sort = searchParams.get("sort") === "recent" ? "recent" : "manual"; const matchingGuides = guides.filter((guide) => (element === "all" || guide.element === element) && (weapon === "all" || guide.weaponType === weapon) && (rarity === "all" || guide.rarity === Number(rarity)), ); const search = searchItems(matchingGuides, query, (guide) => `${guide.name} ${guide.overview} ${guide.aliases.join(" ")}`, (guide) => [guide.name, ...guide.aliases]); const filtered = sort === "recent" ? [...search.items].sort((left, right) => new Date(right.publishedAt ?? right.updatedAt).getTime() - new Date(left.publishedAt ?? left.updatedAt).getTime()) : search.items; function setParam(name: string, value: string) { const next = new URLSearchParams(searchParams.toString()); if (!value || value === "all" || (name === "sort" && value === "manual")) next.delete(name); else next.set(name, value); const suffix = next.toString(); window.history.replaceState(null, "", suffix ? `?${suffix}` : window.location.pathname); } return (
setParam("q", event.target.value)} placeholder="ค้นหาไกด์…" aria-label="ค้นหาไกด์" />
setParam("element", value)} /> setParam("weapon", value)} /> setParam("rarity", value)} /> setParam("sort", value)} />
{filtered.length ? (
{filtered.map((guide, index) => (
{`ไกด์
{guide.name} 0 ? undefined : "invisible"} > {presence?.byGuide[guide.id] ?? 0} คน {guide.overview}
{guide.publishedCatalogVersion ? v{guide.publishedCatalogVersion} : null}
))}
) : ( ไม่พบไกด์ ลองค้นหาด้วยชื่อตัวละครอื่น )}
); } function GuideFilter({ label, value, options, optionLabels = {}, includeAll = true, onChange, }: { label: string; value: string; options: string[]; optionLabels?: Record; includeAll?: boolean; onChange: (value: string) => void; }) { return ( ); }