From 2874bf94dfdba63e364f3fe15efdb27fcfae74f4 Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Sun, 4 Oct 2026 16:19:16 +0530 Subject: [PATCH] feat: add SellerContext provider and inventory and bulk upload dashboard pages --- src/context/SellerContext.tsx | 1 + src/pages/dashboard/BulkUploadPage.tsx | 967 +++++++++++++++++++++---- src/pages/dashboard/InventoryPage.tsx | 379 +++++----- 3 files changed, 1023 insertions(+), 324 deletions(-) diff --git a/src/context/SellerContext.tsx b/src/context/SellerContext.tsx index e61f801..d2355f5 100644 --- a/src/context/SellerContext.tsx +++ b/src/context/SellerContext.tsx @@ -168,6 +168,7 @@ export const SellerProvider: React.FC<{ children: React.ReactNode }> = ({ childr compare_at_price: parseFloat(p.compare_at_price) || undefined, package_weight: p.package_weight || 1.0, shipping_profile: p.shipping_profile || 'Standard Fragile', + uploadSource: p.upload_source || (p.sku?.startsWith('TRDX-') || p.sku?.includes('CSV') || p.sku?.startsWith('BATCH-') ? 'bulk' : 'individual'), salesCount: 0 }))); diff --git a/src/pages/dashboard/BulkUploadPage.tsx b/src/pages/dashboard/BulkUploadPage.tsx index b4eac1c..0906d61 100644 --- a/src/pages/dashboard/BulkUploadPage.tsx +++ b/src/pages/dashboard/BulkUploadPage.tsx @@ -8,12 +8,27 @@ import { FileText, Clock, RefreshCw, - Archive, - Info + Info, + Package, + Layers, + Search, + Filter, + Plus, + Minus, + Award, + Edit3, + Trash2, + Tag, + ShieldCheck, + Check, + X, + Sparkles, + ExternalLink, + ChevronRight, + ArrowUpRight } from 'lucide-react'; import { api } from '../../services/api'; import { useSeller } from '../../context/SellerContext'; -import { formatISTDateTime } from '../../utils/date'; interface BatchLog { id: string; @@ -26,8 +41,23 @@ interface BatchLog { errorSummary?: string; } +const HEALTH_CONFIG: Record = { + Critical: { label: 'Critical', bg: '#FEE2E2', color: '#991B1B', dot: '#DC2626' }, + Low: { label: 'Low Buffer', bg: '#FFF3E0', color: '#C2410C', dot: '#EA580C' }, + Adequate: { label: 'Adequate', bg: '#FEF9C3', color: '#854D0E', dot: '#CA8A04' }, + Healthy: { label: 'Healthy', bg: '#E6F4EA', color: '#166534', dot: '#16A34A' }, +}; + +function healthFromStock(stock: number): string { + if (stock === 0) return 'Critical'; + if (stock <= 4) return 'Low'; + if (stock <= 15) return 'Adequate'; + return 'Healthy'; +} + export const BulkUploadPage: React.FC = () => { - const { fetchData } = useSeller(); + const { inventory, updateStock, deleteProduct, fetchData } = useSeller(); + const [isUploading, setIsUploading] = useState(false); const [uploadSuccess, setUploadSuccess] = useState(null); const [uploadSummary, setUploadSummary] = useState<{ total: number; created: number; updated: number; skipped: number; errors: string[] } | null>(null); @@ -38,6 +68,31 @@ export const BulkUploadPage: React.FC = () => { const [batches, setBatches] = useState([]); const [isLoadingLogs, setIsLoadingLogs] = useState(false); + // Filters for items + const [activeTab, setActiveTab] = useState<'all_items' | 'bulk_logs'>('all_items'); + const [searchTerm, setSearchTerm] = useState(''); + const [sourceFilter, setSourceFilter] = useState<'all' | 'bulk' | 'individual'>('all'); + const [categoryFilter, setCategoryFilter] = useState('All'); + const [stockFilter, setStockFilter] = useState<'all' | 'in_stock' | 'low_stock'>('all'); + + // Quick Edit Modal state + const [editingProduct, setEditingProduct] = useState(null); + const [editPrice, setEditPrice] = useState(''); + const [editStock, setEditStock] = useState(''); + const [isSavingEdit, setIsSavingEdit] = useState(false); + + const [stockValues, setStockValues] = useState>({}); + + useEffect(() => { + setStockValues(prev => { + const next = { ...prev }; + inventory.forEach(item => { + next[item.id] = item.stock; + }); + return next; + }); + }, [inventory]); + const triggerCsvDownload = (csvString: string, filename = 'Validation_Errors.csv') => { const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); @@ -82,18 +137,7 @@ export const BulkUploadPage: React.FC = () => { errorSummary: l.error_summary }))); } else { - // Fallback demo log if no history exists yet - setBatches([ - { - id: 'BATCH-2026-DEFAULT', - fileName: 'Craft_Standard_Catalog_Template.csv', - date: formatISTDateTime(), - totalRows: 3, - successfulRows: 3, - failedRows: 0, - status: 'Completed' - } - ]); + setBatches([]); } } catch (err) { console.warn('Could not fetch bulk upload logs, fallback to local state:', err); @@ -104,6 +148,7 @@ export const BulkUploadPage: React.FC = () => { useEffect(() => { loadBatchLogs(); + fetchData(); }, []); const handleFileChange = async (e: React.ChangeEvent) => { @@ -175,30 +220,217 @@ export const BulkUploadPage: React.FC = () => { document.body.removeChild(link); }; + // Classify item source: Bulk vs Individual + const getItemSourceInfo = (item: any) => { + const isBulk = item.uploadSource === 'bulk' || + item.sku?.startsWith('TRDX-') || + item.sku?.startsWith('BATCH-') || + item.sku?.includes('CSV'); + if (isBulk) { + return { + type: 'bulk', + label: 'Bulk CSV Batch', + icon: FileSpreadsheet, + bg: '#EFF6FF', + color: '#1D4ED8', + border: '#BFDBFE' + }; + } + return { + type: 'individual', + label: 'Individual Form', + icon: Plus, + bg: '#F0FDF4', + color: '#15803D', + border: '#BBF7D0' + }; + }; + + const handleStepStock = (id: string, delta: number) => { + setStockValues(prev => { + const current = prev[id] ?? (inventory.find(i => i.id === id)?.stock ?? 0); + const next = Math.max(0, current + delta); + updateStock(id, next); + return { ...prev, [id]: next }; + }); + }; + + const handleDeleteItem = async (id: string, title: string) => { + if (window.confirm(`Are you sure you want to remove "${title}" from your catalog?`)) { + try { + await deleteProduct(id); + await fetchData(); + } catch (err: any) { + alert(err.message || 'Failed to delete product'); + } + } + }; + + const handleOpenEdit = (item: any) => { + setEditingProduct(item); + setEditPrice(item.price); + setEditStock(stockValues[item.id] ?? item.stock); + }; + + const handleSaveEditModal = async () => { + if (!editingProduct) return; + setIsSavingEdit(true); + try { + const updatedPrice = editPrice !== '' ? Number(editPrice) : editingProduct.price; + const updatedStock = editStock !== '' ? Number(editStock) : (stockValues[editingProduct.id] ?? editingProduct.stock); + + if (editingProduct.id && !String(editingProduct.id).startsWith('prod-')) { + await api.patchProduct(editingProduct.id, { + price: updatedPrice, + stock: updatedStock + }); + } + await updateStock(editingProduct.id, updatedStock); + await fetchData(); + setEditingProduct(null); + } catch (err: any) { + alert(err.message || 'Could not update product details'); + } finally { + setIsSavingEdit(false); + } + }; + + // Filtered inventory list + const categoriesList = ['All', ...Array.from(new Set(inventory.map(i => i.category || 'General')))]; + + const filteredInventory = inventory.filter(item => { + const q = searchTerm.toLowerCase(); + const matchesSearch = !q || + item.title.toLowerCase().includes(q) || + item.sku.toLowerCase().includes(q) || + item.category.toLowerCase().includes(q); + + const source = getItemSourceInfo(item).type; + const matchesSource = sourceFilter === 'all' ? true : sourceFilter === source; + const matchesCategory = categoryFilter === 'All' || item.category === categoryFilter; + + const currentStock = stockValues[item.id] ?? item.stock; + const matchesStock = stockFilter === 'all' ? true : stockFilter === 'in_stock' ? currentStock > 0 : currentStock === 0; + + return matchesSearch && matchesSource && matchesCategory && matchesStock; + }); + + const bulkCount = inventory.filter(i => getItemSourceInfo(i).type === 'bulk').length; + const individualCount = inventory.filter(i => getItemSourceInfo(i).type === 'individual').length; + const totalCatalogValue = inventory.reduce((acc, i) => acc + ((stockValues[i.id] ?? i.stock) * i.price), 0); + const lowStockCount = inventory.filter(i => (stockValues[i.id] ?? i.stock) <= 4).length; + return (
-
-

- Bulk Catalog & Batch Management -

-

- Add or update dozens of handcrafted SKUs at once using our official spreadsheets or image-packed ZIP archives. -

+ + {/* Top Header */} +
+
+

+ Bulk & Account Catalog Registry +

+

+ Manage all products uploaded from your account — both bulk CSV/ZIP batch uploads and individual craft listings. +

+
+
+ +
+ {/* Summary KPI Cards */} +
+ +
+
+ Total Account SKUs +
+ +
+
+
{inventory.length}
+
+ ✓ All Active + · Ready for discovery +
+
+ +
+
+ Bulk Batch Uploads +
+ +
+
+
{bulkCount} SKUs
+
+ {batches.length} Batch Log Records +
+
+ +
+
+ Individual Uploads +
+ +
+
+
{individualCount} SKUs
+
+ Direct craft form entry +
+
+ +
+
+ Catalog Value +
+ +
+
+
₹{totalCatalogValue.toLocaleString('en-IN')}
+
0 ? '#DC2626' : '#6B7280', marginTop: '2px', fontWeight: lowStockCount > 0 ? 700 : 400 }}> + {lowStockCount > 0 ? `⚠ ${lowStockCount} items low stock` : '✓ Stock levels healthy'} +
+
+ +
+ + {/* File Upload Zone (Drop Zone & Download Template Cards) */}
+ {/* Left: File Upload Zone */}
-
+
-

Upload Batch File

+

Upload Bulk Batch File

.CSV · .ZIP

- Supported formats: .csv (catalog list) or .zip (catalog + image folders). Max 15 MB. + Supported formats: .csv (catalog list) or .zip (catalog + product image folders).

{ border: '2px dashed #701A29', borderRadius: '16px', backgroundColor: '#FFF9F9', - padding: '2.5rem 1.5rem', + padding: '2.2rem 1.25rem', textAlign: 'center', cursor: 'pointer', transition: 'all 0.2s ease' @@ -223,14 +455,14 @@ export const BulkUploadPage: React.FC = () => { onMouseOver={e => e.currentTarget.style.backgroundColor = '#FFF0F2'} onMouseOut={e => e.currentTarget.style.backgroundColor = '#FFF9F9'} > -
- +
+
-
- {isUploading ? 'Validating & Ingesting SKUs to Database...' : 'Click to Browse or Drop Batch File Here'} +
+ {isUploading ? 'Validating & Ingesting SKUs...' : 'Click to Browse or Drop Batch File Here'}
-

- Columns are automatically verified for GST rates, craft origin, prices, and stock inventory. +

+ Automatic validation for GST rates, craft origin, price, stock, and GI certification tags.

- {/* Right: Template Download and Instructions */} + {/* Right: Template Download & Direct Upload Options */}
-

Official Catalog Template

+

Official Catalog Template

- Use our standardized template to avoid inventory sync delays or incorrect GI tagging. + Download our standardized CSV template for batch product imports.

@@ -370,122 +602,591 @@ export const BulkUploadPage: React.FC = () => {
-
Spreadsheet Guidelines:
+
Batch Standards:
  • Title & SKU: Unique identifier per variant.
  • -
  • Price & MRP: Pure numeric values in INR.
  • +
  • Price & MRP: Numeric values in INR.
  • GI_Tagged: Set to Yes or No.
  • -
  • ZIP Ingestion: Name your image files to match the SKU (e.g. TRDX-001.jpg).
  • +
  • ZIP Ingestion: Name image files matching SKU (e.g. TRDX-001.jpg).
- Need help? Check Heritage Cataloging standards in Settings. + Have a single craft item to add? Use the direct add product form.
+
- {/* Batch Processing History Table */} + {/* Navigation Tabs for All Account Items & Bulk Logs */}
-
-
-

Recent Batch History & Upload Logs

-

Track live processing results from your bulk uploads

+ + {/* Tab Header */} +
+
+ + + +
+ +
+ {activeTab === 'all_items' ? `Showing ${filteredInventory.length} of ${inventory.length} total products` : `Showing ${batches.length} batch upload sessions`}
-
-
- - - - - - - - - - - - - - {batches.map((b) => ( - - - - - - - - + + {/* Tab 1: All Uploaded Items (Registry with Bulk & Individual details) */} + {activeTab === 'all_items' && ( +
+ {/* Filter & Search Toolbar */} +
+ + {/* Search input */} +
+ + setSearchTerm(e.target.value)} + style={{ + width: '100%', + padding: '8px 12px 8px 36px', + fontSize: '0.8rem', + borderRadius: '8px', + border: '1px solid #D1D5DB', + outline: 'none' + }} + /> +
+ + {/* Filters */} +
+ + {/* Source Filter */} +
+ Source: + +
+ + {/* Category Filter */} +
+ Category: + +
+ + {/* Stock Filter */} +
+ Stock: + +
+ +
+ +
+ + {/* Product Items Table */} +
+
Batch IDFile Name & Upload DateTotal SKUsSuccessfulErrorsStatusActions
{b.id} -
{b.fileName}
-
{b.date}
-
{b.totalRows}{b.successfulRows} 0 ? '#DC2626' : '#6B7280' }}>{b.failedRows} - - {b.status} - - - {(b.failedRows > 0 || b.status.toLowerCase() === 'failed') ? ( - - ) : ( - — - )} -
+ + + + + + + + + + + + + {filteredInventory.length === 0 ? ( + + + + ) : ( + filteredInventory.map(item => { + const srcInfo = getItemSourceInfo(item); + const SourceIcon = srcInfo.icon; + const stockVal = stockValues[item.id] ?? item.stock; + const healthKey = healthFromStock(stockVal); + const healthCfg = HEALTH_CONFIG[healthKey] || HEALTH_CONFIG.Adequate; + const discountPct = item.mrp && item.mrp > item.price ? Math.round(((item.mrp - item.price) / item.mrp) * 100) : 0; + + return ( + + + {/* Item Details */} + + + {/* Source Badge */} + + + {/* Category */} + + + {/* Pricing */} + + + {/* Stock Controls */} + + + {/* Approval Status */} + + + {/* Actions */} + + + + ); + }) + )} + +
Craft Product & SKUUpload SourceCategory & GI TagPricing (INR)Stock & AdjustStatusActions
+ No items found matching your filter criteria. Try uploading a batch file above or clearing filters. +
+
+ {item.title} { + (e.target as HTMLElement).setAttribute('src', 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=200'); + }} + /> +
+
{item.title}
+
+ + {item.sku} + + {item.giTagged && ( + + + GI Tagged + + )} +
+
+
+
+ + + {srcInfo.label} + + +
{item.category}
+
{item.craftTechnique || 'Authentic Craft'}
+
+
₹{item.price.toLocaleString('en-IN')}
+ {item.mrp && item.mrp > item.price && ( +
+ MRP ₹{item.mrp.toLocaleString('en-IN')} {discountPct > 0 && ({discountPct}% off)} +
+ )} +
+
+ + + {stockVal} + + +
+
+ + + {healthCfg.label} + +
+
+ + {item.approvalStatus === 'approved' ? : item.approvalStatus === 'rejected' ? : } + {item.approvalStatus === 'approved' ? 'Approved & Live' : item.approvalStatus === 'rejected' ? 'Needs Review' : 'In Verification'} + + +
+ + +
+
+
+
+ )} + + {/* Tab 2: Bulk Batch History & Ingestion Logs */} + {activeTab === 'bulk_logs' && ( +
+ + + + + + + + + + - ))} - -
Batch IDFile Name & Upload DateTotal SKUsSuccessfulErrorsStatusActions
-
+ + + {batches.length === 0 ? ( + + + No bulk upload batches processed yet. Upload your CSV or ZIP batch file above to add products to your catalog. + + + ) : ( + batches.map((b) => ( + + {b.id} + +
{b.fileName}
+
{b.date}
+ + {b.totalRows} + {b.successfulRows} + 0 ? '#DC2626' : '#6B7280' }}>{b.failedRows} + + + {b.status} + + + + {(b.failedRows > 0 || b.status.toLowerCase() === 'failed') ? ( + + ) : ( + — + )} + + + )) + )} + + +
+ )} +
+ + {/* Quick Edit Modal */} + {editingProduct && ( +
+
+
+

Quick Edit Product

+ +
+ +
+ {editingProduct.title} +
+
{editingProduct.title}
+
SKU: {editingProduct.sku}
+
+
+ +
+
+ + setEditPrice(e.target.value === '' ? '' : Number(e.target.value))} + style={{ + width: '100%', + padding: '8px 12px', + fontSize: '0.84rem', + borderRadius: '8px', + border: '1px solid #D1D5DB', + outline: 'none' + }} + /> +
+ +
+ + setEditStock(e.target.value === '' ? '' : Number(e.target.value))} + style={{ + width: '100%', + padding: '8px 12px', + fontSize: '0.84rem', + borderRadius: '8px', + border: '1px solid #D1D5DB', + outline: 'none' + }} + /> +
+
+ +
+ + +
+
+
+ )} +
); }; diff --git a/src/pages/dashboard/InventoryPage.tsx b/src/pages/dashboard/InventoryPage.tsx index 5bb4cf8..a6a746d 100644 --- a/src/pages/dashboard/InventoryPage.tsx +++ b/src/pages/dashboard/InventoryPage.tsx @@ -1,16 +1,14 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useSeller } from '../../context/SellerContext'; import { api } from '../../services/api'; import { Search, - Filter, Trash2, UploadCloud, Plus, Minus, Award, - AlertCircle, AlertTriangle, Package, IndianRupee, @@ -19,8 +17,7 @@ import { Edit3, ShieldCheck, CheckCircle2, - Clock, - XCircle + FileSpreadsheet } from 'lucide-react'; const HEALTH_CONFIG: Record = { @@ -31,8 +28,8 @@ const HEALTH_CONFIG: Record { const { inventory, updateStock, deleteProduct, fetchData } = useSeller(); + useEffect(() => { + fetchData(); + }, []); + + const getItemSourceInfo = (item: any) => { + const isBulk = item.uploadSource === 'bulk' || + item.sku?.startsWith('TRDX-') || + item.sku?.startsWith('BATCH-') || + item.sku?.includes('CSV'); + if (isBulk) { + return { + type: 'bulk', + label: 'Bulk CSV', + icon: FileSpreadsheet, + bg: '#EFF6FF', + color: '#1D4ED8', + border: '#BFDBFE' + }; + } + return { + type: 'individual', + label: 'Single Craft', + icon: Plus, + bg: '#F0FDF4', + color: '#15803D', + border: '#BBF7D0' + }; + }; + const allSkus = inventory.map(inv => ({ ...inv, subtitle: inv.craftTechnique || inv.category, - health: healthFromStock(inv.stock) + health: healthFromStock(inv.stock), + sourceInfo: getItemSourceInfo(inv) })); const [searchTerm, setSearchTerm] = useState(''); const [stockFilter, setStockFilter] = useState<'all' | 'in_stock' | 'low_stock'>('all'); + const [sourceFilter, setSourceFilter] = useState<'all' | 'bulk' | 'individual'>('all'); const [categoryFilter, setCategoryFilter] = useState('All'); const [stockValues, setStockValues] = useState>({}); const [currentPage, setCurrentPage] = useState(1); @@ -57,7 +85,7 @@ export const InventoryPage: React.FC = () => { const [editPrice, setEditPrice] = useState(''); const [isSavingEdit, setIsSavingEdit] = useState(false); - React.useEffect(() => { + useEffect(() => { setStockValues(prev => { const next = { ...prev }; inventory.forEach(inv => { @@ -67,21 +95,24 @@ export const InventoryPage: React.FC = () => { }); }, [inventory]); - const categories = ['All', 'Pottery & Ceramics', 'Brass & Metal Craft', 'Woodcraft & Carvings', 'Handloom & Textiles', 'Jewellery & Ornaments']; + const categories = ['All', ...Array.from(new Set(allSkus.map(s => s.category || 'General')))]; const filtered = allSkus.filter(item => { const q = searchTerm.toLowerCase(); const matchSearch = !q || item.title.toLowerCase().includes(q) || item.sku.toLowerCase().includes(q) || item.category.toLowerCase().includes(q); const stk = stockValues[item.id] ?? item.stock; - const matchStock = stockFilter === 'all' ? true : stockFilter === 'in_stock' ? stk > 4 : stk <= 4; + const matchStock = stockFilter === 'all' ? true : stockFilter === 'in_stock' ? stk > 0 : stk <= 4; + const matchSource = sourceFilter === 'all' ? true : sourceFilter === item.sourceInfo.type; const matchCat = categoryFilter === 'All' || item.category === categoryFilter; - return matchSearch && matchStock && matchCat; + return matchSearch && matchStock && matchSource && matchCat; }); const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const paginated = filtered.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE); const totalListed = allSkus.length; + const bulkCount = allSkus.filter(s => s.sourceInfo.type === 'bulk').length; + const singleCount = allSkus.filter(s => s.sourceInfo.type === 'individual').length; const lowStockCount = allSkus.filter(s => (stockValues[s.id] ?? s.stock) <= 4).length; const catalogValue = allSkus.reduce((sum, s) => sum + (stockValues[s.id] ?? s.stock) * s.price, 0); const saleableValue = allSkus.filter(s => (stockValues[s.id] ?? s.stock) > 0) @@ -102,6 +133,17 @@ export const InventoryPage: React.FC = () => { updateStock(id, n); }; + const handleDeleteProduct = async (id: string, title: string) => { + if (window.confirm(`Are you sure you want to delete "${title}" from your inventory catalog?`)) { + try { + await deleteProduct(id); + await fetchData(); + } catch (err: any) { + alert(err.message || 'Could not delete product'); + } + } + }; + const handleSaveEdit = async () => { if (!editingItem) return; setIsSavingEdit(true); @@ -114,6 +156,7 @@ export const InventoryPage: React.FC = () => { if (editingItem.id && !String(editingItem.id).startsWith('prod-')) { await api.patchProduct(editingItem.id, patchPayload); } + await updateStock(editingItem.id, currentStock); await fetchData(); } catch (err) { console.warn('Failed to update product via quick edit drawer:', err); @@ -133,7 +176,7 @@ export const InventoryPage: React.FC = () => { Catalog & Inventory

- Manage your artisan SKUs, stock levels, and pricing across all craft disciplines. + Manage your artisan SKUs (both single and bulk uploaded items), stock levels, and pricing.

@@ -158,10 +201,11 @@ export const InventoryPage: React.FC = () => {
-
{totalListed} SKUs
+
{totalListed} SKUs
- ↑ +6 this week - · {allSkus.filter(s => (stockValues[s.id] ?? s.stock) > 0).length} active + {bulkCount} Bulk + · + {singleCount} Single
@@ -175,8 +219,8 @@ export const InventoryPage: React.FC = () => {
-
0 ? '#DC2626' : '#16A34A', lineHeight: 1.1 }}> - {lowStockCount} Crafts +
0 ? '#DC2626' : '#16A34A', lineHeight: 1.1 }}> + {lowStockCount} Crafts
{lowStockCount > 0 ? 'Restock urgently to avoid cancellations' : 'All SKUs adequately stocked'} @@ -207,19 +251,43 @@ export const InventoryPage: React.FC = () => { {/* Filter Controls */}
+
- {([ - { key: 'all', label: `All (${allSkus.length})` }, - { key: 'in_stock', label: `In Stock (${allSkus.filter(s => (stockValues[s.id] ?? s.stock) > 4).length})` }, - { key: 'low_stock', label: `Low Stock (${lowStockCount})` }, - ] as const).map(f => ( - - ))} + + + + + + +
@@ -268,6 +336,8 @@ export const InventoryPage: React.FC = () => { const stk = stockValues[item.id] ?? item.stock; const health = (item as { health?: string }).health || healthFromStock(stk); const hConf = HEALTH_CONFIG[health] || HEALTH_CONFIG['Healthy']; + const SrcIcon = item.sourceInfo.icon; + return ( (e.currentTarget.style.backgroundColor = '#FAFAFA')} @@ -275,16 +345,33 @@ export const InventoryPage: React.FC = () => { >
- {item.title} { (e.target as HTMLImageElement).src = '/terracotta_vases.jpg'; }} + onError={e => { (e.target as HTMLImageElement).src = 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=200'; }} />
{item.title}
{(item as { subtitle?: string }).subtitle || item.craftTechnique}
-
{item.sku}
+
+ {item.sku} + + + {item.sourceInfo.label} + +
@@ -301,8 +388,12 @@ export const InventoryPage: React.FC = () => {
₹{item.price.toLocaleString('en-IN')}
-
MRP ₹{item.mrp.toLocaleString('en-IN')}
-
{Math.round(((item.mrp - item.price) / item.mrp) * 100)}% off
+ {item.mrp && item.mrp > item.price && ( +
MRP ₹{item.mrp.toLocaleString('en-IN')}
+ )} + {item.mrp && item.mrp > item.price && ( +
{Math.round(((item.mrp - item.price) / item.mrp) * 100)}% off
+ )} @@ -318,60 +409,61 @@ export const InventoryPage: React.FC = () => {
-
{item.salesCount} sold all-time
- - {((item as any).approvalStatus === 'pending' || (item as any).status === 'pending') ? ( - - - Pending Approval - - ) : (item as any).approvalStatus === 'rejected' ? ( - - - Rejected - - ) : ( - - - Approved - - )} - - - - - - {hConf.label} + + + + {item.approvalStatus === 'approved' ? 'Approved' : item.approvalStatus === 'rejected' ? 'Rejected' : 'Pending'} - -
- -
+ ); }) : ( - - -

No matching craft SKUs found.

+ + +

No matching craft SKUs found.

+

Try resetting your search query or filter settings above.

)} @@ -379,127 +471,32 @@ export const InventoryPage: React.FC = () => {
- {/* Pagination Strip */} -
- - Showing {Math.min(filtered.length, currentPage * PAGE_SIZE)} of{' '} - {filtered.length > 0 ? filtered.length : allSkus.length} Handcrafted SKUs · All GI Tags Verified - -
- - {Array.from({ length: totalPages }, (_, i) => i + 1).map(pg => ( - - ))} - + + {currentPage} + + +
-
+ )} +
- {/* Quick Edit Drawer */} - {editingItem && ( -
- {/* Backdrop */} -
setEditingItem(null)} - /> - - {/* Drawer Panel */} -
- - {/* Drawer Header */} -
-
-

Quick Edit SKU

-
SKU: {editingItem.sku}
-
- -
- - {/* Drawer Content Scrollable */} -
- - {/* Product Info Card */} -
-
- Product { (e.target as HTMLImageElement).src = '/terracotta_vases.jpg'; }} /> -
-
{editingItem.title}
-
{editingItem.category} · Handcrafted
-
-
-
- {/* Toggle Switch */} -
-
-
-
Live on Storefront
-
-
- - {/* Selling Price Card */} -
- -
- ₹ - setEditPrice(e.target.value === '' ? '' : parseFloat(e.target.value))} - style={{ width: '100%', border: 'none', borderRadius: '4px', padding: '12px 16px 12px 32px', fontSize: '1rem', fontWeight: 700, color: '#111827', outline: 'none' }} - /> -
-
- Artisan Payout (100% retained) - ₹{editPrice || editingItem.price} -
-
- - {/* Stock on Hand Card */} -
-
-
Stock on Hand
-
Finished sets ready for dispatch
-
-
- -
{(stockValues[editingItem.id] ?? editingItem.stock)}
- -
-
- -
Lead Time / Dispatch Readiness
- -
- - {/* Drawer Footer */} -
- - -
- -
-
- )}
); };