udpates on the ui changes on theprodut catalogs
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user