dispatch page
This commit is contained in:
@@ -38,6 +38,7 @@ import {
|
||||
} from '../services/fiestaQueries';
|
||||
import { FIESTA_TENANT_ID, str as fstr } from '../services/fiestaApi';
|
||||
import { stockRowToProduct, stockRowToInventory } from '../services/fiestaMappers';
|
||||
import { useStoreCatalogue } from '../services/storeCatalogue';
|
||||
import AwaitingApi from './AwaitingApi';
|
||||
|
||||
type StockRow = Record<string, unknown>;
|
||||
@@ -46,18 +47,20 @@ const rowId = (r: StockRow) => String(r.productid ?? '') || String(r.productname
|
||||
interface InventoryViewProps {
|
||||
searchQuery: string;
|
||||
isCoimbatoreView: boolean;
|
||||
tenantId?: number;
|
||||
}
|
||||
|
||||
export default function InventoryView({
|
||||
searchQuery,
|
||||
isCoimbatoreView
|
||||
isCoimbatoreView,
|
||||
tenantId = FIESTA_TENANT_ID
|
||||
}: InventoryViewProps) {
|
||||
// ── 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 locationsQ = useFiestaTenantLocations(tenantId);
|
||||
const locations = useMemo(
|
||||
() =>
|
||||
(locationsQ.data ?? []).map((l) => ({
|
||||
@@ -69,7 +72,7 @@ export default function InventoryView({
|
||||
);
|
||||
|
||||
const storesStock = useFiestaStoresStock(
|
||||
FIESTA_TENANT_ID,
|
||||
tenantId,
|
||||
locations.map(({ locationid, locationname }) => ({ locationid, locationname })),
|
||||
);
|
||||
const storesLoading = locationsQ.isLoading || storesStock.some((s) => s.isLoading);
|
||||
@@ -95,6 +98,8 @@ export default function InventoryView({
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'catalog' | 'import_branding'>('catalog');
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('ALL');
|
||||
// The store catalogue the admin curates from the global catalogue (shown to users).
|
||||
const storeCat = useStoreCatalogue();
|
||||
const [showAddProductModal, setShowAddProductModal] = useState(false);
|
||||
const [outletFilter, setOutletFilter] = useState<'all' | 'alerts'>('all');
|
||||
const [outletSearch, setOutletSearch] = useState('');
|
||||
@@ -197,7 +202,7 @@ export default function InventoryView({
|
||||
const handleAddNewProduct = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!newProduct.name || !newProduct.sku) {
|
||||
alert('Kindly supply correct product specifications and catalog SKU code.');
|
||||
alert('Kindly supply correct product specifications and catalogue SKU code.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -217,7 +222,7 @@ export default function InventoryView({
|
||||
|
||||
setProducts([createdProd, ...products]);
|
||||
setShowAddProductModal(false);
|
||||
alert(`Fresh product "${createdProd.name}" added to the Global Catalog. It is now available to roll out to all outlets.`);
|
||||
alert(`Fresh product "${createdProd.name}" added to the Global Catalogue. It is now available to roll out to all outlets.`);
|
||||
|
||||
setNewProduct({
|
||||
name: '',
|
||||
@@ -274,9 +279,9 @@ export default function InventoryView({
|
||||
|
||||
if (parsedCount > 0) {
|
||||
setProducts(prev => [...newProds, ...prev]);
|
||||
alert(`Synchronized ${parsedCount} regional products into Catalog database successfully!`);
|
||||
alert(`Synchronized ${parsedCount} regional products into Catalogue database successfully!`);
|
||||
} else {
|
||||
alert('All the specified SKU codes are already active in the catalog ledger.');
|
||||
alert('All the specified SKU codes are already active in the catalogue ledger.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -293,7 +298,7 @@ export default function InventoryView({
|
||||
<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="Catalog Command Center Banner"
|
||||
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" />
|
||||
@@ -308,13 +313,13 @@ export default function InventoryView({
|
||||
<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 Catalog
|
||||
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 catalog registry with regional assortment presets, brand styling studio, and live stock synchronization feeds.
|
||||
Master catalogue registry with regional assortment presets, brand styling studio, and live stock synchronization feeds.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -329,7 +334,7 @@ export default function InventoryView({
|
||||
}`}
|
||||
>
|
||||
<Package size={13} />
|
||||
<span>Catalog & Stocks</span>
|
||||
<span>Catalogue & Stocks</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -361,7 +366,7 @@ export default function InventoryView({
|
||||
<h3 className="text-xl font-extrabold tracking-tight font-mono">
|
||||
{products.length}
|
||||
</h3>
|
||||
<p className="text-[10px] text-purple-400 font-semibold mt-1">Master catalog</p>
|
||||
<p className="text-[10px] text-purple-400 font-semibold mt-1">Master catalogue</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -405,7 +410,7 @@ export default function InventoryView({
|
||||
{/* 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="flex justify-between items-start">
|
||||
<span className="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Catalog Sync Ratio</span>
|
||||
<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" />
|
||||
</div>
|
||||
@@ -489,18 +494,26 @@ export default function InventoryView({
|
||||
{/* Global Catalog — master assortment grid (full width) */}
|
||||
<div className="bg-white/40 backdrop-blur-md border border-[#e2e8f0] p-md rounded-2xl text-xs font-sans shadow-sm">
|
||||
<div className="flex items-center justify-between mb-sm">
|
||||
<h3 className="font-sans font-bold text-sm text-[#0f172a] flex items-center gap-1.5">
|
||||
<Package size={15} className="text-[#581c87]" /> Global Catalog Assortment
|
||||
</h3>
|
||||
<span className="text-[10px] text-[#581c87] 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>
|
||||
<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
|
||||
</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>
|
||||
<div className="flex items-center gap-2">
|
||||
<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">
|
||||
{filteredProducts.length} item{filteredProducts.length === 1 ? '' : 's'} loaded
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{storesLoading && products.length === 0 ? (
|
||||
<div className="text-center py-xl text-zinc-400 text-xs font-bold">Synchronizing regional database...</div>
|
||||
) : filteredProducts.length === 0 ? (
|
||||
<div className="text-center py-xl text-zinc-400 text-xs font-bold">No catalog products match your selection.</div>
|
||||
<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) => (
|
||||
@@ -565,6 +578,26 @@ export default function InventoryView({
|
||||
<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>
|
||||
@@ -874,10 +907,10 @@ 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">
|
||||
<Sparkles className="text-amber-500 animate-pulse" size={18} />
|
||||
<h3>Cooperative Catalog Presets</h3>
|
||||
<h3>Cooperative Catalogue Presets</h3>
|
||||
</div>
|
||||
|
||||
<AwaitingApi label="Catalog presets" api="[R5]" compact />
|
||||
<AwaitingApi label="Catalogue presets" api="[R5]" compact />
|
||||
</div>
|
||||
|
||||
{/* Custom CSV Parsing Box */}
|
||||
@@ -955,7 +988,7 @@ export default function InventoryView({
|
||||
<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]" />
|
||||
Introduce New Grocery Catalog SKU
|
||||
Introduce New Grocery Catalogue SKU
|
||||
</h4>
|
||||
<button
|
||||
onClick={() => setShowAddProductModal(false)}
|
||||
|
||||
Reference in New Issue
Block a user