new changes

This commit is contained in:
Gokul
2026-06-04 11:40:06 +05:30
parent 6eaeb5c4a7
commit a11a859761
10 changed files with 678 additions and 801 deletions

View File

@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useMemo } from 'react';
import {
Layers,
Search,
@@ -18,6 +18,7 @@ import {
TrendingDown,
Trash2,
PackageCheck,
ShieldCheck,
Zap,
Tag,
UploadCloud,
@@ -27,12 +28,15 @@ import {
Info,
X
} from 'lucide-react';
import { ProductMatrixItem, InventoryItem, ImportLog } from '../types';
import { ProductMatrixItem, ImportLog } from '../types';
import { initialImportLogs } from '../data';
import { useFiestaStockStatement, useFiestaTenantLocations } from '../services/fiestaQueries';
import { FIESTA_TENANT_ID, FIESTA_PRIMARY_LOCATION_ID, str as fstr } from '../services/fiestaApi';
import { useFiestaTenantLocations, useFiestaStoresStock } from '../services/fiestaQueries';
import { FIESTA_TENANT_ID, str as fstr } from '../services/fiestaApi';
import { stockRowToProduct, stockRowToInventory } from '../services/fiestaMappers';
type StockRow = Record<string, unknown>;
const rowId = (r: StockRow) => String(r.productid ?? '') || String(r.productname ?? '');
interface InventoryViewProps {
searchQuery: string;
isCoimbatoreView: boolean;
@@ -42,42 +46,51 @@ export default function InventoryView({
searchQuery,
isCoimbatoreView
}: InventoryViewProps) {
// ── Live stock data (Fiesta) ─────────────────────────────────────────────
// The catalog grid and the hub-balance ledger are both derived from the live
// stock statement for the tenant's primary outlet. We seed local state from
// it once it loads so the existing add / CSV / replenish interactions keep
// mutating in-session without losing the live baseline.
// ── Live stock across every outlet (Fiesta) ───────────────────────────────
// This page is the admin's command surface. The GLOBAL CATALOG is the deduped
// union of products across all outlets the tenant owns (admin-only import adds
// to it); the STORE STOCK section shows each outlet's live stock so the admin
// can see all the stores under them at a glance.
const locationsQ = useFiestaTenantLocations(FIESTA_TENANT_ID);
const primaryLocation =
(locationsQ.data ?? []).find((l) => Number(l.locationid) === FIESTA_PRIMARY_LOCATION_ID) ||
(locationsQ.data ?? [])[0];
const locationId = primaryLocation ? Number(primaryLocation.locationid) : FIESTA_PRIMARY_LOCATION_ID;
const locationName = fstr(primaryLocation?.locationname) || 'Primary Outlet';
const locations = useMemo(
() =>
(locationsQ.data ?? []).map((l) => ({
locationid: Number(l.locationid),
locationname: fstr(l.locationname) || `Outlet ${fstr(l.locationid)}`,
status: fstr(l.status) || 'Active',
})),
[locationsQ.data],
);
const stockQ = useFiestaStockStatement({
tenantid: FIESTA_TENANT_ID,
locationid: locationId,
keyword: '',
pageno: 1,
pagesize: 100,
});
const storesStock = useFiestaStoresStock(
FIESTA_TENANT_ID,
locations.map(({ locationid, locationname }) => ({ locationid, locationname })),
);
const storesLoading = locationsQ.isLoading || storesStock.some((s) => s.isLoading);
const storesError =
locationsQ.isError || (storesStock.length > 0 && storesStock.every((s) => s.isError));
// Global catalog = deduped union of every outlet's products, plus anything the
// admin adds/imports in-session. Seeded once from the live data.
const [products, setProducts] = useState<ProductMatrixItem[]>([]);
const [inventory, setInventory] = useState<InventoryItem[]>([]);
const [importLogs, setImportLogs] = useState<ImportLog[]>(initialImportLogs);
const [seeded, setSeeded] = useState(false);
const allStoreRows = storesStock.flatMap((s) => s.rows);
useEffect(() => {
if (stockQ.data) {
setProducts(stockQ.data.map(stockRowToProduct));
setInventory(stockQ.data.map((r) => stockRowToInventory(r, locationName)));
}
// locationName is derived from the same query chain; safe to depend on data.
}, [stockQ.data, locationName]);
if (seeded || allStoreRows.length === 0) return;
const byId = new Map<string, StockRow>();
allStoreRows.forEach((r) => {
const id = rowId(r);
if (id && !byId.has(id)) byId.set(id, r);
});
setProducts(Array.from(byId.values()).map(stockRowToProduct));
setSeeded(true);
}, [allStoreRows, seeded]);
const [activeTab, setActiveTab] = useState<'catalog' | 'import_branding'>('catalog');
const [selectedCategory, setSelectedCategory] = useState<string>('ALL');
const [showAddProductModal, setShowAddProductModal] = useState(false);
const [replenishmentList, setReplenishmentList] = useState<string[]>([]);
// CSV Textarea input
const [csvText, setCsvText] = useState(
@@ -109,29 +122,7 @@ export default function InventoryView({
products.forEach((p) => categorySet.add(p.category));
const categories: string[] = ['ALL', ...Array.from(categorySet)];
// Handle SKU quantity change
const handleUpdateStock = (sku: string, delta: number) => {
setInventory(prev => prev.map(item => {
if (item.sku === sku) {
const newLevel = Math.max(0, item.stockLevel + delta);
const status = newLevel < 25 ? 'Critical' : newLevel < 120 ? 'Low Stock' : 'Optimal';
return { ...item, stockLevel: newLevel, status };
}
return item;
}));
};
// Trigger quick reorder recommendation
const handleReplenishSku = (sku: string) => {
if (replenishmentList.includes(sku)) return;
setReplenishmentList(prev => [...prev, sku]);
handleUpdateStock(sku, 500); // Add 500 units to stock
setTimeout(() => {
alert(`Auto-Replenish complete! 500 units ordered and allocated directly to corresponding hub for SKU ${sku}`);
}, 100);
};
// Filter criteria
// Filter criteria
const filteredProducts = products.filter(p => {
const matchesSearch = p.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
p.sku.toLowerCase().includes(searchQuery.toLowerCase()) ||
@@ -161,20 +152,9 @@ export default function InventoryView({
verified: true
};
const createdInv: InventoryItem = {
sku: newProduct.sku,
name: newProduct.name,
warehouse: 'RS Puram Hub (CBE-01)',
stockLevel: newProduct.initialStock,
maxCapacity: 1000,
status: 'Optimal',
region: 'CBE-NORTH'
};
setProducts([createdProd, ...products]);
setInventory([createdInv, ...inventory]);
setShowAddProductModal(false);
alert(`Fresh product "${createdProd.name}" incorporated into Master Grocery Catalog and standard ledger!`);
alert(`Fresh product "${createdProd.name}" added to the Global Catalog. It is now available to roll out to all outlets.`);
setNewProduct({
name: '',
@@ -196,7 +176,6 @@ export default function InventoryView({
let parsedCount = 0;
const newProds: ProductMatrixItem[] = [];
const newInvs: InventoryItem[] = [];
lines.forEach(line => {
const parts = line.split(',').map(p => p.trim());
@@ -204,8 +183,6 @@ export default function InventoryView({
const name = parts[0];
const sku = parts[1];
const category = parts[2] || 'Staples / Rice';
const price = Number(parts[3]) || 120;
const initialStock = Number(parts[4]) || 150;
if (!products.some(p => p.sku === sku)) {
newProds.push({
@@ -221,16 +198,6 @@ export default function InventoryView({
exposure: 'All Outlets',
verified: true
});
newInvs.push({
sku,
name,
warehouse: 'RS Puram Hub (CBE-01)',
stockLevel: initialStock,
maxCapacity: 1000,
status: 'Optimal',
region: 'CBE-NORTH'
});
parsedCount++;
}
}
@@ -238,7 +205,6 @@ export default function InventoryView({
if (parsedCount > 0) {
setProducts(prev => [...newProds, ...prev]);
setInventory(prev => [...newInvs, ...prev]);
const logEntry: ImportLog = {
timestamp: new Date().toLocaleTimeString() + ' (IST)',
@@ -259,7 +225,6 @@ export default function InventoryView({
const handleImportPreset = (presetName: string, itemsList: Array<{name: string, sku: string, cat: string, price: number, stock: number, img: string}>) => {
let imported = 0;
const newProds: ProductMatrixItem[] = [];
const newInvs: InventoryItem[] = [];
itemsList.forEach((itm) => {
if (!products.some(p => p.sku === itm.sku)) {
@@ -276,23 +241,12 @@ export default function InventoryView({
exposure: 'All Outlets',
verified: true
});
newInvs.push({
sku: itm.sku,
name: itm.name,
warehouse: 'Peelamedu Sort Center',
stockLevel: itm.stock,
maxCapacity: 800,
status: 'Optimal',
region: 'CBE-EAST'
});
imported++;
}
});
if (imported > 0) {
setProducts(prev => [...newProds, ...prev]);
setInventory(prev => [...newInvs, ...prev]);
const logEntry: ImportLog = {
timestamp: new Date().toLocaleTimeString() + ' (IST)',
@@ -331,23 +285,23 @@ export default function InventoryView({
<div>
<h1 className="font-sans font-bold text-2xl tracking-tight text-[#0f172a] flex items-center gap-xs">
<Layers className="text-[#581c87]" size={24} />
Coimbatore Grocery Assortment & Catalogue Studio
Product Catalog · Global Assortment
</h1>
<p className="text-zinc-500 font-sans text-xs mt-1">
Build regional catalogues, update localized stock balances, parse batch imports, and style brand bag templates.
The master product catalog for all your outlets. Import products into the global catalog and monitor live stock across every store under you.
</p>
<div className="mt-1.5">
{stockQ.isLoading ? (
{storesLoading ? (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-zinc-400 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-zinc-300 animate-pulse" /> Loading live stock
<span className="w-1.5 h-1.5 rounded-full bg-zinc-300 animate-pulse" /> Loading live stock across outlets
</span>
) : stockQ.isError ? (
) : storesError ? (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-rose-600 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-rose-500" /> Live data unavailable
</span>
) : (
<span className="inline-flex items-center gap-1 text-[10px] font-semibold text-emerald-600 uppercase tracking-wide">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" /> Live · {locationName} · {products.length} SKUs
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" /> Live · {locations.length} outlet{locations.length === 1 ? '' : 's'} · {products.length} catalog SKUs
</span>
)}
</div>
@@ -362,7 +316,7 @@ export default function InventoryView({
: 'bg-white hover:bg-zinc-50 text-zinc-700 border border-[#e2e8f0]'
}`}
>
🌾 Catalog Grid & Ledger
🌐 Global Catalog & Stocks
</button>
<button
@@ -380,7 +334,23 @@ export default function InventoryView({
{activeTab === 'catalog' ? (
<>
{/* Quick Category Tab Filter Row */}
{/* Admin access banner */}
<div className="bg-[#faf5ff] border border-purple-100 rounded-xl p-md flex flex-col sm:flex-row sm:items-center justify-between gap-sm">
<div className="flex items-start gap-sm">
<ShieldCheck size={16} className="text-[#581c87] shrink-0 mt-0.5" />
<div>
<p className="font-sans text-xs text-zinc-700 font-semibold">Global Catalog Admin access</p>
<p className="text-[11px] text-zinc-500 mt-0.5 leading-relaxed">
As an admin you can import products into the global catalog. Store managers see it read-only. The stock below is live across every outlet under you.
</p>
</div>
</div>
<span className="shrink-0 self-start sm:self-center text-[9px] font-bold uppercase tracking-wider bg-[#581c87] text-white px-2 py-1 rounded">
Admin
</span>
</div>
{/* Category filter + admin import actions */}
<div className="flex flex-wrap gap-2 py-1 items-center justify-between">
<div className="flex flex-wrap gap-2">
{categories.map((cat) => (
@@ -388,156 +358,178 @@ export default function InventoryView({
key={cat}
onClick={() => setSelectedCategory(cat)}
className={`px-4 py-2 rounded-lg font-sans text-xs font-semibold tracking-wide transition-all border outline-none cursor-pointer ${
selectedCategory === cat
selectedCategory === cat
? 'bg-[#581c87] text-white border-[#581c87] shadow-sm'
: 'bg-white text-zinc-700 border-[#e2e8f0] hover:bg-zinc-50'
}`}
>
{cat === 'ALL' ? '🌾 All Catalog Items' : cat.replace('Groceries / ', '').replace('Staples / ', '').replace('Beverages / ', '').replace('Fresh Produce / ', '')}
{cat === 'ALL' ? '🌐 All Catalog Items' : cat.replace('Groceries / ', '').replace('Staples / ', '').replace('Beverages / ', '').replace('Fresh Produce / ', '')}
</button>
))}
</div>
<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"
>
<Plus size={14} />
Add Manual SKU
</button>
<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"
>
<UploadCloud size={14} />
Import to Global Catalog
</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"
>
<Plus size={14} />
Add Manual SKU
</button>
</div>
</div>
{/* Multi-Pane Layout: Left Catalog Grid, Right Stock balances */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-gutter text-xs font-sans">
{/* Left Grid: Grocery Catalogue Items Showcase */}
<div className="lg:col-span-2 space-y-md">
<div className="bg-[#f8fafc]/50 border border-[#e2e8f0] p-md rounded-xl">
<h3 className="font-sans font-bold text-sm text-[#0f172a] mb-xs">Active Assortment Items</h3>
<p className="text-zinc-500 font-normal mb-md leading-relaxed text-[11px]">Primary catalog schema synchronized on customer booking apps. Total: {filteredProducts.length} items</p>
{/* Global Catalog — master assortment grid (full width) */}
<div className="bg-[#f8fafc]/50 border border-[#e2e8f0] p-md rounded-xl text-xs font-sans">
<div className="flex items-center justify-between mb-xs">
<h3 className="font-sans font-bold text-sm text-[#0f172a]">Global Product Catalog</h3>
<span className="text-[10px] text-[#581c87] font-bold bg-purple-50 px-2 py-0.5 rounded border border-purple-100">
{filteredProducts.length} item{filteredProducts.length === 1 ? '' : 's'}
</span>
</div>
<p className="text-zinc-500 font-normal mb-md leading-relaxed text-[11px]">
Master assortment available to roll out to every outlet imported by the admin and synced to the customer booking apps.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-sm">
{filteredProducts.map((prod) => {
return (
<div key={prod.id} className="bg-white border border-[#e2e8f0] rounded-xl overflow-hidden p-md flex gap-md shadow-sm hover:shadow-md transition-shadow relative">
<div className="w-16 h-16 rounded-xl border border-zinc-100 shrink-0 overflow-hidden bg-zinc-50">
<img
src={prod.image}
alt={prod.name}
referrerPolicy="no-referrer"
className="w-full h-full object-cover"
/>
{storesLoading && products.length === 0 ? (
<div className="text-center py-xl text-zinc-400 text-xs">Loading global catalog</div>
) : filteredProducts.length === 0 ? (
<div className="text-center py-xl text-zinc-400 text-xs">No catalog products match your search or category.</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-sm">
{filteredProducts.map((prod) => (
<div key={prod.id} className="bg-white border border-[#e2e8f0] rounded-xl overflow-hidden p-md flex gap-md shadow-sm hover:shadow-md transition-shadow relative">
<div className="w-16 h-16 rounded-xl border border-zinc-100 shrink-0 overflow-hidden bg-zinc-50">
<img src={prod.image} alt={prod.name} referrerPolicy="no-referrer" className="w-full h-full object-cover" />
</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-zinc-900 leading-tight text-xs truncate">{prod.name}</h4>
<span className="text-[10px] text-zinc-400 font-bold tracking-tight">{prod.sku}</span>
</div>
<div className="flex-1 space-y-1">
<div className="flex items-start justify-between">
<div>
<h4 className="font-bold text-zinc-900 leading-tight text-xs">{prod.name}</h4>
<span className="text-[10px] text-zinc-400 font-bold tracking-tight">{prod.sku}</span>
</div>
<span className="px-1.5 py-0.5 rounded text-[9px] font-bold uppercase bg-purple-50 text-purple-700">
{prod.category.split(' / ')[0]}
</span>
</div>
<div className="flex justify-between items-center pt-2">
<div>
<span className="text-[9px] text-zinc-400 block uppercase tracking-wider font-bold">Sold (Units)</span>
<span className="font-bold text-zinc-800 font-mono">{prod.unitsSold.toLocaleString()}</span>
</div>
<div className="text-right">
<span className="text-[9px] text-zinc-400 block uppercase tracking-wider font-bold">Total revenue</span>
<span className="font-bold text-emerald-600 font-mono">{prod.revenue.toLocaleString()}</span>
</div>
</div>
<span className="px-1.5 py-0.5 rounded text-[9px] font-bold uppercase bg-purple-50 text-purple-700 shrink-0">
{prod.category.split(' / ')[0]}
</span>
</div>
<div className="flex justify-between items-center pt-2">
<div>
<span className="text-[9px] text-zinc-400 block uppercase tracking-wider font-bold">Sold (Units)</span>
<span className="font-bold text-zinc-800 font-mono">{prod.unitsSold.toLocaleString()}</span>
</div>
<div className="text-right">
<span className="text-[9px] text-zinc-400 block uppercase tracking-wider font-bold">Total revenue</span>
<span className="font-bold text-emerald-600 font-mono">{prod.revenue.toLocaleString()}</span>
</div>
</div>
);
})}
</div>
</div>
</div>
))}
</div>
)}
</div>
{/* Store Stock — live per-outlet breakdown for every store under the admin */}
<div className="space-y-md text-xs font-sans">
<div className="flex items-center justify-between">
<div>
<h3 className="font-sans font-bold text-sm text-[#0f172a] flex items-center gap-2">
<PackageCheck size={16} className="text-[#581c87]" /> Store Stock · All Outlets Under You
</h3>
<p className="text-zinc-500 text-[11px] mt-0.5">Live on-hand balances for each store you manage.</p>
</div>
<span className="text-[10px] text-[#581c87] font-bold bg-purple-50 px-2 py-0.5 rounded border border-purple-100">
{locations.length} store{locations.length === 1 ? '' : 's'}
</span>
</div>
{/* Right Pane: Stock level adjustment ledgers */}
<div className="space-y-md">
<div className="bg-white border border-[#e2e8f0] rounded-xl p-md shadow-sm space-y-md">
<div>
<h3 className="font-sans font-bold text-sm text-[#0f172a]">Hub Balances Ledger</h3>
<p className="text-zinc-500 font-normal leading-relaxed text-[11px] mt-0.5">Physical checkout balances across localized Coimbatore warehouse locations.</p>
</div>
<div className="divide-y divide-[#f1f5f9] select-none">
{inventory.map((item, idx) => {
const percentage = (item.stockLevel / item.maxCapacity) * 100;
return (
<div key={idx} className="py-md space-y-xs">
<div className="flex justify-between items-start">
<div>
<p className="font-bold text-[#0f172a]">{item.name}</p>
<p className="text-[10px] text-zinc-400 mt-1 font-medium">{item.warehouse}</p>
<div className="flex gap-px pt-1 items-center">
<span className="bg-[#f1f5f9] px-1 py-0.5 rounded text-[8px] font-bold text-zinc-500 font-mono tracking-tight mr-1">{item.region}</span>
<span className={`px-1.5 py-0.5 rounded text-[8px] font-bold tracking-wide uppercase ${
item.status === 'Critical' ? 'bg-rose-50 text-rose-600 border border-rose-100 animate-pulse' : item.status === 'Low Stock' ? 'bg-amber-50 text-amber-600' : 'bg-emerald-50 text-emerald-600'
}`}>
{item.status}
</span>
</div>
</div>
<div className="text-right space-y-1">
<span className="font-mono font-bold text-[#0f172a] block">{item.stockLevel.toLocaleString()} units</span>
<div className="flex gap-1 justify-end">
<button
className="bg-zinc-100 hover:bg-zinc-200 p-1 px-2 rounded font-bold cursor-pointer text-[10px]"
onClick={() => handleUpdateStock(item.sku, -5)}
title="Decrement 5 units"
>
-5
</button>
<button
className="bg-zinc-100 hover:bg-zinc-200 p-1 px-2 rounded font-bold cursor-pointer text-[10px]"
onClick={() => handleUpdateStock(item.sku, 5)}
title="Increment 5 units"
>
+5
</button>
</div>
</div>
</div>
{/* Gauge percentage */}
<div className="pt-1.5 space-y-1">
<div className="w-full bg-[#eceef0] h-1.5 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-300 ${
item.status === 'Critical' ? 'bg-rose-500' : item.status === 'Low Stock' ? 'bg-amber-500' : 'bg-[#581c87]'
}`}
style={{ width: `${Math.min(percentage, 100)}%` }}
/>
</div>
<div className="flex justify-between text-[9px] text-zinc-400 font-bold">
<span>Verification Level: {Math.round(percentage)}%</span>
{item.status !== 'Optimal' && (
<button
onClick={() => handleReplenishSku(item.sku)}
className="text-[#581c87] hover:underline flex items-center gap-px font-bold cursor-pointer"
>
<Zap size={11} className="text-amber-500 animate-bounce" />
Auto-Replenish
</button>
)}
{locations.length === 0 ? (
<div className="text-center py-xl text-zinc-400 text-xs border border-dashed border-[#e2e8f0] rounded-xl bg-white">
{locationsQ.isLoading ? 'Loading outlets…' : 'No outlets found under this tenant.'}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-gutter">
{storesStock.map((store) => {
const items = store.rows
.map((r) => stockRowToInventory(r, store.locationname))
.filter((it) => !searchQuery || it.name.toLowerCase().includes(searchQuery.toLowerCase()));
const totalUnits = items.reduce((a, it) => a + it.stockLevel, 0);
const lowCount = items.filter((it) => it.status !== 'Optimal').length;
const meta = locations.find((l) => l.locationid === store.locationid);
const status = meta?.status ?? 'Active';
return (
<div key={store.locationid} className="bg-white border border-[#e2e8f0] rounded-xl shadow-sm overflow-hidden flex flex-col">
<div className="p-md border-b border-[#e2e8f0] bg-[#f8fafc]">
<div className="flex justify-between items-start gap-2">
<div className="min-w-0">
<p className="font-bold text-[#0f172a] truncate">{store.locationname}</p>
<p className="text-[10px] text-zinc-400 font-medium mt-0.5">
{store.isLoading ? 'Syncing…' : `${items.length} SKUs · ${totalUnits.toLocaleString('en-IN')} units on hand`}
</p>
</div>
<span className={`shrink-0 px-1.5 py-0.5 rounded text-[9px] font-bold uppercase ${
status.toLowerCase() === 'active'
? 'text-emerald-600 bg-emerald-50 border border-emerald-100'
: 'text-zinc-500 bg-zinc-100'
}`}>
{status}
</span>
</div>
{lowCount > 0 && !store.isLoading && (
<p className="text-[10px] text-amber-600 font-semibold mt-1.5 flex items-center gap-1">
<AlertTriangle size={11} /> {lowCount} low / critical SKU{lowCount === 1 ? '' : 's'}
</p>
)}
</div>
);
})}
</div>
</div>
</div>
<div className="divide-y divide-[#f1f5f9] max-h-72 overflow-y-auto">
{store.isLoading ? (
<div className="p-lg text-center text-zinc-400 text-[11px]">Loading store stock</div>
) : store.isError ? (
<div className="p-lg text-center text-rose-500 text-[11px]">Couldn't load this store's stock.</div>
) : items.length === 0 ? (
<div className="p-lg text-center text-zinc-400 text-[11px]">No stock items{searchQuery ? ' match your search' : ''}.</div>
) : (
items.map((it, idx) => {
const pct = Math.min(100, (it.stockLevel / it.maxCapacity) * 100);
return (
<div key={idx} className="p-sm">
<div className="flex justify-between items-start gap-2">
<p className="font-semibold text-zinc-800 text-[11px] leading-tight min-w-0 truncate">{it.name}</p>
<span className="font-mono font-bold text-[#0f172a] text-[11px] shrink-0">{it.stockLevel.toLocaleString('en-IN')}</span>
</div>
<div className="flex items-center gap-2 mt-1">
<div className="flex-1 bg-[#eceef0] h-1.5 rounded-full overflow-hidden">
<div
className={`h-full rounded-full ${
it.status === 'Critical' ? 'bg-rose-500' : it.status === 'Low Stock' ? 'bg-amber-500' : 'bg-[#581c87]'
}`}
style={{ width: `${pct}%` }}
/>
</div>
<span className={`text-[8px] font-bold uppercase shrink-0 ${
it.status === 'Critical' ? 'text-rose-600' : it.status === 'Low Stock' ? 'text-amber-600' : 'text-emerald-600'
}`}>
{it.status}
</span>
</div>
</div>
);
})
)}
</div>
</div>
);
})}
</div>
)}
</div>
</>
) : (