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
+53
-18
@@ -5,7 +5,14 @@ export interface SearchParams {
|
||||
q?: string;
|
||||
categories?: string[];
|
||||
rarities?: string[];
|
||||
sort?: "name" | "price-asc" | "price-desc" | "rarity" | "recent";
|
||||
sort?:
|
||||
| "name"
|
||||
| "level-asc"
|
||||
| "level-desc"
|
||||
| "weight-asc"
|
||||
| "weight-desc"
|
||||
| "category";
|
||||
vocation?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
@@ -34,21 +41,22 @@ function getDatabase() {
|
||||
}
|
||||
|
||||
// Tenta deduzir ordenação segura a partir do parâmetro de sort
|
||||
function getOrderBy(sort?: SearchParams["sort"]): string {
|
||||
function getOrderBy(sort: SearchParams["sort"], vocation?: string): { sql: string; bindings: any[] } {
|
||||
// Ordenação sem prioridade de vocação (filtro de voc estrito cuida apenas do WHERE)
|
||||
switch (sort) {
|
||||
case "price-asc":
|
||||
return "npc_price ASC, name ASC";
|
||||
case "price-desc":
|
||||
return "npc_price DESC, name ASC";
|
||||
case "rarity":
|
||||
// Requerer que a tabela possua coluna 'rarity'; caso não exista, recai para nome
|
||||
return "rarity ASC, name ASC";
|
||||
case "recent":
|
||||
// Caso exista created_at/updated_at; fallback para name
|
||||
return "updated_at DESC, name ASC";
|
||||
case "level-asc":
|
||||
return { sql: "level ASC, name ASC", bindings: [] };
|
||||
case "level-desc":
|
||||
return { sql: "level DESC, name ASC", bindings: [] };
|
||||
case "weight-asc":
|
||||
return { sql: "weight ASC, name ASC", bindings: [] };
|
||||
case "weight-desc":
|
||||
return { sql: "weight DESC, name ASC", bindings: [] };
|
||||
case "category":
|
||||
return { sql: "category ASC, name ASC", bindings: [] };
|
||||
case "name":
|
||||
default:
|
||||
return "name ASC";
|
||||
return { sql: "name ASC", bindings: [] };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +69,7 @@ export function searchItems<T = any>(params: SearchParams): SearchResult<T> {
|
||||
categories = [],
|
||||
rarities = [],
|
||||
sort = "name",
|
||||
vocation,
|
||||
page = 1,
|
||||
pageSize = 24,
|
||||
} = params;
|
||||
@@ -117,7 +126,7 @@ export function searchItems<T = any>(params: SearchParams): SearchResult<T> {
|
||||
bindings.push(...rarities.map((r) => r.toLowerCase()));
|
||||
}
|
||||
|
||||
const orderBy = getOrderBy(sort);
|
||||
const { sql: orderBy, bindings: orderBindings } = getOrderBy(sort, vocation);
|
||||
|
||||
// Seleciona de múltiplas tabelas (PT) unificando shape padronizado
|
||||
const allTables = db
|
||||
@@ -141,18 +150,44 @@ export function searchItems<T = any>(params: SearchParams): SearchResult<T> {
|
||||
const colNames = new Set(cols.map((c) => (c?.name || "").toLowerCase()));
|
||||
// Requer colunas mínimas
|
||||
if (!colNames.has("id") || !colNames.has("nome") || !colNames.has("image_url")) continue;
|
||||
// Se filtro de vocação foi solicitado e a tabela não possui coluna 'voc', ignora esta tabela
|
||||
if (vocation && !colNames.has("voc")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tableWhere: string[] = [];
|
||||
if (q && colNames.has("nome")) {
|
||||
tableWhere.push("LOWER(nome) LIKE ?");
|
||||
unionBindings.push(`%${q.toLowerCase()}%`);
|
||||
}
|
||||
if (vocation && colNames.has("voc")) {
|
||||
const sel = vocation.toLowerCase();
|
||||
// Filtro estrito: comparar normalizando espaços
|
||||
tableWhere.push("REPLACE(LOWER(voc), ' ', '') = REPLACE(?, ' ', '')");
|
||||
unionBindings.push(sel);
|
||||
}
|
||||
const whereSqlTbl = tableWhere.length ? `WHERE ${tableWhere.join(" AND ")}` : "";
|
||||
// category: derivamos do nome da tabela (pt)
|
||||
const levelCol = colNames.has("nivel") ? "nivel" : "NULL";
|
||||
const weightCol = colNames.has("peso") ? "peso" : "NULL";
|
||||
const levelCol = colNames.has("nivel") || colNames.has("lvl")
|
||||
? `COALESCE(${colNames.has("nivel") ? "nivel" : "NULL"}, ${colNames.has("lvl") ? "lvl" : "NULL"})`
|
||||
: "NULL";
|
||||
const weightCol = colNames.has("peso") || colNames.has("peso_em_oz")
|
||||
? `COALESCE(${colNames.has("peso") ? "peso" : "NULL"}, ${colNames.has("peso_em_oz") ? "peso_em_oz" : "NULL"})`
|
||||
: "NULL";
|
||||
const slotsCol = colNames.has("slots") ? "slots" : "NULL";
|
||||
const skillCol = colNames.has("skill_para_usar") ? "skill_para_usar" : "NULL";
|
||||
// Campos extras opcionais por tabela (ex.: aljavas)
|
||||
const extraCols = [
|
||||
"lvl",
|
||||
"voc",
|
||||
"volume",
|
||||
"bonus_de_skill",
|
||||
"protecao",
|
||||
"dropa_de",
|
||||
] as const;
|
||||
const extraSelect = extraCols
|
||||
.map((c) => (colNames.has(c) ? `${c}` : `NULL AS ${c}`))
|
||||
.join(",\n ");
|
||||
selects.push(
|
||||
`SELECT
|
||||
id,
|
||||
@@ -162,7 +197,7 @@ export function searchItems<T = any>(params: SearchParams): SearchResult<T> {
|
||||
${levelCol} AS level,
|
||||
${weightCol} AS weight,
|
||||
${slotsCol} AS slots,
|
||||
${skillCol} AS skill
|
||||
${skillCol} AS skill${extraSelect ? ",\n " + extraSelect : ""}
|
||||
FROM ${t} ${whereSqlTbl}`
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -182,7 +217,7 @@ export function searchItems<T = any>(params: SearchParams): SearchResult<T> {
|
||||
const total = totalRow?.cnt ?? 0;
|
||||
|
||||
const selectStmt = db.prepare(
|
||||
`SELECT * FROM (${unionSql}) AS u ORDER BY name ASC LIMIT ? OFFSET ?`
|
||||
`SELECT * FROM (${unionSql}) AS u ORDER BY ${orderBy} LIMIT ? OFFSET ?`
|
||||
);
|
||||
const items = selectStmt.all(...unionBindings, safePageSize, offset) as T[];
|
||||
|
||||
|
||||
Reference in new issue
Block a user