"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import { MarketplaceFilters } from "@/components/marketplace-filters"; import { ItemCard } from "@/components/item-card"; interface Item { id: string | number; name: string; category: string; image?: string; rarity?: string; npcPrice?: number; price?: { buy: number; sell: number }; priceHistory?: { highest: number; lowest: number; average: number }; level?: number; weight?: number; slots?: number; skill?: string; } interface InitialData { items: Item[]; total: number; page: number; pageSize: number; totalPages: number; } export default function LibraryClient({ initial }: { initial: InitialData }) { const [items, setItems] = useState(initial.items); const [total, setTotal] = useState(initial.total); const [page, setPage] = useState(initial.page || 1); const [pageSize] = useState(initial.pageSize || 24); const [totalPages, setTotalPages] = useState(initial.totalPages || 1); const [query, setQuery] = useState(""); const [categories, setCategories] = useState([]); const [rarities, setRarities] = useState([]); const [sort, setSort] = useState("name"); const [categoryOptions, setCategoryOptions] = useState<{ value: string; label: string }[] | undefined>(undefined); const [vocation, setVocation] = useState(null); const debounceRef = useRef(undefined); const searchParams = useMemo(() => { const p = new URLSearchParams(); if (query) p.set("q", query); if (categories.length > 0) p.set("category", categories.join(",")); if (rarities.length > 0) p.set("rarity", rarities.join(",")); if (sort) p.set("sort", sort); if (vocation) p.set("voc", vocation); p.set("page", String(page)); p.set("pageSize", String(pageSize)); return p; }, [query, categories, rarities, sort, page, pageSize, vocation]); useEffect(() => { // Debounce para evitar chamadas excessivas window.clearTimeout(debounceRef.current); debounceRef.current = window.setTimeout(async () => { const res = await fetch(`/api/items?${searchParams.toString()}`); if (!res.ok) return; const data = await res.json(); setItems(data.items); setTotal(data.total); setTotalPages(data.totalPages); }, 250); return () => window.clearTimeout(debounceRef.current); }, [searchParams]); // Carregar categorias dinâmicas a partir das tabelas do banco useEffect(() => { (async () => { try { const res = await fetch("/api/items/categories"); if (!res.ok) return; const data = await res.json(); if (Array.isArray(data.categories)) { const opts = (data.categories as string[]).map((c) => ({ value: c, label: c.charAt(0).toUpperCase() + c.slice(1) })); setCategoryOptions(opts); } } catch { // no-op } })(); }, []); const onSearch = (q: string) => { setPage(1); setQuery(q); }; const onFilter = (f: { rarity: string[]; category: string[]; priceRange?: { min: number; max: number }; vocation?: string | null }) => { setPage(1); setCategories(f.category || []); setRarities(f.rarity || []); setVocation(f.vocation ?? null); }; const onSort = (s: string) => { setPage(1); setSort(s); }; return (
{items.map((item) => { const anyItem = item as any; // Normalização de categoria para os ícones do ItemCard const normalizeCategory = (c: string | undefined): string => { const v = (c || "").toLowerCase(); const map: Record = { escudos: "shield", armaduras: "armor", capacetes: "helmet", calcas: "legs", botas: "boots", amuletos: "amulet", aneis: "ring", runas: "rune", ferramentas: "tool", municao: "ammunition", aljavas: "ammunition", distancia: "weapon", espadas: "weapon", machados: "weapon", clavas: "weapon", punhos: "weapon", wands: "weapon", rods: "weapon", spellbooks: "other", chaves: "other", comidas: "other", decoracoes: "other", liquidos: "other", plantas: "other", recipientes: "other", trofeus: "other", }; return map[v] || v || "other"; }; const categoryPt = typeof anyItem.categoria === "string" ? anyItem.categoria : undefined; const category = normalizeCategory(item.category ?? categoryPt); const name = item.name ?? anyItem.nome ?? String(item.id); const image = item.image ?? anyItem.image_url ?? ""; // Campos numéricos normalizados const level: number | undefined = typeof item.level === "number" ? item.level : typeof anyItem.nivel === "number" ? anyItem.nivel : typeof anyItem.lvl === "number" ? anyItem.lvl : undefined; const weight: number | undefined = typeof item.weight === "number" ? item.weight : typeof anyItem.peso === "number" ? anyItem.peso : typeof anyItem.peso_em_oz === "number" ? anyItem.peso_em_oz : undefined; const slots: number | undefined = typeof item.slots === "number" ? item.slots : typeof anyItem.slots === "number" ? anyItem.slots : undefined; const attack: number | undefined = typeof anyItem.ataque === "number" ? anyItem.ataque : typeof anyItem.atq === "number" ? anyItem.atq : undefined; const defense: number | undefined = typeof anyItem.defesa === "number" ? anyItem.defesa : typeof anyItem.def === "number" ? anyItem.def : undefined; const armor: number | undefined = typeof anyItem.arm === "number" ? anyItem.arm : undefined; // Skill/requisito para usar const skill: string | undefined = typeof item.skill === "string" && item.skill !== "Nenhum." ? item.skill : typeof anyItem.skill_para_usar === "string" && anyItem.skill_para_usar !== "Nenhum." ? anyItem.skill_para_usar : undefined; const vocation: string | undefined = typeof anyItem.voc === "string" && anyItem.voc ? anyItem.voc : undefined; return ( ); })}
Página {page} de {totalPages}
); }