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 (
Manage your product inventory with ease
Manage your inventory operations
Complete list of all products in your inventory
| 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()}
|
Add products to your inventory
| Product No | Product Name * | Brand | Model | Qty | Cost Price | Validity | {category === 'Mobile' && (IMEI 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' }} /> | {category === 'Mobile' && (
{(() => {
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'
}}
/>
));
})()}
|
)}
Products expiring within 7 days