186 lines
7.6 KiB
TypeScript
186 lines
7.6 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import Image from 'next/image';
|
|
import { useState } from 'react';
|
|
import { Check, ShoppingCart } from 'lucide-react';
|
|
import BlurHashImage from '@/components/ui/BlurHashImage';
|
|
import { ProductResponse } from '@/services/api/catalogService';
|
|
import { useCartStore } from '@/store/cartStore';
|
|
import { formatCatalogPriceLabel, formatCurrency, getImageUrl } from '@/lib/utils';
|
|
|
|
interface QuickComparisonTableProps {
|
|
products: ProductResponse[];
|
|
}
|
|
|
|
function priceLabel(product: ProductResponse): string {
|
|
const label = formatCatalogPriceLabel(product);
|
|
return label === formatCurrency(0) ? '—' : label;
|
|
}
|
|
|
|
function productImage(product: ProductResponse): string {
|
|
return getImageUrl(product.images?.[0]?.image_url || product.variants?.[0]?.images?.[0]?.image_url);
|
|
}
|
|
|
|
function extraAttributes(product: ProductResponse): { label: string; values: string[] }[] {
|
|
const buckets: Record<string, { label: string; values: Set<string> }> = {};
|
|
|
|
(product.variants || []).forEach((variant) => {
|
|
(variant.attributes || []).forEach((attr) => {
|
|
const raw = attr.attribute_name || attr.attribute_code || '';
|
|
const value = String(attr.attribute_value || '').trim();
|
|
if (!raw || !value) return;
|
|
const key = raw.trim().toLowerCase().replace(/[\s-]+/g, '_');
|
|
if (!buckets[key]) {
|
|
let label = raw.replace(/[_-]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
if (['qul', 'qual'].includes(key)) label = 'Quality';
|
|
else if (['clr'].includes(key)) label = 'Color';
|
|
else if (['str'].includes(key)) label = 'Storage';
|
|
buckets[key] = {
|
|
label,
|
|
values: new Set(),
|
|
};
|
|
}
|
|
buckets[key].values.add(value);
|
|
});
|
|
});
|
|
|
|
return Object.values(buckets)
|
|
.map((item) => ({
|
|
label: item.label,
|
|
values: Array.from(item.values),
|
|
}))
|
|
.slice(0, 3);
|
|
}
|
|
|
|
function ctaKind(product: ProductResponse): 'view' | 'cart' {
|
|
return (product.variants?.length || 0) > 1 ? 'view' : 'cart';
|
|
}
|
|
|
|
export function QuickComparisonTable({ products }: QuickComparisonTableProps) {
|
|
const addToCart = useCartStore((state) => state.addToCart);
|
|
const [addedId, setAddedId] = useState<string | null>(null);
|
|
|
|
if (!products.length) return null;
|
|
|
|
const handleAdd = async (product: ProductResponse) => {
|
|
const variant = product.variants?.[0];
|
|
if (!variant) return;
|
|
await addToCart(product, variant, 1);
|
|
setAddedId(product.product_id);
|
|
setTimeout(() => setAddedId(null), 1800);
|
|
};
|
|
|
|
const labelCls =
|
|
'sticky left-0 z-10 w-[150px] min-w-[150px] px-4 py-4 text-[13px] font-medium text-gray-500 text-left align-middle bg-[#f3f4f6]';
|
|
const cellCls = 'px-5 py-4 min-w-[170px] align-middle text-center';
|
|
|
|
return (
|
|
<section className="mb-10">
|
|
<h3 className="text-[20px] font-semibold text-gray-800 mb-4 tracking-tight">Quick Comparison</h3>
|
|
<div className="overflow-x-auto rounded-md border border-gray-200">
|
|
<table className="w-full border-collapse min-w-[860px]">
|
|
<thead>
|
|
<tr className="bg-white">
|
|
<th className={labelCls} />
|
|
{products.map((prod) => {
|
|
const img = productImage(prod);
|
|
return (
|
|
<th key={prod.product_id} className={`${cellCls} font-normal bg-white`}>
|
|
<Link
|
|
href={`/products/${prod.slug}`}
|
|
className="block text-[13px] font-medium text-gray-700 leading-snug hover:text-primary line-clamp-2 min-h-[36px] mb-3"
|
|
>
|
|
{prod.name}
|
|
</Link>
|
|
<Link
|
|
href={`/products/${prod.slug}`}
|
|
className="w-[92px] h-[92px] mx-auto bg-white border border-gray-200 rounded-md p-2 flex items-center justify-center hover:border-primary/40 transition-colors"
|
|
>
|
|
<BlurHashImage
|
|
src={img}
|
|
alt={prod.name}
|
|
width={84}
|
|
height={84}
|
|
className="max-h-full max-w-full object-contain"
|
|
/>
|
|
</Link>
|
|
</th>
|
|
);
|
|
})}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr className="bg-[#f8f8f8]">
|
|
<td className={labelCls}>Price</td>
|
|
{products.map((prod) => (
|
|
<td key={prod.product_id} className={`${cellCls} text-[14px] font-bold text-gray-900`}>
|
|
{priceLabel(prod)}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
|
|
<tr className="bg-white">
|
|
<td className={labelCls}>Add to cart</td>
|
|
{products.map((prod) => {
|
|
const kind = ctaKind(prod);
|
|
const justAdded = addedId === prod.product_id;
|
|
return (
|
|
<td key={prod.product_id} className={cellCls}>
|
|
{kind === 'view' ? (
|
|
<Link
|
|
href={`/products/${prod.slug}`}
|
|
className="inline-flex items-center justify-center min-w-[120px] bg-primary hover:bg-primary-hover text-white text-[13px] font-semibold px-4 py-2.5 rounded-md transition-colors"
|
|
>
|
|
View Products
|
|
</Link>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() => void handleAdd(prod)}
|
|
className="inline-flex items-center justify-center gap-1.5 min-w-[120px] bg-primary hover:bg-primary-hover text-white text-[13px] font-semibold px-4 py-2.5 rounded-md transition-colors cursor-pointer"
|
|
>
|
|
{justAdded ? <Check size={14} /> : <ShoppingCart size={14} />}
|
|
{justAdded ? 'Added' : 'Add To Cart'}
|
|
</button>
|
|
)}
|
|
</td>
|
|
);
|
|
})}
|
|
</tr>
|
|
|
|
<tr className="bg-[#f8f8f8]">
|
|
<td className={`${labelCls} align-top`}>Additional information</td>
|
|
{products.map((prod) => {
|
|
const attrs = extraAttributes(prod);
|
|
return (
|
|
<td key={prod.product_id} className={`${cellCls} align-top text-[12px] leading-relaxed`}>
|
|
{attrs.length === 0 ? (
|
|
<span className="text-gray-300">—</span>
|
|
) : (
|
|
<div className="space-y-1.5 text-left inline-block">
|
|
{attrs.map((attr) => (
|
|
<div key={attr.label}>
|
|
<span className="text-gray-700 font-medium">{attr.label}: </span>
|
|
{attr.values.map((value, i) => (
|
|
<span key={value}>
|
|
<Link href={`/products/${prod.slug}`} className="text-primary hover:underline">
|
|
{value}
|
|
</Link>
|
|
{i < attr.values.length - 1 ? <span className="text-gray-400">, </span> : null}
|
|
</span>
|
|
))}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</td>
|
|
);
|
|
})}
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|