feat(library): add vocation filter, normalize categories, improve grid layout
Added vocation state and included it in query string builder, updated onFilter to accept a vocation parameter, improved grid layout with grid flow classes, normalized category names for consistent item icons, and refined stats mapping to handle type safety.
This commit is contained in:
1 parent
44d7f8ce81
commit
2b790d8eb4
19 files changed
+2638
-173
No files matched your search
@@ -39,6 +39,7 @@ export default function LibraryClient({ initial }: { initial: InitialData }) {
|
||||
const [rarities, setRarities] = useState<string[]>([]);
|
||||
const [sort, setSort] = useState<string>("name");
|
||||
const [categoryOptions, setCategoryOptions] = useState<{ value: string; label: string }[] | undefined>(undefined);
|
||||
const [vocation, setVocation] = useState<string | null>(null);
|
||||
|
||||
const debounceRef = useRef<number | undefined>(undefined);
|
||||
|
||||
@@ -48,10 +49,11 @@ export default function LibraryClient({ initial }: { initial: InitialData }) {
|
||||
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]);
|
||||
}, [query, categories, rarities, sort, page, pageSize, vocation]);
|
||||
|
||||
useEffect(() => {
|
||||
// Debounce para evitar chamadas excessivas
|
||||
@@ -88,10 +90,11 @@ export default function LibraryClient({ initial }: { initial: InitialData }) {
|
||||
setPage(1);
|
||||
setQuery(q);
|
||||
};
|
||||
const onFilter = (f: { rarity: string[]; category: string[]; priceRange?: { min: number; max: number } }) => {
|
||||
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);
|
||||
@@ -102,28 +105,133 @@ export default function LibraryClient({ initial }: { initial: InitialData }) {
|
||||
<div className="space-y-8">
|
||||
<MarketplaceFilters onSearch={onSearch} onFilter={onFilter} onSort={onSort} totalItems={total} showRarity={false} categoryOptionsOverride={categoryOptions} />
|
||||
|
||||
<section className="grid gap-6 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6">
|
||||
{items.map((item) => (
|
||||
<ItemCard
|
||||
key={`${String(item.category || "")}::${String(item.id)}`}
|
||||
item={{
|
||||
id: String(item.id),
|
||||
name: item.name,
|
||||
image: item.image ?? "",
|
||||
category: (item.category as any) ?? "other",
|
||||
rarity: (item.rarity as any) ?? "common",
|
||||
stats: {
|
||||
weight: typeof item.weight === "number" ? item.weight : undefined,
|
||||
slots: typeof item.slots === "number" ? item.slots : undefined,
|
||||
} as any,
|
||||
requirements: {
|
||||
level: typeof item.level === "number" ? item.level : undefined,
|
||||
skill: typeof item.skill === "string" && item.skill !== "Nenhum." ? item.skill : undefined,
|
||||
} as any,
|
||||
} as any}
|
||||
mode="catalog"
|
||||
/>)
|
||||
)}
|
||||
<section className="grid gap-6 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 grid-flow-row-dense">
|
||||
{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<string, string> = {
|
||||
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 (
|
||||
<ItemCard
|
||||
key={`${String((anyItem as any).category)}::${String(item.id)}::${name}`}
|
||||
item={{
|
||||
id: String(item.id),
|
||||
name,
|
||||
image,
|
||||
category: category as any,
|
||||
rarity: (item.rarity as any) ?? "common",
|
||||
categoryPt,
|
||||
stats: {
|
||||
weight,
|
||||
slots,
|
||||
attack,
|
||||
defense,
|
||||
armor,
|
||||
} as any,
|
||||
requirements: {
|
||||
level,
|
||||
skill,
|
||||
vocation,
|
||||
} as any,
|
||||
// Campos específicos de aljavas (e reutilizáveis para outras categorias)
|
||||
volume: typeof anyItem.volume === "number" ? anyItem.volume : undefined,
|
||||
bonus_de_skill: typeof anyItem.bonus_de_skill === "number" ? anyItem.bonus_de_skill : undefined,
|
||||
protecao: typeof anyItem.protecao === "string" ? anyItem.protecao : undefined,
|
||||
dropa_de: typeof anyItem.dropa_de === "string" ? anyItem.dropa_de : undefined,
|
||||
} as any}
|
||||
mode="catalog"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
|
||||
@@ -19,18 +19,21 @@ export async function GET(req: NextRequest) {
|
||||
const rarities = parseArray(searchParams.get("rarity"));
|
||||
const sortParam = (searchParams.get("sort") || "name") as
|
||||
| "name"
|
||||
| "price-asc"
|
||||
| "price-desc"
|
||||
| "rarity"
|
||||
| "recent";
|
||||
| "level-asc"
|
||||
| "level-desc"
|
||||
| "weight-asc"
|
||||
| "weight-desc"
|
||||
| "category";
|
||||
const page = Number(searchParams.get("page") || "1");
|
||||
const pageSize = Number(searchParams.get("pageSize") || "24");
|
||||
const voc = searchParams.get("voc") || undefined;
|
||||
|
||||
const { items, total } = searchItems({
|
||||
q,
|
||||
categories,
|
||||
rarities,
|
||||
sort: sortParam,
|
||||
vocation: voc,
|
||||
page,
|
||||
pageSize,
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user