diff --git a/workloads/ecommerce/app/catalog/page.tsx b/workloads/ecommerce/app/catalog/page.tsx index deca906..d548dd4 100644 --- a/workloads/ecommerce/app/catalog/page.tsx +++ b/workloads/ecommerce/app/catalog/page.tsx @@ -41,6 +41,7 @@ export default async function CatalogPage({ searchParams }: CatalogPageProps) { ; initialCategory?: string; initialCollection?: string; initialQuery?: string; }; +function matchesQuery(product: Product, query: string): boolean { + if (!query) return true; + const search = [product.name, product.category, product.collection, product.brand].join(" ").toLowerCase(); + return search.includes(query.toLowerCase()); +} +function matchesCategory(product: Product, allowedCategories: string[]): boolean { + return allowedCategories.includes(product.category); +} +function matchesCollection(product: Product, collection: string): boolean { + return !collection || product.collection === collection; +} +function matchesBrand(product: Product, brand: string): boolean { + return brand === "Todas" || product.brand === brand; +} +function matchesAge(product: Product, age: string): boolean { + return age === "Todas" || product.ageRange === age; +} +function matchesPrice(product: Product, maxPrice: number): boolean { + return product.price === null || maxPrice === 0 || product.price <= maxPrice; +} + +// Camina parentId hacia arriba desde la categoria seleccionada para armar +// el breadcrumb real (Inicio > raiz > hija), igual que en Amazon. +function buildBreadcrumb(categories: Category[], name: string): Category[] { + const node = categories.find((category) => category.name === name); + if (!node) return []; + const trail = [node]; + let current = node; + while (current.parentId) { + const parent = categories.find((category) => category.id === current.parentId); + if (!parent) break; + trail.unshift(parent); + current = parent; + } + return trail; +} + export function CatalogClient({ products, + categories: categoryNodes = [], categoryGroups = {}, initialCategory = "Todos", initialCollection = "", @@ -55,29 +95,20 @@ export function CatalogClient({ // tiene la raiz asignada directamente. Si no es una raiz conocida, se // trata como el nombre de categoria exacto de siempre. const allowedCategories = categoryGroups[category] ?? [category]; + const breadcrumb = useMemo(() => (category === "Todos" ? [] : buildBreadcrumb(categoryNodes, category)), [categoryNodes, category]); const filtered = useMemo( () => products - .filter((product) => { - const search = [ - product.name, - product.category, - product.collection, - product.brand, - ] - .join(" ") - .toLowerCase(); - - return ( - (!query || search.includes(query.toLowerCase())) && - (category === "Todos" || allowedCategories.includes(product.category)) && - (!collection || product.collection === collection) && - (brand === "Todas" || product.brand === brand) && - (age === "Todas" || product.ageRange === age) && - (product.price === null || maxPrice === 0 || product.price <= maxPrice) - ); - }) + .filter( + (product) => + matchesQuery(product, query) && + (category === "Todos" || matchesCategory(product, allowedCategories)) && + matchesCollection(product, collection) && + matchesBrand(product, brand) && + matchesAge(product, age) && + matchesPrice(product, maxPrice), + ) .sort((a, b) => sort === "name" ? a.name.localeCompare(b.name, "es") @@ -88,6 +119,57 @@ export function CatalogClient({ [age, allowedCategories, brand, category, collection, maxPrice, products, query, sort], ); + // Conteos por valor de filtro (patron Amazon: "Marca (12)"), calculados + // sobre los productos que ya pasan el resto de filtros activos -- cada + // dimension se cuenta excluyendose a si misma para reflejar cuantos + // resultados habria si se eligiera ese valor. + const categoryCounts = useMemo(() => { + const base = products.filter( + (product) => matchesQuery(product, query) && matchesCollection(product, collection) && matchesBrand(product, brand) && matchesAge(product, age) && matchesPrice(product, maxPrice), + ); + const counts: Record = { Todos: base.length }; + for (const name of categories) { + if (name === "Todos") continue; + const allowed = categoryGroups[name] ?? [name]; + counts[name] = base.filter((product) => matchesCategory(product, allowed)).length; + } + return counts; + }, [age, brand, categories, categoryGroups, collection, maxPrice, products, query]); + + const brandCounts = useMemo(() => { + const base = products.filter( + (product) => + matchesQuery(product, query) && + (category === "Todos" || matchesCategory(product, allowedCategories)) && + matchesCollection(product, collection) && + matchesAge(product, age) && + matchesPrice(product, maxPrice), + ); + const counts: Record = { Todas: base.length }; + for (const value of brands) { + if (value === "Todas") continue; + counts[value] = base.filter((product) => product.brand === value).length; + } + return counts; + }, [age, allowedCategories, brands, category, collection, maxPrice, products, query]); + + const ageCounts = useMemo(() => { + const base = products.filter( + (product) => + matchesQuery(product, query) && + (category === "Todos" || matchesCategory(product, allowedCategories)) && + matchesCollection(product, collection) && + matchesBrand(product, brand) && + matchesPrice(product, maxPrice), + ); + const counts: Record = { Todas: base.length }; + for (const value of ages) { + if (value === "Todas") continue; + counts[value] = base.filter((product) => product.ageRange === value).length; + } + return counts; + }, [ages, allowedCategories, brand, category, collection, maxPrice, products, query]); + const reset = () => { setQuery(""); setCategory("Todos"); @@ -101,6 +183,27 @@ export function CatalogClient({ return (
+ {breadcrumb.length > 0 && ( + + )} Catálogo completo @@ -134,9 +237,10 @@ export function CatalogClient({ values={categories} selected={category} onChange={setCategory} + counts={categoryCounts} /> - - + +
Precio máximo @@ -205,11 +309,13 @@ function Filter({ values, selected, onChange, + counts, }: { title: string; values: string[]; selected: string; onChange: (value: string) => void; + counts?: Record; }) { return (
@@ -227,6 +333,7 @@ function Filter({ }`} > {value} + {counts && ({counts[value] ?? 0})} ))}