diff --git a/workloads/ecommerce/components/catalog/catalog-client.tsx b/workloads/ecommerce/components/catalog/catalog-client.tsx index d5b22dd..5cbf1cd 100644 --- a/workloads/ecommerce/components/catalog/catalog-client.tsx +++ b/workloads/ecommerce/components/catalog/catalog-client.tsx @@ -3,11 +3,19 @@ import Link from "next/link"; import { ChevronRight, SlidersHorizontal, X } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; +import { PromoTile } from "@/components/home/category-tiles"; import { ProductCard } from "@/components/product/product-card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import type { Category, Product } from "@/lib/headless/types"; +// Cada cuantas tarjetas de producto se intercala un tile promocional en +// la grilla (patron Walmart, ADR 0005 seccion 3): grid con tiles +// promocionales intercalados, no solo tarjetas homogeneas. +const PROMO_TILE_INTERVAL = 8; + +type GridItem = { type: "product"; product: Product } | { type: "promo"; category: Category }; + type CatalogClientProps = { products: Product[]; categories?: Category[]; @@ -96,6 +104,11 @@ export function CatalogClient({ // trata como el nombre de categoria exacto de siempre. const allowedCategories = categoryGroups[category] ?? [category]; const breadcrumb = useMemo(() => (category === "Todos" ? [] : buildBreadcrumb(categoryNodes, category)), [categoryNodes, category]); + // No promociona la misma categoria raiz que el usuario ya esta viendo. + const promoRoots = useMemo(() => { + const excludeRootId = breadcrumb[0]?.id; + return categoryNodes.filter((node) => !node.parentId && node.id !== excludeRootId); + }, [categoryNodes, breadcrumb]); const filtered = useMemo( () => @@ -119,6 +132,26 @@ export function CatalogClient({ [age, allowedCategories, brand, category, collection, maxPrice, products, query, sort], ); + // Intercala un tile promocional cada PROMO_TILE_INTERVAL productos, + // ciclando por las categorias raiz -- si no hay jerarquia de + // categorias disponible, la grilla queda igual que antes (solo + // productos). + const gridItems = useMemo(() => { + if (promoRoots.length === 0) return filtered.map((product) => ({ type: "product", product })); + + const items: GridItem[] = []; + let promoIndex = 0; + filtered.forEach((product, index) => { + items.push({ type: "product", product }); + const isLast = index === filtered.length - 1; + if (!isLast && (index + 1) % PROMO_TILE_INTERVAL === 0) { + items.push({ type: "promo", category: promoRoots[promoIndex % promoRoots.length] }); + promoIndex += 1; + } + }); + return items; + }, [filtered, promoRoots]); + // 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 @@ -284,9 +317,13 @@ export function CatalogClient({ {filtered.length ? (
- {filtered.map((product) => ( - - ))} + {gridItems.map((item, index) => + item.type === "product" ? ( + + ) : ( + + ), + )}
) : (
diff --git a/workloads/ecommerce/components/home/category-tiles.tsx b/workloads/ecommerce/components/home/category-tiles.tsx index d9e8fa4..cff1d07 100644 --- a/workloads/ecommerce/components/home/category-tiles.tsx +++ b/workloads/ecommerce/components/home/category-tiles.tsx @@ -2,6 +2,7 @@ import Image from "next/image"; import Link from "next/link"; import { ArrowUpRight } from "lucide-react"; import type { Category } from "@/lib/headless/types"; +import { cn } from "@/lib/utils"; type TileContent = { subtitle: string; image?: string; color: string }; @@ -20,6 +21,37 @@ const tileContent: Record = { const defaultTile: TileContent = { subtitle: "Explora esta categoría", color: "from-slate-400 to-slate-600" }; +// Tile individual reutilizado tanto por la seccion de mundos del home +// como por el grid del catalogo (tiles promocionales intercalados, +// ADR 0005 seccion 3, patron Walmart) -- mismo primitivo visual, no un +// componente nuevo. +export function PromoTile({ category, className = "" }: { category: Category; className?: string }) { + const content = tileContent[category.handle] ?? defaultTile; + + return ( + + {content.image && ( + <> + {category.name} +
+ + )} +
+
+

{category.name}

+

{content.subtitle}

+
+ + + +
+ + ); +} + export function CategoryTiles({ categories }: { categories: Category[] }) { const roots = categories.filter((category) => !category.parentId); @@ -35,33 +67,9 @@ export function CategoryTiles({ categories }: { categories: Category[] }) { Ver todo →
- {roots.map((category) => { - const content = tileContent[category.handle] ?? defaultTile; - - return ( - - {content.image && ( - <> - {category.name} -
- - )} -
-
-

{category.name}

-

{content.subtitle}

-
- - - -
- - ); - })} + {roots.map((category) => ( + + ))}
);