Update src/components/ecommerce/productCatalog/ProductCatalog.tsx

This commit is contained in:
2026-03-09 08:22:21 +00:00
parent 18b7ff7e31
commit 63cc4bdeff

View File

@@ -1,156 +1,68 @@
"use client";
import React, { useState, useEffect } from "react";
import { Product } from "@/lib/api/product";
import { memo, useMemo, useCallback } from "react";
import { useRouter } from "next/navigation";
import Input from "@/components/form/Input";
import ProductDetailVariantSelect from "@/components/ecommerce/productDetail/ProductDetailVariantSelect";
import type { ProductVariant } from "@/components/ecommerce/productDetail/ProductDetailCard";
import { cls } from "@/lib/utils";
import { useProducts } from "@/hooks/useProducts";
import ProductCatalogItem from "./ProductCatalogItem";
import type { CatalogProduct } from "./ProductCatalogItem";
interface ProductCatalogProps {
layout: "page" | "section";
products?: CatalogProduct[];
searchValue?: string;
onSearchChange?: (value: string) => void;
searchPlaceholder?: string;
filters?: ProductVariant[];
emptyMessage?: string;
className?: string;
gridClassName?: string;
cardClassName?: string;
imageClassName?: string;
searchClassName?: string;
filterClassName?: string;
toolbarClassName?: string;
interface CatalogProduct {
id: string;
name: string;
price: string;
imageSrc: string;
imageAlt?: string;
rating?: number;
reviewCount?: string;
category?: string;
onProductClick?: () => void;
}
const ProductCatalog = ({
layout,
products: productsProp,
searchValue = "",
onSearchChange,
searchPlaceholder = "Search products...",
filters,
emptyMessage = "No products found",
className = "",
gridClassName = "",
cardClassName = "",
imageClassName = "",
searchClassName = "",
filterClassName = "",
toolbarClassName = "",
}: ProductCatalogProps) => {
const router = useRouter();
const { products: fetchedProducts, isLoading } = useProducts();
interface ProductCatalogProps {
products?: Product[];
loading?: boolean;
error?: string;
}
const handleProductClick = useCallback((productId: string) => {
router.push(`/shop/${productId}`);
}, [router]);
export const ProductCatalog: React.FC<ProductCatalogProps> = ({
products = [],
loading = false,
error = ""}) => {
const [catalogProducts, setCatalogProducts] = useState<CatalogProduct[]>([]);
const products: CatalogProduct[] = useMemo(() => {
if (productsProp && productsProp.length > 0) {
return productsProp;
}
if (fetchedProducts.length === 0) {
return [];
}
return fetchedProducts.map((product) => ({
id: product.id,
name: product.name,
price: product.price,
imageSrc: product.imageSrc,
imageAlt: product.imageAlt || product.name,
rating: product.rating || 0,
reviewCount: product.reviewCount,
category: product.brand,
onProductClick: () => handleProductClick(product.id),
}));
}, [productsProp, fetchedProducts, handleProductClick]);
if (isLoading && (!productsProp || productsProp.length === 0)) {
return (
<section
className={cls(
"relative w-content-width mx-auto",
layout === "page" ? "pt-hero-page-padding pb-20" : "py-20",
className
)}
>
<p className="text-sm text-foreground/50 text-center py-20">
Loading products...
</p>
</section>
);
useEffect(() => {
if (!loading) {
const transformed = products.map((product) => ({
id: product.id,
name: product.name,
price: String(product.price),
imageSrc: product.imageSrc || "/placeholder.jpg", imageAlt: product.imageAlt || product.name,
rating: product.rating,
reviewCount: product.reviewCount,
category: product.category,
brand: product.brand,
onProductClick: () => {},
}));
setCatalogProducts(transformed);
}
}, [products, loading]);
return (
<section
className={cls(
"relative w-content-width mx-auto",
layout === "page" ? "pt-hero-page-padding pb-20" : "py-20",
className
)}
>
{(onSearchChange || (filters && filters.length > 0)) && (
<div
className={cls(
"flex flex-col md:flex-row gap-4 md:items-end mb-6",
toolbarClassName
)}
>
{onSearchChange && (
<Input
value={searchValue}
onChange={onSearchChange}
placeholder={searchPlaceholder}
ariaLabel={searchPlaceholder}
className={cls("flex-1 w-full h-9 text-sm", searchClassName)}
/>
)}
{filters && filters.length > 0 && (
<div className="flex gap-4 items-end">
{filters.map((filter) => (
<ProductDetailVariantSelect
key={filter.label}
variant={filter}
selectClassName={filterClassName}
/>
))}
</div>
)}
</div>
)}
if (error) {
return <div className="error">Error: {error}</div>;
}
{products.length === 0 ? (
<p className="text-sm text-foreground/50 text-center py-20">
{emptyMessage}
</p>
) : (
<div
className={cls(
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6",
gridClassName
)}
>
{products.map((product) => (
<ProductCatalogItem
key={product.id}
product={product}
className={cardClassName}
imageClassName={imageClassName}
/>
))}
</div>
)}
</section>
);
if (loading) {
return <div className="loading">Loading...</div>;
}
return (
<div className="product-catalog">
<div className="product-grid">
{catalogProducts.map((product) => (
<div key={product.id} className="product-item">
<img src={product.imageSrc} alt={product.imageAlt} />
<h3>{product.name}</h3>
<p className="price">${product.price}</p>
{product.rating && <div className="rating">{product.rating} stars</div>}
{product.reviewCount && <div className="reviews">{product.reviewCount} reviews</div>}
</div>
))}
</div>
</div>
);
};
ProductCatalog.displayName = "ProductCatalog";
export default memo(ProductCatalog);