39 lines
5.5 KiB
TypeScript
39 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import { SlidersHorizontal, X } from "lucide-react";
|
|
import { useMemo, useState } from "react";
|
|
import { ProductCard } from "@/components/product/product-card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import type { Product } from "@/lib/headless/types";
|
|
|
|
export function CatalogClient({ products, initialCategory = "Todos", initialCollection = "", initialQuery = "" }: { products: Product[]; initialCategory?: string; initialCollection?: string; initialQuery?: string }) {
|
|
const [query, setQuery] = useState(initialQuery);
|
|
const [category, setCategory] = useState(initialCategory);
|
|
const [collection, setCollection] = useState(initialCollection);
|
|
const [brand, setBrand] = useState("Todas");
|
|
const [age, setAge] = useState("Todas");
|
|
const [sort, setSort] = useState("featured");
|
|
const categories = ["Todos", ...new Set(products.map((product) => product.category))];
|
|
const brands = ["Todas", ...new Set(products.map((product) => product.brand))];
|
|
const ages = ["Todas", ...new Set(products.map((product) => product.ageRange))];
|
|
const pricedProducts = products.filter((product) => product.price !== null);
|
|
const maxCatalogPrice = pricedProducts.length ? Math.max(...pricedProducts.map((product) => product.price ?? 0)) : 0;
|
|
const [maxPrice, setMaxPrice] = useState(maxCatalogPrice);
|
|
|
|
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" || product.category === category) && (!collection || product.collection === collection) && (brand === "Todas" || product.brand === brand) && (age === "Todas" || product.ageRange === age) && (product.price === null || maxPrice === 0 || product.price <= maxPrice);
|
|
}).sort((a, b) => sort === "name" ? a.name.localeCompare(b.name, "es") : sort === "new" ? Number(b.isNew) - Number(a.isNew) : Number(b.featured) - Number(a.featured)), [age, brand, category, products, query, sort]);
|
|
|
|
const reset = () => { setQuery(""); setCategory("Todos"); setCollection(""); setBrand("Todas"); setAge("Todas"); setMaxPrice(maxCatalogPrice); setSort("featured"); };
|
|
|
|
return <div className="mx-auto max-w-7xl px-4 py-10"><div className="mb-8"><span className="text-xs font-black uppercase tracking-[.18em] text-fuchsia-600">Catálogo completo</span><h1 className="mt-2 text-5xl font-black tracking-tight">Todos los tesoros</h1><p className="mt-3 text-slate-500">Filtra por categoría, marca o edad sin recargar la página.</p></div><div className="grid gap-8 lg:grid-cols-[260px_1fr]"><aside className="h-fit space-y-6 rounded-3xl border border-slate-100 bg-white p-6 shadow-lg shadow-slate-900/5 lg:sticky lg:top-44"><div className="flex items-center justify-between"><strong className="flex items-center gap-2 text-lg font-black"><SlidersHorizontal className="size-5 text-cyan-600" />Filtros</strong><button onClick={reset} className="text-xs font-black text-fuchsia-600"><X className="mr-1 inline size-3" />Limpiar</button></div><Input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Buscar…" /><Filter title="Categoría" values={categories} selected={category} onChange={setCategory} /><Filter title="Marca" values={brands} selected={brand} onChange={setBrand} /><Filter title="Edad" values={ages} selected={age} onChange={setAge} />
|
|
<fieldset><legend className="mb-3 text-sm font-black">Precio máximo</legend>{maxCatalogPrice > 0 ? <><input aria-label="Precio máximo" type="range" min={0} max={maxCatalogPrice} step={5000} value={maxPrice} onChange={(event) => setMaxPrice(Number(event.target.value))} className="w-full accent-fuchsia-500" /><p className="mt-2 text-xs font-bold text-slate-500">Hasta ${maxPrice.toLocaleString("es-CO")}</p></> : <p className="rounded-2xl bg-yellow-50 p-3 text-xs font-bold leading-5 text-yellow-800">El filtro se activa automáticamente cuando el backend entregue precios.</p>}</fieldset>
|
|
</aside><section><div className="mb-5 flex items-center justify-between gap-4"><p className="text-sm font-bold text-slate-500">{filtered.length} productos</p><select value={sort} onChange={(event) => setSort(event.target.value)} className="h-11 rounded-full border border-slate-200 bg-white px-4 text-sm font-bold"><option value="featured">Destacados</option><option value="new">Novedades</option><option value="name">Nombre</option></select></div>{filtered.length ? <div className="grid gap-5 sm:grid-cols-2 xl:grid-cols-3">{filtered.map((product) => <ProductCard key={product.id} product={product} />)}</div> : <div className="grid min-h-80 place-items-center rounded-3xl border border-dashed border-slate-300 text-center"><div><p className="text-xl font-black">No encontramos productos</p><Button variant="magenta" className="mt-4" onClick={reset}>Restablecer filtros</Button></div></div>}</section></div></div>;
|
|
}
|
|
|
|
function Filter({ title, values, selected, onChange }: { title: string; values: string[]; selected: string; onChange: (value: string) => void }) {
|
|
return <fieldset><legend className="mb-3 text-sm font-black">{title}</legend><div className="flex flex-wrap gap-2">{values.map((value) => <button type="button" key={value} onClick={() => onChange(value)} className={`rounded-full px-3 py-2 text-xs font-bold transition ${selected === value ? "bg-slate-950 text-white" : "bg-slate-100 text-slate-600 hover:bg-cyan-100"}`}>{value}</button>)}</div></fieldset>;
|
|
}
|