function InStockView({ salesUrl, token }) { const [suppliers, setSuppliers] = React.useState([]); const [banks, setBanks] = React.useState([]); const [entries, setEntries] = React.useState([]); const [open, setOpen] = React.useState(false); const [supplierId, setSupplierId] = React.useState(''); const [bankId, setBankId] = React.useState(''); const [supplierAmount, setSupplierAmount] = React.useState(''); const [gstAmount, setGstAmount] = React.useState(''); const [category, setCategory] = React.useState(''); const [items, setItems] = React.useState([ { productNo: '', productName: '', brand: '', model: '', quantity: 1, costPrice: '', validity: '', imes: [] } ]); const [saving, setSaving] = React.useState(false); const [error, setError] = React.useState(''); const [filter, setFilter] = React.useState({ productNo: '', productName: '', brand: '', model: '', productDays: '', quantity: '' }); const [validityPopup, setValidityPopup] = React.useState(false); const [validityData, setValidityData] = React.useState([]); const [showRedDot, setShowRedDot] = React.useState(false); const loadSuppliers = async () => { try { const storedBranchToken = typeof window !== 'undefined' ? (localStorage.getItem('branch_token') || '') : ''; const effectiveToken = token || storedBranchToken || ''; let res = await fetch(salesUrl + '/api/suppliers', { headers: { Authorization: 'Bearer ' + effectiveToken } }); if (res.status === 401 && storedBranchToken && storedBranchToken !== effectiveToken) { res = await fetch(salesUrl + '/api/suppliers', { headers: { Authorization: 'Bearer ' + storedBranchToken } }); } const data = await res.json(); if (!res.ok) throw new Error(data.message || 'Failed to load suppliers'); setSuppliers(Array.isArray(data.suppliers) ? data.suppliers : []); } catch (e) { setError(e.message); } }; const loadBanks = async () => { try { const storedBranchToken = typeof window !== 'undefined' ? (localStorage.getItem('branch_token') || '') : ''; const effectiveToken = token || storedBranchToken || ''; const res = await fetch(salesUrl + '/api/banks', { headers: { Authorization: 'Bearer ' + effectiveToken } }); const data = await res.json(); if (!res.ok) throw new Error(data.message || 'Failed to load banks'); setBanks(Array.isArray(data.banks) ? data.banks : []); } catch (e) { setError(e.message); } }; const loadEntries = async () => { try { const storedBranchToken = typeof window !== 'undefined' ? (localStorage.getItem('branch_token') || '') : ''; const effectiveToken = token || storedBranchToken || ''; let res = await fetch(salesUrl + '/api/in-stock', { headers: { Authorization: 'Bearer ' + effectiveToken } }); if (res.status === 401 && storedBranchToken && storedBranchToken !== effectiveToken) { res = await fetch(salesUrl + '/api/in-stock', { headers: { Authorization: 'Bearer ' + storedBranchToken } }); } const data = await res.json(); if (!res.ok) throw new Error(data.message || 'Failed to load'); setEntries(Array.isArray(data.entries) ? data.entries : []); } catch (e) { setError(e.message); } }; React.useEffect(() => { loadSuppliers(); loadBanks(); loadEntries(); }, []); React.useEffect(() => { const hasExpiringProducts = entries.some(entry => { return entry.items.some(item => { if (!item.validity) return false; const validityDate = new Date(item.validity); const today = new Date(); const diffInDays = Math.ceil((validityDate - today) / (1000 * 60 * 60 * 24)); return diffInDays > 0 && diffInDays <= 7; // Check for products expiring within 7 days }); }); setShowRedDot(hasExpiringProducts); }, [entries]); const handleFilterChange = (field, value) => { setFilter(prev => ({ ...prev, [field]: value })); }; const filteredEntries = React.useMemo(() => { return entries.filter(entry => { return entry.items.some(item => { const productDays = filter.productDays ? parseInt(filter.productDays, 10) : null; const createdDate = new Date(entry.createdAt); const daysInStock = Math.floor((Date.now() - createdDate.getTime()) / (1000 * 60 * 60 * 24)) + 1; return ( (!filter.productNo || item.productNo?.includes(filter.productNo)) && (!filter.productName || item.productName?.toLowerCase().includes(filter.productName.toLowerCase())) && (!filter.brand || item.brand?.toLowerCase().includes(filter.brand.toLowerCase())) && (!filter.model || item.model?.toLowerCase().includes(filter.model.toLowerCase())) && (!productDays || daysInStock === productDays) && (filter.quantity === '' || item.quantity === parseInt(filter.quantity, 10)) ); }); }); }, [entries, filter]); // Removed sumCost calculation and Supplier Amount check const canSubmit = supplierId && bankId && items.every(it => it.productName); // Calculate product amount (qty x cost price) for each item const productAmounts = items.map(it => (Number(it.quantity) || 0) * (Number(it.costPrice) || 0)); const totalProductAmount = productAmounts.reduce((sum, amt) => sum + amt, 0); // Calculate total bill amount const totalBillAmount = (Number(supplierAmount) || 0) + (Number(gstAmount) || 0); const addRow = () => setItems(it => [...it, { productNo: '', productName: '', brand: '', model: '', quantity: 1, costPrice: '', validity: '' }]); const addRowWithImes = () => setItems(it => [...it, { productNo: '', productName: '', brand: '', model: '', quantity: 1, costPrice: '', validity: '', imes: [] }]); const updateItem = (idx, field, value) => setItems(list => list.map((it, i) => { if (i !== idx) return it; // if quantity changed and category is Mobile, ensure imes array length matches quantity if (field === 'quantity') { const qty = Number(value) || 0; const prevImes = Array.isArray(it.imes) ? it.imes.slice(0, qty) : []; while (prevImes.length < qty) prevImes.push(''); return { ...it, [field]: value, imes: prevImes }; } return { ...it, [field]: value }; })); const removeRow = (idx) => setItems(list => list.filter((_, i) => i !== idx)); const resetModal = () => { setSupplierId(''); setBankId(''); setSupplierAmount(''); setItems([{ productNo: '', productName: '', brand: '', model: '', quantity: 1, costPrice: '', validity: '', imes: [] }]); setError(''); setGstAmount(''); setCategory(''); }; // Ensure IMES inputs are present when category is Mobile: keep imes array length == quantity React.useEffect(() => { setItems(prev => prev.map(item => { const qty = Number(item.quantity) || 0; if (category === 'Mobile') { const imes = Array.isArray(item.imes) ? item.imes.slice(0, qty) : []; while (imes.length < qty) imes.push(''); return { ...item, imes }; } // clear imes for non-mobile categories to avoid showing inputs return { ...item, imes: [] }; })); }, [category]); // Helper to generate random alphanumeric string (2-9 chars) function randomProductNo() { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const len = Math.floor(Math.random() * 3) + 2; // 2 to 9 let str = ''; for (let i = 0; i < len; i++) { str += chars.charAt(Math.floor(Math.random() * chars.length)); } return str; } const submit = async () => { setSaving(true); setError(''); try { const res = await fetch(salesUrl + '/api/in-stock', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token }, body: JSON.stringify({ supplier_id: supplierId, bank_id: bankId, supplierAmount: Number(supplierAmount) || 0, gstAmount: Number(gstAmount) || 0, category: category || null, items: items.map(it => ({ productNo: it.productNo && it.productNo.trim() ? it.productNo : randomProductNo(), productName: it.productName, brand: it.brand, model: it.model, quantity: Number(it.quantity) || 1, costPrice: Number(it.costPrice) || 0, validity: it.validity, imes: Array.isArray(it.imes) ? it.imes.filter(x => x && x.trim()) : [] })) }) }); const data = await res.json(); if (!res.ok || !data.success) throw new Error(data.message || 'Save failed'); setOpen(false); resetModal(); await loadEntries(); } catch (e) { setError(e.message); } finally { setSaving(false); } }; const selectedSupplierTotal = React.useMemo(() => { if (!supplierId) return 0; const fromEntries = entries.reduce((sum, e) => { const sid = e.supplier_id?._id || e.supplier_id; if (sid !== supplierId) return sum; return sum + (Number(e.supplierAmount) || 0); }, 0); return fromEntries; }, [entries, supplierId, open]); const handleProductValidity = () => { const today = new Date(); const data = entries.flatMap(entry => { return entry.items .filter(item => { if (!item.validity) return false; const validityDate = new Date(item.validity); const diffInDays = Math.ceil((validityDate - today) / (1000 * 60 * 60 * 24)); return diffInDays > 0 && diffInDays <= 7; }) .map(item => ({ productNo: item.productNo || 'N/A', validityDate: new Date(item.validity).toLocaleDateString() })); }); setValidityData(data); setValidityPopup(true); setShowRedDot(false); // Hide red dot when popup is opened }; return (
{/* Page Header */}

Inventory Management

Manage your product inventory with ease

{/* Statistics Cards */}
đŸ“Ļ
Total Products
{entries.reduce((sum, e) => sum + (Array.isArray(e.items) ? e.items.length : 0), 0)}
Unique items in stock
📊
Total Quantity
{entries.reduce((sum, e) => sum + (Array.isArray(e.items) ? e.items.reduce((itemSum, it) => itemSum + (Number(it.quantity) || 0), 0) : 0), 0)}
Items available
{/* Action Section */}

Quick Actions

Manage your inventory operations

{/* Filter Section */}

🔍 Filter Inventory

handleFilterChange('productNo', e.target.value)} style={{ width: '100%', padding: '12px 16px', border: '2px solid #e5e7eb', borderRadius: '8px', fontSize: '14px', transition: 'border-color 0.2s ease', outline: 'none' }} onFocus={(e) => e.target.style.borderColor = '#667eea'} onBlur={(e) => e.target.style.borderColor = '#e5e7eb'} />
handleFilterChange('productName', e.target.value)} style={{ width: '100%', padding: '12px 16px', border: '2px solid #e5e7eb', borderRadius: '8px', fontSize: '14px', transition: 'border-color 0.2s ease', outline: 'none' }} onFocus={(e) => e.target.style.borderColor = '#667eea'} onBlur={(e) => e.target.style.borderColor = '#e5e7eb'} />
handleFilterChange('brand', e.target.value)} style={{ width: '100%', padding: '12px 16px', border: '2px solid #e5e7eb', borderRadius: '8px', fontSize: '14px', transition: 'border-color 0.2s ease', outline: 'none' }} onFocus={(e) => e.target.style.borderColor = '#667eea'} onBlur={(e) => e.target.style.borderColor = '#e5e7eb'} />
handleFilterChange('model', e.target.value)} style={{ width: '100%', padding: '12px 16px', border: '2px solid #e5e7eb', borderRadius: '8px', fontSize: '14px', transition: 'border-color 0.2s ease', outline: 'none' }} onFocus={(e) => e.target.style.borderColor = '#667eea'} onBlur={(e) => e.target.style.borderColor = '#e5e7eb'} />
handleFilterChange('productDays', e.target.value)} style={{ width: '100%', padding: '12px 16px', border: '2px solid #e5e7eb', borderRadius: '8px', fontSize: '14px', transition: 'border-color 0.2s ease', outline: 'none' }} onFocus={(e) => e.target.style.borderColor = '#667eea'} onBlur={(e) => e.target.style.borderColor = '#e5e7eb'} />
handleFilterChange('quantity', e.target.value)} style={{ width: '100%', padding: '12px 16px', border: '2px solid #e5e7eb', borderRadius: '8px', fontSize: '14px', transition: 'border-color 0.2s ease', outline: 'none' }} onFocus={(e) => e.target.style.borderColor = '#667eea'} onBlur={(e) => e.target.style.borderColor = '#e5e7eb'} />
{/* Inventory Table */}

📋 Master Inventory {showRedDot && ( )}

Complete list of all products in your inventory

{filteredEntries.length === 0 ? (
đŸ“Ļ
No Inventory Items
No items match the filter criteria
) : (
{filteredEntries.flatMap((e) => ( (Array.isArray(e.items) ? e.items : []).map((it, idx) => ( e.target.parentElement.style.backgroundColor = '#f8fafc'} onMouseOut={(e) => e.target.parentElement.style.backgroundColor = 'transparent'} > )) ))}
Supplier Product No Product Name Brand Model Qty Total Qty IMEI Count Cost Price Validity Days in Stock Created
{e.supplier_id?.supplierName || 'Unknown Supplier'}
{e.supplier_id?.agencyName || '-'}
{it.productNo || '-'}
{it.productName || '-'}
{it.brand || '-'} {it.model || '-'} {it.quantity ?? 0} {it.totalQuantity ?? it.quantity ?? 0} {Array.isArray(it.imes) && it.imes.length ? ( {it.imes.length} IMEIs ) : ( - )} {new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', maximumFractionDigits: 2 }).format(it.costPrice || 0)}
{it.validity ? new Date(it.validity).toLocaleDateString() : '-'}
{(() => { try { const created = new Date(e.createdAt); if (isNaN(created.getTime())) return '-'; const msPerDay = 1000 * 60 * 60 * 24; const days = Math.floor((Date.now() - created.getTime()) / msPerDay) + 1; return `${days} day${days !== 1 ? 's' : ''}`; } catch (err) { return '-'; } })()} {new Date(e.createdAt).toLocaleDateString()}
{new Date(e.createdAt).toLocaleTimeString()}
)}
{open && (

đŸ“Ļ Add New Stock

Add products to your inventory

{ if(e.key==='Enter'){ e.preventDefault(); if(canSubmit && !saving) submit(); } }}> {error && (
{error}
)} {/* Basic Information Section */}

â„šī¸ Basic Information

setSupplierAmount(e.target.value)} placeholder="0.00" style={{ width: '100%', padding: '12px 16px', border: '2px solid #e5e7eb', borderRadius: '8px', fontSize: '14px', outline: 'none' }} />
setGstAmount(e.target.value)} placeholder="0.00" style={{ width: '100%', padding: '12px 16px', border: '2px solid #e5e7eb', borderRadius: '8px', fontSize: '14px', outline: 'none' }} />
{/* Products Section */}

📋 Product Details

{category === 'Mobile' && ( )} {items.map((it, idx) => ( {category === 'Mobile' && ( )} ))}
Product No Product Name * Brand Model Qty Cost Price ValidityIMEI Numbers
updateItem(idx,'productNo',e.target.value)} placeholder="Auto-generated" style={{ width: '100%', padding: '8px 12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '13px', outline: 'none' }} /> updateItem(idx,'productName',e.target.value)} placeholder="Enter product name" style={{ width: '100%', padding: '8px 12px', border: '2px solid #d1d5db', borderRadius: '6px', fontSize: '13px', outline: 'none' }} /> updateItem(idx,'brand',e.target.value)} placeholder="Brand" style={{ width: '100%', padding: '8px 12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '13px', outline: 'none' }} /> updateItem(idx,'model',e.target.value)} placeholder="Model" style={{ width: '100%', padding: '8px 12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '13px', outline: 'none' }} /> updateItem(idx,'quantity',e.target.value)} placeholder="1" min="1" style={{ width: '100%', padding: '8px 12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '13px', textAlign: 'center', outline: 'none' }} /> updateItem(idx,'costPrice',e.target.value)} placeholder="0.00" step="0.01" style={{ width: '100%', padding: '8px 12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '13px', textAlign: 'right', outline: 'none' }} /> updateItem(idx,'validity',e.target.value)} style={{ width: '100%', padding: '8px 12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '13px', outline: 'none' }} />
{(() => { const qty = Number(it.quantity) || 1; const imesArr = Array.isArray(it.imes) && it.imes.length ? it.imes.slice(0, qty) : Array.from({ length: qty }, () => ''); return imesArr.map((im, iim) => ( { const val = e.target.value; setItems(list => list.map((row, rIdx) => { if (rIdx !== idx) return row; const qtyLocal = Number(row.quantity) || 1; const newImes = Array.isArray(row.imes) ? row.imes.slice(0, qtyLocal) : Array.from({ length: qtyLocal }, () => ''); while (newImes.length < qtyLocal) newImes.push(''); newImes[iim] = val; return { ...row, imes: newImes }; })); }} placeholder={`IMEI ${iim+1}`} style={{ width: '140px', padding: '6px 8px', border: '1px solid #d1d5db', borderRadius: '4px', fontSize: '12px', outline: 'none' }} /> )); })()}
{/* Summary Section */}

💰 Amount Summary

Product Amount
₹{items.map(it => (Number(it.quantity) || 0) * (Number(it.costPrice) || 0)).reduce((sum, amt) => sum + amt, 0).toLocaleString()}
Quantity × Cost Price
Total Bill Amount
₹{((Number(supplierAmount) || 0) + (Number(gstAmount) || 0)).toLocaleString()}
Supplier Amount + GST Amount
{/* Modal Footer */}
)} {validityPopup && (

📅 Product Validity Alert

Products expiring within 7 days

{validityData.length === 0 ? (
✅
All Products Fresh!
No products are expiring within the next 7 days
) : (
âš ī¸
{validityData.length} product{validityData.length > 1 ? 's' : ''} expiring soon
Please review and take necessary action
{validityData.map((vd, idx) => (
e.target.style.transform = 'translateY(-2px)'} onMouseOut={(e) => e.target.style.transform = 'translateY(0px)'} >
{vd.productNo}
📅 Expires: {vd.validityDate}
))}
)}
)}
); }