import { useEffect, useState } from 'react'; interface SearchResult { unique_name: string; name_en: string | null; tier: number | null; enchantment: number; shop_category: string | null; shop_subcategory: string | null; } interface RecipeInput { input_item: string; count: number; max_return_amount: number | null; name_en: string | null; } interface Recipe { id: number; source: string; variant: number; amount_crafted: number; silver_cost: number; focus_cost: number; inputs: RecipeInput[]; } interface ItemDetail { item: SearchResult & { weight: number | null; item_value: number | null; crafting_category: string | null }; recipes: Recipe[]; usedIn: { output_item: string; name_en: string | null }[]; } export default function ItemsPage() { const [q, setQ] = useState(''); const [results, setResults] = useState([]); const [detail, setDetail] = useState(null); useEffect(() => { if (!q.trim()) { setResults([]); return; } const t = setTimeout(() => { fetch(`/api/items/search?q=${encodeURIComponent(q)}`) .then((r) => r.json()) .then((d) => setResults(d.results ?? [])); }, 250); return () => clearTimeout(t); }, [q]); const open = (uniqueName: string) => { fetch(`/api/items/${encodeURIComponent(uniqueName)}`) .then((r) => r.json()) .then(setDetail); }; return (

Item Browser

setQ(e.target.value)} /> {results.length > 0 && ( {results.map((r) => ( open(r.unique_name)}> ))}
NameIDTierCategory
{r.name_en ?? (unnamed)} {r.enchantment > 0 && .{r.enchantment}} {r.unique_name} {r.tier ?? '—'} {r.shop_subcategory ?? r.shop_category ?? '—'}
)}
{detail && (

{detail.item.name_en ?? detail.item.unique_name} {detail.item.unique_name}

Tier {detail.item.tier ?? '—'} · weight {detail.item.weight ?? '—'} · item value{' '} {detail.item.item_value ?? '—'} · {detail.item.crafting_category ?? 'no crafting category'}

{detail.recipes.length === 0 &&

No crafting recipe (gathered, dropped, or vendor item).

} {detail.recipes.map((r) => (
{r.source === 'upgrade' ? 'Enchanting (upgrade)' : 'Crafting'} recipe {r.variant > 0 ? ` (alt ${r.variant})` : ''} focus {r.focus_cost} {r.amount_crafted !== 1 && crafts {r.amount_crafted}}
    {r.inputs.map((inp) => (
  • {inp.count}× {inp.name_en ?? inp.input_item}{' '} {inp.max_return_amount === 0 && no return}
  • ))}
))} {detail.usedIn.length > 0 && ( <> Used in

{detail.usedIn.slice(0, 20).map((u) => u.name_en ?? u.output_item).join(', ')} {detail.usedIn.length > 20 ? ` … +${detail.usedIn.length - 20} more` : ''}

)}
)}
); }