udpates on the ui changes on theprodut catalogs

This commit is contained in:
2026-06-16 20:12:45 +05:30
parent a0586dc11c
commit 2e9ffb15bb
22 changed files with 921 additions and 598 deletions

View File

@@ -92,7 +92,56 @@ export default function InventoryView({
const id = rowId(r);
if (id && !byId.has(id)) byId.set(id, r);
});
setProducts(Array.from(byId.values()).map(stockRowToProduct));
const initialProducts = Array.from(byId.values()).map(stockRowToProduct);
// Add 3 mock products with isNew: true
const mockProducts: ProductMatrixItem[] = [
{
id: 'mock-1',
name: 'Organic Honey 500g',
sku: 'GRO-HON-500G',
unitsSold: 0,
revenue: 0,
stockStatus: 'Healthy',
trend: 'flat',
image: 'https://images.unsplash.com/photo-1587049352847-4d4b1240c5f2?auto=format&fit=crop&q=80&w=200',
category: 'Groceries / Pantry',
exposure: 'All Outlets',
verified: true,
isNew: true
},
{
id: 'mock-2',
name: 'Premium Basmati Rice 5kg',
sku: 'STA-BAS-5KG',
unitsSold: 0,
revenue: 0,
stockStatus: 'Healthy',
trend: 'flat',
image: 'https://images.unsplash.com/photo-1586201375761-83865001e31c?auto=format&fit=crop&q=80&w=200',
category: 'Staples / Rice',
exposure: 'All Outlets',
verified: true,
isNew: true
},
{
id: 'mock-3',
name: 'Fresh Farm Eggs (Dozen)',
sku: 'FRE-EGG-12P',
unitsSold: 0,
revenue: 0,
stockStatus: 'Healthy',
trend: 'flat',
image: 'https://images.unsplash.com/photo-1587486913049-53fc88980cfc?auto=format&fit=crop&q=80&w=200',
category: 'Fresh Produce / Dairy',
exposure: 'All Outlets',
verified: true,
isNew: true
}
];
setProducts([...mockProducts, ...initialProducts]);
setSeeded(true);
}, [allStoreRows, seeded]);
@@ -217,7 +266,8 @@ export default function InventoryView({
image: newProduct.image,
category: newProduct.category,
exposure: 'All Outlets',
verified: true
verified: true,
isNew: true
};
setProducts([createdProd, ...products]);
@@ -270,7 +320,8 @@ export default function InventoryView({
image: 'https://images.unsplash.com/photo-1542838132-92c53300491e?auto=format&fit=crop&q=80&w=200',
category,
exposure: 'All Outlets',
verified: true
verified: true,
isNew: true
});
parsedCount++;
}
@@ -292,110 +343,52 @@ export default function InventoryView({
<div className="absolute top-10 left-1/4 w-96 h-96 bg-purple-400/5 rounded-full blur-[120px] pointer-events-none -z-10 animate-pulse" />
<div className="absolute top-40 right-1/4 w-[28rem] h-[28rem] bg-indigo-400/5 rounded-full blur-[140px] pointer-events-none -z-10 animate-pulse" style={{ animationDuration: '8s' }} />
{/* ── Immersive Analytics Banner (With Catalog Cover Image & Slate Gradient Overlay) ── */}
<div className="relative rounded-2xl p-6 md:p-8 text-white shadow-xl border border-purple-500/20 mb-8 animate-in fade-in duration-300 z-40">
{/* Cover Image Background & Decor */}
<div className="absolute inset-0 z-0 overflow-hidden rounded-2xl">
<img
src="https://images.unsplash.com/photo-1542838132-92c53300491e?auto=format&fit=crop&w=1200&q=80"
alt="Catalogue Command Center Banner"
className="w-full h-full object-cover object-center opacity-30"
/>
<div className="absolute inset-0 bg-gradient-to-r from-slate-950 via-slate-900/95 to-purple-950/85" />
{/* Background decorative glowing circles */}
<div className="absolute top-0 right-0 w-72 h-72 bg-purple-500/10 rounded-full blur-3xl -mr-20 -mt-20 pointer-events-none z-0" />
<div className="absolute bottom-0 left-0 w-56 h-56 bg-slate-500/5 rounded-full blur-2xl -ml-20 -mb-20 pointer-events-none z-0" />
</div>
{/* Content Row */}
<div className="relative z-20 flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h1 className="font-sans font-bold text-2xl tracking-tight text-white flex items-center gap-2">
<Layers size={24} className="text-purple-300" />
Product Catalogue
<span className="text-[10px] text-purple-200 font-bold bg-purple-900/60 border border-purple-500/30 px-2 py-0.5 rounded-full uppercase tracking-wider animate-pulse">
Global Sync
</span>
</h1>
<p className="text-purple-250 font-sans text-xs mt-1.5 font-medium max-w-2xl">
Master catalogue registry with regional assortment presets, brand styling studio, and live stock synchronization feeds.
</p>
</div>
{/* Navigation Tab pills styled like a modern control unit */}
<div className="flex items-center gap-1.5 bg-slate-950/60 backdrop-blur-md border border-white/10 p-1.5 rounded-xl shrink-0">
<button
onClick={() => setActiveTab('catalog')}
className={`px-4 py-2 rounded-lg text-xs font-bold transition-all duration-300 flex items-center gap-2 cursor-pointer ${
activeTab === 'catalog'
? 'bg-purple-900/50 text-purple-200 border border-purple-500/40 shadow-[0_0_15px_rgba(168,85,247,0.2)]'
: 'text-zinc-400 hover:text-white border border-transparent'
}`}
>
<Package size={13} />
<span>Catalogue & Stocks</span>
</button>
<button
onClick={() => setActiveTab('import_branding')}
className={`px-4 py-2 rounded-lg text-xs font-bold transition-all duration-300 flex items-center gap-2 cursor-pointer ${
activeTab === 'import_branding'
? 'bg-purple-900/50 text-purple-200 border border-purple-500/40 shadow-[0_0_15px_rgba(168,85,247,0.2)]'
: 'text-zinc-400 hover:text-white border border-transparent'
}`}
>
<UploadCloud size={13} />
<span>Import & Brand Studio</span>
</button>
</div>
</div>
{/* Small card metrics grid - 4 dynamic columns inside the banner */}
<div className="grid grid-cols-1 sm:grid-cols-4 gap-4 mt-8 pt-6 border-t border-slate-800/80 relative z-10">
{/* Header and Metrics */}
<div className="mb-8 z-40 relative">
{/* Small card metrics grid */}
<div className="grid grid-cols-1 sm:grid-cols-4 gap-4">
{/* Card 1: Total SKUs */}
<div className="bg-slate-900/40 backdrop-blur-sm rounded-xl p-4 border border-slate-800/80 hover:border-purple-500/30 transition-all group">
<div className="bg-white rounded-xl p-3 border border-slate-200 shadow-sm hover:border-purple-200 transition-all group">
<div className="flex justify-between items-start">
<span className="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Total SKUs</span>
<div className="p-2 rounded-lg bg-purple-500/10 text-purple-400 border border-purple-500/20 group-hover:scale-110 transition-transform">
<Package className="w-4 h-4" />
<span className="text-[10px] text-slate-500 uppercase tracking-widest font-bold">Total SKUs</span>
<div className="p-1.5 rounded-lg bg-purple-50 text-purple-600 group-hover:scale-110 transition-transform">
<Package className="w-3.5 h-3.5" />
</div>
</div>
<div className="mt-2">
<h3 className="text-xl font-extrabold tracking-tight font-mono">
<div className="mt-1">
<h3 className="text-lg font-extrabold tracking-tight font-mono text-slate-900">
{products.length}
</h3>
<p className="text-[10px] text-purple-400 font-semibold mt-1">Master catalogue</p>
<p className="text-[10px] text-slate-400 font-semibold mt-0.5">Master catalogue</p>
</div>
</div>
{/* Card 2: Synced Outlets */}
<div className="bg-slate-900/40 backdrop-blur-sm rounded-xl p-4 border border-slate-800/80 hover:border-purple-500/30 transition-all group">
<div className="bg-white rounded-xl p-3 border border-slate-200 shadow-sm hover:border-indigo-200 transition-all group">
<div className="flex justify-between items-start">
<span className="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Active Outlets</span>
<div className="p-2 rounded-lg bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 group-hover:scale-110 transition-transform">
<Layers className="w-4 h-4" />
<span className="text-[10px] text-slate-500 uppercase tracking-widest font-bold">Active Outlets</span>
<div className="p-1.5 rounded-lg bg-indigo-50 text-indigo-600 group-hover:scale-110 transition-transform">
<Layers className="w-3.5 h-3.5" />
</div>
</div>
<div className="mt-2">
<h3 className="text-xl font-extrabold tracking-tight font-mono">
<div className="mt-1">
<h3 className="text-lg font-extrabold tracking-tight font-mono text-slate-900">
{locations.length}
</h3>
<p className="text-[10px] text-indigo-400 font-semibold mt-1">Synced locations</p>
<p className="text-[10px] text-slate-400 font-semibold mt-0.5">Synced locations</p>
</div>
</div>
{/* Card 3: Total On-Hand Volume */}
<div className="bg-slate-900/40 backdrop-blur-sm rounded-xl p-4 border border-slate-800/80 hover:border-purple-500/30 transition-all group">
<div className="bg-white rounded-xl p-3 border border-slate-200 shadow-sm hover:border-emerald-200 transition-all group">
<div className="flex justify-between items-start">
<span className="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Total Stock</span>
<div className="p-2 rounded-lg bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 group-hover:scale-110 transition-transform">
<Check className="w-4 h-4" />
<span className="text-[10px] text-slate-500 uppercase tracking-widest font-bold">Total Stock</span>
<div className="p-1.5 rounded-lg bg-emerald-50 text-emerald-600 group-hover:scale-110 transition-transform">
<Check className="w-3.5 h-3.5" />
</div>
</div>
<div className="mt-2">
<h3 className="text-xl font-extrabold tracking-tight font-mono">
<div className="mt-1">
<h3 className="text-lg font-extrabold tracking-tight font-mono text-slate-900">
{storesStockWithOverrides.reduce((total, store) => {
return total + (store.rows || []).reduce((subTotal, r) => {
const inv = stockRowToInventory(r, store.locationname);
@@ -403,26 +396,25 @@ export default function InventoryView({
}, 0);
}, 0).toLocaleString('en-IN')}
</h3>
<p className="text-[10px] text-emerald-400 font-semibold mt-1">Units on hand</p>
<p className="text-[10px] text-slate-400 font-semibold mt-0.5">Units on hand</p>
</div>
</div>
{/* Card 4: Catalog Health */}
<div className="bg-slate-900/40 backdrop-blur-sm rounded-xl p-4 border border-slate-800/80 hover:border-purple-500/30 transition-all group">
<div className="bg-white rounded-xl p-3 border border-slate-200 shadow-sm hover:border-amber-200 transition-all group">
<div className="flex justify-between items-start">
<span className="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Catalogue Sync Ratio</span>
<div className="p-2 rounded-lg bg-amber-500/10 text-amber-400 border border-amber-500/20 group-hover:scale-110 transition-transform">
<ShieldCheck className="w-4 h-4" />
<span className="text-[10px] text-slate-500 uppercase tracking-widest font-bold">Catalogue Sync Ratio</span>
<div className="p-1.5 rounded-lg bg-amber-50 text-amber-600 group-hover:scale-110 transition-transform">
<ShieldCheck className="w-3.5 h-3.5" />
</div>
</div>
<div className="mt-2">
<h3 className="text-xl font-extrabold tracking-tight font-mono">
<div className="mt-1">
<h3 className="text-lg font-extrabold tracking-tight font-mono text-slate-900">
{products.length > 0 ? `${Math.round((products.filter(p => p.verified).length / products.length) * 100)}%` : '100%'}
</h3>
<p className="text-[10px] text-amber-400 font-semibold mt-1">Active Portfolio</p>
<p className="text-[10px] text-slate-400 font-semibold mt-0.5">Active Portfolio</p>
</div>
</div>
</div>
</div>
@@ -476,14 +468,14 @@ export default function InventoryView({
<div className="flex items-center gap-sm">
<button
onClick={() => setActiveTab('import_branding')}
className="bg-white text-[#581c87] border border-purple-200 px-4 py-2 rounded-lg text-xs font-bold uppercase tracking-wider flex items-center justify-center gap-xs cursor-pointer hover:bg-[#faf5ff] transition shadow-sm"
className="bg-white text-[#662582] border border-purple-200 px-4 py-2 rounded-lg text-xs font-bold uppercase tracking-wider flex items-center justify-center gap-xs cursor-pointer hover:bg-[#faf5ff] transition shadow-sm"
>
<UploadCloud size={14} />
Import SKUs
</button>
<button
onClick={() => setShowAddProductModal(true)}
className="bg-[#581c87] text-white px-xl py-2 rounded-lg text-xs font-bold uppercase tracking-wider flex items-center justify-center gap-xs cursor-pointer hover:bg-purple-800 transition shadow-sm"
className="bg-[#662582] text-white px-xl py-2 rounded-lg text-xs font-bold uppercase tracking-wider flex items-center justify-center gap-xs cursor-pointer hover:bg-purple-800 transition shadow-sm"
>
<Plus size={14} />
Add SKU
@@ -496,7 +488,7 @@ export default function InventoryView({
<div className="flex items-center justify-between mb-sm">
<div>
<h3 className="font-sans font-bold text-sm text-[#0f172a] flex items-center gap-1.5">
<Package size={15} className="text-[#581c87]" /> Global Catalogue Assortment
<Package size={15} className="text-[#662582]" /> Global Catalogue Assortment
</h3>
<p className="text-[10px] text-zinc-400 font-medium mt-0.5">Pick products & set quantities selected items appear in every store's catalogue.</p>
</div>
@@ -504,7 +496,7 @@ export default function InventoryView({
<span className="text-[10px] text-emerald-700 font-bold bg-emerald-50 px-2 py-0.5 rounded-lg border border-emerald-100">
{storeCat.items.length} in store catalogue
</span>
<span className="text-[10px] text-[#581c87] font-bold bg-purple-50 px-2 py-0.5 rounded-lg border border-purple-100/50">
<span className="text-[10px] text-[#662582] font-bold bg-purple-50 px-2 py-0.5 rounded-lg border border-purple-100/50">
{filteredProducts.length} item{filteredProducts.length === 1 ? '' : 's'} loaded
</span>
</div>
@@ -515,91 +507,202 @@ export default function InventoryView({
) : filteredProducts.length === 0 ? (
<div className="text-center py-xl text-zinc-400 text-xs font-bold">No catalogue products match your selection.</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-md">
{filteredProducts.map((prod) => (
<div key={prod.id} className="bg-white/80 backdrop-blur-md border border-[#e2e8f0] rounded-2xl p-md flex flex-col justify-between gap-sm shadow-sm hover:shadow-[0_12px_24px_rgba(99,102,241,0.06)] hover:border-purple-200 hover:-translate-y-0.5 transition-all duration-300 relative group">
<div className="flex gap-md">
{/* Image zoom effect on hover */}
<div className="w-16 h-16 rounded-xl border border-zinc-100 shrink-0 overflow-hidden bg-zinc-50 relative">
<img
src={prod.image}
alt={prod.name}
referrerPolicy="no-referrer"
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
/>
</div>
<div className="flex-1 space-y-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<h4 className="font-bold text-[#0f172a] leading-tight text-xs truncate group-hover:text-[#581c87] transition-colors">{prod.name}</h4>
<span className="text-[10px] text-zinc-400 font-bold font-mono tracking-tight">{prod.sku}</span>
<div className="flex flex-col xl:flex-row gap-6">
{/* Left Side: Normal Catalogue */}
<div className="flex-1 min-w-0">
<div className="grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-3 gap-md">
{filteredProducts.filter(p => !p.isNew).map((prod) => (
<div key={prod.id} className="bg-white/80 backdrop-blur-md border border-[#e2e8f0] rounded-2xl p-md flex flex-col justify-between gap-sm shadow-sm hover:shadow-[0_12px_24px_rgba(99,102,241,0.06)] hover:border-purple-200 hover:-translate-y-0.5 transition-all duration-300 relative group">
<div className="flex gap-md">
{/* Image zoom effect on hover */}
<div className="w-24 h-24 rounded-xl border border-zinc-100 shrink-0 overflow-hidden bg-zinc-50 relative">
<img
src={prod.image}
alt={prod.name}
referrerPolicy="no-referrer"
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
/>
</div>
{/* Categorized pill badge */}
<span className={`px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase shrink-0 ${
prod.category.startsWith('Staples') ? 'bg-amber-50 text-amber-600 border border-amber-100' :
prod.category.startsWith('Groceries') ? 'bg-emerald-50 text-emerald-600 border border-emerald-100' :
prod.category.startsWith('Beverages') ? 'bg-sky-50 text-sky-600 border border-sky-100' :
'bg-rose-50 text-rose-600 border border-rose-100'
<div className="flex-1 space-y-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex items-center gap-2">
<h4 className="font-bold text-[#0f172a] leading-tight text-xs truncate group-hover:text-[#662582] transition-colors">{prod.name}</h4>
</div>
<div>
<span className="text-[10px] text-zinc-400 font-bold font-mono tracking-tight">{prod.sku}</span>
</div>
{/* Categorized pill badge */}
<span className={`px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase shrink-0 ${
prod.category.startsWith('Staples') ? 'bg-amber-50 text-amber-600 border border-amber-100' :
prod.category.startsWith('Groceries') ? 'bg-emerald-50 text-emerald-600 border border-emerald-100' :
prod.category.startsWith('Beverages') ? 'bg-sky-50 text-sky-600 border border-sky-100' :
'bg-rose-50 text-rose-600 border border-rose-100'
}`}>
{prod.category.split(' / ')[0]}
</span>
</div>
<div className="flex justify-between items-center pt-2">
<div>
<span className="text-[8px] text-zinc-400 block uppercase tracking-wider font-extrabold">Units Sold</span>
<span className="font-extrabold text-zinc-755 font-mono text-xs">{prod.unitsSold.toLocaleString()}</span>
</div>
<div className="text-right">
<span className="text-[8px] text-zinc-400 block uppercase tracking-wider font-extrabold">Revenue</span>
<span className="font-black text-emerald-600 font-mono text-xs">₹{prod.revenue.toLocaleString()}</span>
</div>
</div>
</div>
</div>
{/* Exposure toggle row */}
<div className="flex justify-between items-center pt-2.5 border-t border-[#f1f5f9] mt-1 select-none">
<span className={`inline-flex items-center gap-1.5 text-[10px] font-bold tracking-tight ${
prod.verified ? 'text-emerald-600' : 'text-zinc-400'
}`}>
{prod.category.split(' / ')[0]}
<span className={`w-1.5 h-1.5 rounded-full ${prod.verified ? 'bg-emerald-500 animate-pulse' : 'bg-zinc-300'}`} />
{prod.verified ? 'Active Portfolio' : 'Under Inspection'}
</span>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={prod.verified}
onChange={() => handleToggleProductExposure(prod.id)}
className="sr-only peer"
/>
<div className="w-8 h-4.5 bg-slate-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-350 after:border after:rounded-full after:h-3.5 after:w-3.5 after:transition-all peer-checked:bg-emerald-500 shadow-inner"></div>
</label>
</div>
<div className="flex justify-between items-center pt-2">
<div>
<span className="text-[8px] text-zinc-400 block uppercase tracking-wider font-extrabold">Units Sold</span>
<span className="font-extrabold text-zinc-755 font-mono text-xs">{prod.unitsSold.toLocaleString()}</span>
{/* Store-catalogue curation: pick the product to show to store users */}
{storeCat.has(prod.id) ? (
<div className="flex items-center justify-between gap-2 pt-2.5 border-t border-[#f1f5f9] mt-1">
<span className="inline-flex items-center gap-1 text-[10px] font-bold text-emerald-600"><CheckCircle size={12} /> In Store Catalogue</span>
<button onClick={() => storeCat.remove(prod.id)} title="Remove from store catalogue" className="flex items-center gap-1 px-2 py-1 rounded-lg text-rose-500 hover:bg-rose-50 font-bold text-[10px] cursor-pointer transition-colors">
<X size={13} /> Remove
</button>
</div>
<div className="text-right">
<span className="text-[8px] text-zinc-400 block uppercase tracking-wider font-extrabold">Revenue</span>
<span className="font-black text-emerald-600 font-mono text-xs">₹{prod.revenue.toLocaleString()}</span>
</div>
</div>
) : (
<button
onClick={() => storeCat.add({ productid: prod.id, name: prod.name, image: prod.image, category: prod.category, sku: prod.sku, price: prod.unitsSold > 0 ? Math.round(prod.revenue / prod.unitsSold) : 0, unit: prod.exposure, qty: 1 })}
disabled={!prod.verified}
className={`w-full flex items-center justify-center gap-1.5 pt-2.5 mt-1 border-t border-[#f1f5f9] text-[11px] font-bold transition-colors ${
prod.verified
? 'text-[#662582] hover:text-purple-800 cursor-pointer'
: 'text-zinc-400 cursor-not-allowed opacity-70'
}`}
title={!prod.verified ? "Product must be Active to be added" : "Add to Store Catalogue"}
>
<Plus size={13} /> Add to Store Catalogue
</button>
)}
</div>
</div>
{/* Exposure toggle row */}
<div className="flex justify-between items-center pt-2.5 border-t border-[#f1f5f9] mt-1 select-none">
<span className={`inline-flex items-center gap-1.5 text-[10px] font-bold tracking-tight ${
prod.verified ? 'text-emerald-600' : 'text-zinc-400'
}`}>
<span className={`w-1.5 h-1.5 rounded-full ${prod.verified ? 'bg-emerald-500 animate-pulse' : 'bg-zinc-300'}`} />
{prod.verified ? 'Active Portfolio' : 'Under Inspection'}
</span>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={prod.verified}
onChange={() => handleToggleProductExposure(prod.id)}
className="sr-only peer"
/>
<div className="w-8 h-4.5 bg-slate-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-350 after:border after:rounded-full after:h-3.5 after:w-3.5 after:transition-all peer-checked:bg-emerald-500 shadow-inner"></div>
</label>
</div>
{/* Store-catalogue curation: pick the product + quantity to show to store users */}
{storeCat.has(prod.id) ? (
<div className="flex items-center justify-between gap-2 pt-2.5 border-t border-[#f1f5f9] mt-1">
<span className="inline-flex items-center gap-1 text-[10px] font-bold text-emerald-600"><CheckCircle size={12} /> In Store Catalogue</span>
<div className="flex items-center gap-1">
<button onClick={() => storeCat.setQty(prod.id, storeCat.getQty(prod.id) - 1)} className="w-6 h-6 rounded-lg border border-[#e2e8f0] text-zinc-500 hover:bg-zinc-50 font-bold cursor-pointer leading-none"></button>
<span className="w-8 text-center font-mono font-bold text-xs text-[#0f172a]">{storeCat.getQty(prod.id)}</span>
<button onClick={() => storeCat.setQty(prod.id, storeCat.getQty(prod.id) + 1)} className="w-6 h-6 rounded-lg border border-[#e2e8f0] text-zinc-500 hover:bg-zinc-50 font-bold cursor-pointer leading-none">+</button>
<button onClick={() => storeCat.remove(prod.id)} title="Remove from store catalogue" className="ml-1 w-6 h-6 rounded-lg text-rose-500 hover:bg-rose-50 flex items-center justify-center cursor-pointer"><X size={13} /></button>
</div>
</div>
) : (
<button
onClick={() => storeCat.add({ productid: prod.id, name: prod.name, image: prod.image, category: prod.category, sku: prod.sku, price: prod.unitsSold > 0 ? Math.round(prod.revenue / prod.unitsSold) : 0, unit: prod.exposure, qty: 1 })}
className="w-full flex items-center justify-center gap-1.5 pt-2.5 mt-1 border-t border-[#f1f5f9] text-[11px] font-bold text-[#581c87] hover:text-purple-800 cursor-pointer"
>
<Plus size={13} /> Add to Store Catalogue
</button>
)}
))}
</div>
))}
</div>
{/* Right Side: Newly Added Items */}
{filteredProducts.filter(p => p.isNew).length > 0 && (
<div className="w-full xl:w-96 shrink-0 xl:border-l xl:border-purple-100/50 xl:pl-6 relative">
<div className="bg-[#f8fafc] px-2 flex items-center gap-2 mb-4 xl:-ml-2">
<Sparkles size={14} className="text-purple-600" />
<h4 className="text-xs font-bold text-purple-900 uppercase tracking-widest">Newly Added</h4>
</div>
<div className="flex flex-col gap-md">
{filteredProducts.filter(p => p.isNew).map((prod) => (
<div key={prod.id} className="bg-white/80 backdrop-blur-md border border-[#e2e8f0] rounded-2xl p-md flex flex-col justify-between gap-sm shadow-sm hover:shadow-[0_12px_24px_rgba(99,102,241,0.06)] hover:border-purple-200 hover:-translate-y-0.5 transition-all duration-300 relative group">
<div className="flex gap-md">
{/* Image zoom effect on hover */}
<div className="w-24 h-24 rounded-xl border border-zinc-100 shrink-0 overflow-hidden bg-zinc-50 relative">
<img
src={prod.image}
alt={prod.name}
referrerPolicy="no-referrer"
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
/>
</div>
<div className="flex-1 space-y-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex items-center gap-2">
<h4 className="font-bold text-[#0f172a] leading-tight text-xs truncate group-hover:text-[#662582] transition-colors">{prod.name}</h4>
</div>
<div>
<span className="text-[10px] text-zinc-400 font-bold font-mono tracking-tight">{prod.sku}</span>
</div>
{/* Categorized pill badge */}
<span className={`px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase shrink-0 ${
prod.category.startsWith('Staples') ? 'bg-amber-50 text-amber-600 border border-amber-100' :
prod.category.startsWith('Groceries') ? 'bg-emerald-50 text-emerald-600 border border-emerald-100' :
prod.category.startsWith('Beverages') ? 'bg-sky-50 text-sky-600 border border-sky-100' :
'bg-rose-50 text-rose-600 border border-rose-100'
}`}>
{prod.category.split(' / ')[0]}
</span>
</div>
<div className="flex justify-between items-center pt-2">
<div>
<span className="text-[8px] text-zinc-400 block uppercase tracking-wider font-extrabold">Units Sold</span>
<span className="font-extrabold text-zinc-755 font-mono text-xs">{prod.unitsSold.toLocaleString()}</span>
</div>
<div className="text-right">
<span className="text-[8px] text-zinc-400 block uppercase tracking-wider font-extrabold">Revenue</span>
<span className="font-black text-emerald-600 font-mono text-xs">₹{prod.revenue.toLocaleString()}</span>
</div>
</div>
</div>
</div>
{/* Exposure toggle row */}
<div className="flex justify-between items-center pt-2.5 border-t border-[#f1f5f9] mt-1 select-none">
<span className={`inline-flex items-center gap-1.5 text-[10px] font-bold tracking-tight ${
prod.verified ? 'text-emerald-600' : 'text-zinc-400'
}`}>
<span className={`w-1.5 h-1.5 rounded-full ${prod.verified ? 'bg-emerald-500 animate-pulse' : 'bg-zinc-300'}`} />
{prod.verified ? 'Active Portfolio' : 'Under Inspection'}
</span>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={prod.verified}
onChange={() => handleToggleProductExposure(prod.id)}
className="sr-only peer"
/>
<div className="w-8 h-4.5 bg-slate-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-350 after:border after:rounded-full after:h-3.5 after:w-3.5 after:transition-all peer-checked:bg-emerald-500 shadow-inner"></div>
</label>
</div>
{/* Store-catalogue curation: pick the product to show to store users */}
{storeCat.has(prod.id) ? (
<div className="flex items-center justify-between gap-2 pt-2.5 border-t border-[#f1f5f9] mt-1">
<span className="inline-flex items-center gap-1 text-[10px] font-bold text-emerald-600"><CheckCircle size={12} /> In Store Catalogue</span>
<button onClick={() => storeCat.remove(prod.id)} title="Remove from store catalogue" className="flex items-center gap-1 px-2 py-1 rounded-lg text-rose-500 hover:bg-rose-50 font-bold text-[10px] cursor-pointer transition-colors">
<X size={13} /> Remove
</button>
</div>
) : (
<button
onClick={() => storeCat.add({ productid: prod.id, name: prod.name, image: prod.image, category: prod.category, sku: prod.sku, price: prod.unitsSold > 0 ? Math.round(prod.revenue / prod.unitsSold) : 0, unit: prod.exposure, qty: 1 })}
disabled={!prod.verified}
className={`w-full flex items-center justify-center gap-1.5 pt-2.5 mt-1 border-t border-[#f1f5f9] text-[11px] font-bold transition-colors ${
prod.verified
? 'text-[#662582] hover:text-purple-800 cursor-pointer'
: 'text-zinc-400 cursor-not-allowed opacity-70'
}`}
title={!prod.verified ? "Product must be Active to be added" : "Add to Store Catalogue"}
>
<Plus size={13} /> Add to Store Catalogue
</button>
)}
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
@@ -916,7 +1019,7 @@ export default function InventoryView({
{/* Custom CSV Parsing Box */}
<div className="bg-white/80 backdrop-blur-md border border-[#e2e8f0] p-md rounded-2xl shadow-sm space-y-md">
<div className="flex items-center gap-xs text-[#0f172a] font-bold text-sm">
<FileSpreadsheet className="text-[#581c87]" size={18} />
<FileSpreadsheet className="text-[#662582]" size={18} />
<h3>Manual CSV Direct-Entry Console</h3>
</div>
@@ -930,7 +1033,7 @@ export default function InventoryView({
<textarea
value={csvText}
onChange={(e) => setCsvText(e.target.value)}
className="w-full h-28 p-sm font-mono text-[11px] bg-slate-50/50 outline-none focus:bg-white focus:ring-1 focus:ring-[#581c87] leading-relaxed border-none"
className="w-full h-28 p-sm font-mono text-[11px] bg-slate-50/50 outline-none focus:bg-white focus:ring-1 focus:ring-[#662582] leading-relaxed border-none"
/>
</div>
@@ -938,7 +1041,7 @@ export default function InventoryView({
<span className="text-[9px] text-zinc-400 font-extrabold uppercase">Header row ignored on parse</span>
<button
onClick={handleCSVImport}
className="bg-[#581c87] text-white px-xl py-2 rounded-lg text-xs font-bold uppercase tracking-wider cursor-pointer hover:bg-purple-800 transition shadow-sm"
className="bg-[#662582] text-white px-xl py-2 rounded-lg text-xs font-bold uppercase tracking-wider cursor-pointer hover:bg-purple-800 transition shadow-sm"
>
Parse CSV Data & Sync
</button>
@@ -966,7 +1069,7 @@ export default function InventoryView({
<div className="bg-white/80 backdrop-blur-md border border-[#e2e8f0] p-md rounded-2xl shadow-sm space-y-md">
<div className="flex items-center gap-xs text-[#0f172a] font-bold text-sm">
<Palette className="text-[#581c87]" size={18} />
<Palette className="text-[#662582]" size={18} />
<h3>Packaging Branding Studio</h3>
</div>
@@ -987,7 +1090,7 @@ export default function InventoryView({
<div className="bg-white border border-[#e2e8f0] rounded-xl w-full max-w-[28rem] max-h-[90vh] flex flex-col shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200 text-xs font-sans cursor-default">
<div className="p-md border-b border-[#e2e8f0] bg-[#f8fafc] flex justify-between items-center shrink-0">
<h4 className="font-bold text-[#0f172a] flex items-center gap-xs">
<Package size={15} className="text-[#581c87]" />
<Package size={15} className="text-[#662582]" />
Introduce New Grocery Catalogue SKU
</h4>
<button