-
-
setCategoryId(e.target.value)}
- className="w-full px-3 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500/20 focus:border-purple-500 transition-colors"
- placeholder="e.g. 1"
- required
- />
+
+ {isLoadingCategories ? (
+
Loading...
+ ) : (
+
+ )}
- {isLoadingSubcats ? (
+ {!categoryId ? (
+
Select a category first
+ ) : isLoadingSubcats ? (
Loading...
) : (
diff --git a/src/components/InventoryView.tsx b/src/components/InventoryView.tsx
index 1410f53..50f560a 100644
--- a/src/components/InventoryView.tsx
+++ b/src/components/InventoryView.tsx
@@ -4,6 +4,7 @@
*/
import React, { useState, useEffect, useMemo } from 'react';
+import { useNavigate } from 'react-router-dom';
import {
Layers,
Search,
@@ -43,12 +44,9 @@ import {
useFiestaProductCategories,
useFiestaUpdateStockRequest,
useFiestaGetStockRequests,
- useFiestaGlobalBrands,
- useFiestaGlobalCategories,
- useFiestaGlobalProducts,
} from '../services/fiestaQueries';
import { FIESTA_TENANT_ID, str as fstr } from '../services/fiestaApi';
-import { stockRowToProduct, stockRowToInventory, globalRowToProduct } from '../services/fiestaMappers';
+import { stockRowToProduct, stockRowToInventory } from '../services/fiestaMappers';
import { useStoreCatalogue } from '../services/storeCatalogue';
import BulkCartDrawer from './BulkCartDrawer';
import AwaitingApi from './AwaitingApi';
@@ -75,8 +73,9 @@ export default function InventoryView({
tenantId = FIESTA_TENANT_ID,
isSidebarOpen = false
}: InventoryViewProps) {
- const { selectedProducts, toggleProduct, setIsComparing, clearSelection, setHideCompareBar } = useCompare();
+ const { setHideCompareBar } = useCompare();
const [searchTerm, setSearchTerm] = useState('');
+ const navigate = useNavigate();
// ── Live stock across every outlet (Fiesta) ───────────────────────────────
// This page is the admin's command surface. The GLOBAL CATALOG is the deduped
@@ -109,39 +108,12 @@ export default function InventoryView({
const allStoreRows = storesStock.flatMap((s) => s.rows);
- const globalBrandsQ = useFiestaGlobalBrands();
- const [globalCatalogSearch, setGlobalCatalogSearch] = useState('');
- const [debouncedGlobalSearch, setDebouncedGlobalSearch] = useState('');
-
- useEffect(() => {
- const handler = setTimeout(() => {
- setDebouncedGlobalSearch(globalCatalogSearch);
- }, 500);
- return () => clearTimeout(handler);
- }, [globalCatalogSearch]);
-
- const [globalSelectedBrand, setGlobalSelectedBrand] = useState
('');
-
- const globalCategoriesQ = useFiestaGlobalCategories(globalSelectedBrand);
- const [globalSelectedCategories, setGlobalSelectedCategories] = useState([]);
-
- const globalProductsQ = useFiestaGlobalProducts({
- brand: globalSelectedBrand,
- keyword: debouncedGlobalSearch,
- pagesize: 500
- });
- const liveMasterCatalog = useMemo(() => globalProductsQ.data ?? [], [globalProductsQ.data]);
- const masterCatalogProducts = useMemo(() => liveMasterCatalog.map(globalRowToProduct), [liveMasterCatalog]);
-
- const [activeTab, setActiveTab] = useState<'catalog' | 'import_branding' | 'requests'>('catalog');
+ const [activeTab, setActiveTab] = useState<'catalog' | 'requests'>('catalog');
const [isLocalSidebarOpen, setIsLocalSidebarOpen] = useState(true);
- const [isGlobalSidebarOpen, setIsGlobalSidebarOpen] = useState(true);
const [selectedCategories, setSelectedCategories] = useState([]);
const [hoveredAdminProduct, setHoveredAdminProduct] = useState(null);
const [localSearch, setLocalSearch] = useState('');
const storeCat = useStoreCatalogue();
- const [globalCatalogPicks, setGlobalCatalogPicks] = useState>(new Set());
- const [csvText, setCsvText] = useState('');
const [importPrice, setImportPrice] = useState('');
const [isSettingPrice, setIsSettingPrice] = useState(false);
const [addingPriceProdId, setAddingPriceProdId] = useState(null);
@@ -323,87 +295,10 @@ export default function InventoryView({
);
};
- // Note: globalSelectedCategories is declared at the top now!
-
- const globalCategories = useMemo(() => {
- if (!globalCategoriesQ.data) return [];
- // The new API returns an array of strings for categories
- return (globalCategoriesQ.data as unknown as string[]).filter(Boolean);
- }, [globalCategoriesQ.data]);
-
- const toggleGlobalCategory = (cat: string) => {
- setGlobalSelectedCategories((prev) =>
- prev.includes(cat) ? [] : [cat], // Only allow one at a time for API compatibility, or filter client-side
- );
- };
-
- const filteredGlobalProducts = useMemo(() => {
- let result = masterCatalogProducts;
- // We already pass keyword to API, but can double filter client-side just in case
- if (globalCatalogSearch) {
- const q = globalCatalogSearch.toLowerCase();
- result = result.filter(
- (p) => p.name.toLowerCase().includes(q) || p.sku.toLowerCase().includes(q) || p.category.toLowerCase().includes(q)
- );
- }
- if (globalSelectedCategories.length > 0) {
- result = result.filter((p) => globalSelectedCategories.includes(p.category.split(' / ')[0]));
- }
- return result;
- }, [masterCatalogProducts, globalCatalogSearch, globalSelectedCategories]);
-
-
-
-
const handleToggleProductExposure = (id: string) => {
// Left empty for now, as products are managed via storeCat
};
- // Custom Raw CSV import
- const handleCSVImport = () => {
- const lines = csvText.split('\n').map(l => l.trim()).filter(l => l.length > 0 && !l.startsWith('Name'));
- if (lines.length === 0) {
- alert('CSV sequence contains no importable entries.');
- return;
- }
-
- let parsedCount = 0;
- const newProds: ProductMatrixItem[] = [];
-
- lines.forEach(line => {
- const parts = line.split(',').map(p => p.trim());
- if (parts.length >= 2) {
- const name = parts[0];
- const sku = parts[1];
- const category = parts[2] || 'Staples / Rice';
-
- if (!products.some(p => p.sku === sku)) {
- newProds.push({
- id: String(products.length + newProds.length + 1),
- name,
- sku,
- unitsSold: 0,
- revenue: 0,
- stockStatus: 'Healthy',
- trend: 'flat',
- image: 'https://images.unsplash.com/photo-1542838132-92c53300491e?auto=format&fit=crop&q=80&w=200',
- category,
- exposure: 'All Outlets',
- verified: true,
- isNew: true
- });
- parsedCount++;
- }
- }
- });
-
- if (parsedCount > 0) {
- newProds.forEach(p => storeCat.add({ productid: p.id, name: p.name, sku: p.sku, category: p.category, price: p.price || 0, image: p.image, unit: p.exposure, qty: 100, status: 'Active' }));
- alert(`Synchronized ${parsedCount} regional products into Catalogue database successfully!`);
- } else {
- alert('All the specified SKU codes are already active in the catalogue ledger.');
- }
- };
const flattenedRequests = useMemo(() => {
return storeRequests
.filter(r => requestStoreFilter === 'All Stores' || r.locationname === requestStoreFilter)
@@ -412,21 +307,7 @@ export default function InventoryView({
.filter(([_, pick]) => requestStatusFilter === 'All Statuses' || String((pick as any).status).toLowerCase() === requestStatusFilter.toLowerCase())
.map(([requestId, pick]) => {
const productId = (pick as any).productid;
- let product = products.find(p => String(p.id) === String(productId));
- if (!product) {
- const liveMatch = liveMasterCatalog.find((r: any) => String(r.productid) === String(productId));
- if (liveMatch) {
- product = {
- id: String(liveMatch.productid),
- name: String(liveMatch.productname || 'Unknown'),
- sku: String(liveMatch.sku || `SKU-${liveMatch.productid}`),
- image: String(liveMatch.productimage || 'https://images.unsplash.com/photo-1542838132-92c53300491e?auto=format&fit=crop&q=80&w=200'),
- category: String(liveMatch.categoryname || 'Uncategorized'),
- } as any;
- } else {
- product = masterCatalogProducts.find(p => String(p.id) === String(productId)) as any;
- }
- }
+ const product = products.find(p => String(p.id) === String(productId));
return {
locationid: store.locationid,
locationname: store.locationname,
@@ -438,7 +319,7 @@ export default function InventoryView({
});
})
.sort((a, b) => new Date(b.pickData.requestedAt).getTime() - new Date(a.pickData.requestedAt).getTime());
- }, [storeRequests, products, requestStoreFilter, requestStatusFilter, liveMasterCatalog]);
+ }, [storeRequests, products, requestStoreFilter, requestStatusFilter]);
const requestingStores = useMemo(() => {
return Array.from(new Set(locations.map(l => l.locationname))).sort();
@@ -631,8 +512,8 @@ export default function InventoryView({
{storeRequests.length}
)}
-