ifixkart-storefront/components/pdp/QuickComparisonTable.tsx

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>
);
}