function SalesTrack({ salesUrl, token }) { const [rows, setRows] = React.useState([]); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(''); const [previewHtml, setPreviewHtml] = React.useState(''); const [showPreview, setShowPreview] = React.useState(false); const [dateFilter, setDateFilter] = React.useState(''); const [customerFilter, setCustomerFilter] = React.useState(''); const [imeFilter, setImeFilter] = React.useState(''); const load = async () => { try { setLoading(true); setError(''); const url = new URL(salesUrl + '/api/sales'); // if branch token is used server should infer branch; include branch param for safety const res = await fetch(url, { headers: { Authorization: 'Bearer ' + token } }); const data = await res.json(); if (!res.ok) throw new Error(data.message || 'Failed to load sales'); setRows(Array.isArray(data.sales) ? data.sales : []); } catch (e) { setError(e.message || 'Failed'); } finally { setLoading(false); } }; React.useEffect(() => { load(); }, []); // decode JWT payload to extract branch/shop info when branch endpoint isn't available function decodeJwt(tk) { try { const theToken = tk || token || localStorage.getItem('branch_token') || localStorage.getItem('sales_token') || ''; const parts = theToken.split('.'); if (parts.length < 2) return {}; const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/'); const json = decodeURIComponent(atob(base64).split('').map(function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); return JSON.parse(json); } catch (e) { return {}; } } async function getBranchInfo() { let branchName = ''; let branchContact = ''; let branchGst = ''; let branchAddress = ''; try { const res = await fetch(new URL(salesUrl + '/api/branches'), { headers: { Authorization: 'Bearer ' + token } }); const data = await res.json(); if (res.ok && Array.isArray(data.branches) && data.branches.length > 0) { const payload = decodeJwt(); const branchId = payload?.branch_id || payload?._id || ''; let found = null; if (branchId) found = data.branches.find(b => String(b._id) === String(branchId)); if (!found) found = data.branches[0]; branchName = found?.name || ''; branchContact = found?.phoneNumber || found?.phone || ''; branchGst = found?.gstNo || found?.gst || ''; branchAddress = found?.address || found?.branchAddress || ''; } } catch (e) { /* ignore */ } if (!branchName || !branchContact) { const payload = decodeJwt(); branchName = branchName || payload.shopName || payload.name || payload.branchName || ''; branchContact = branchContact || payload.phone || payload.phoneNumber || payload.branchPhone || ''; branchGst = branchGst || payload.gstNo || payload.gst || ''; branchAddress = branchAddress || payload.address || payload.branchAddress || ''; } return { branchName, branchContact, branchGst, branchAddress }; } function buildReceiptHtml(sale, branchName, branchContact, stock, branchGst, branchAddress) { const items = (sale.items || []).map(i => { // resolve from stock when available const found = (stock || []).find(p => (String(p._id) && String(p._id) === String(i.productId || i._id)) || (p.productId && String(p.productId) === String(i.productId)) || (p.productNo && i.productNo && String(p.productNo) === String(i.productNo))); const name = found?.productName || found?.name || i.productName || i.productNo || ''; // Extract IMEI numbers from the item const imes = Array.isArray(i.imes) ? i.imes : (Array.isArray(i.selectedImes) ? i.selectedImes : []); const imeiText = imes.length > 0 ? imes.map(imei => `IMEI: ${imei}`).join(', ') : ''; // Combine product name with IMEI information const productDescription = imeiText ? `${name}\n${imeiText}` : name; const qty = Number(i.qty || i.sellingQty || 0); const unit = Number(found?.sellingPrice ?? found?.unitSellingPrice ?? i.sellingPrice ?? 0).toFixed(2); const line = (qty * Number(unit)).toFixed(2); return { name: productDescription, qty, unit, line, hasImei: imes.length > 0 }; }); // build table rows with S.no, Description (wide), HSN (empty), Qty, Rate, Amount const itemsRows = items.map((it, idx) => ` ${idx+1} ${it.name.replace(/\n/g, '
')}   ${it.qty} ${it.unit} ${it.line} `).join(''); // GST details const cgstPercent = sale.cgst || sale.cgstPercent || 0; const sgstPercent = sale.sgst || sale.sgstPercent || 0; const igstPercent = sale.igst || sale.igstPercent || 0; const cgstAmt = sale.cgstAmount ?? 0; const sgstAmt = sale.sgstAmount ?? 0; const igstAmt = sale.igstAmount ?? 0; const subTotal = sale.subTotal ?? items.reduce((s, it) => s + Number(it.line), 0); const discount = sale.discount ?? 0; const discountAmount = sale.discountAmount ?? 0; const taxable = sale.taxableAmount ?? Math.max(0, subTotal - discountAmount); const total = Number(sale.totalAmount ?? (taxable + cgstAmt + sgstAmt + igstAmt)).toFixed(2); const date = new Date(sale.createdAt || Date.now()).toLocaleString(); let gstLines = ''; if (cgstPercent > 0) gstLines += `
CGST ${cgstPercent}%: ${Number(cgstAmt).toFixed(2)}
`; if (sgstPercent > 0) gstLines += `
SGST ${sgstPercent}%: ${Number(sgstAmt).toFixed(2)}
`; if (igstPercent > 0) gstLines += `
IGST ${igstPercent}%: ${Number(igstAmt).toFixed(2)}
`; if (gstLines) gstLines += `
`; // Build a bordered invoice that matches provided layout const outSubTotal = Number(subTotal || 0); const outDiscount = Number(discountAmount || 0); const outTaxable = Number(taxable || Math.max(0, outSubTotal - outDiscount)); const outTotal = Number(total || 0).toFixed(2); const html = `Invoice` + `
πŸ“‹ GSTIN: ${branchGst || 'N/A'}
πŸ“ž ${branchContact || 'Contact N/A'}
` + `
πŸ’° CASH RECEIPT πŸ’°
` + `
${branchName || 'Branch Name'}
` + `
πŸ“ ${branchAddress || 'Branch Address'}
` + `
πŸ‘€ Customer: ${sale.customerName || 'Walk-in Customer'}
` + `
πŸ“± Phone: ${sale.customerNo || 'N/A'}     πŸ“… Date: ${date}
` + `${itemsRows}
# πŸ“¦ Product Details HSN Qty Rate Amount
` + `` + `` + (discount ? `` : '') + `` + (cgstPercent > 0 ? `` : '') + (sgstPercent > 0 ? `` : '') + (igstPercent > 0 ? `` : '') + `` + `
πŸ“Š Sub Total:β‚Ή ${outSubTotal.toFixed(2)}
🏷️ Discount (${discount}%):- β‚Ή ${outDiscount.toFixed(2)}
πŸ’΅ Taxable Amount:β‚Ή ${outTaxable.toFixed(2)}
πŸ›οΈ CGST ${cgstPercent}%:β‚Ή ${Number(cgstAmt).toFixed(2)}
πŸ›οΈ SGST ${sgstPercent}%:β‚Ή ${Number(sgstAmt).toFixed(2)}
πŸ›οΈ IGST ${igstPercent}%:β‚Ή ${Number(igstAmt).toFixed(2)}
πŸ’° GRAND TOTAL:β‚Ή ${outTotal}
` + `
πŸ™ Thank you for your business! πŸ™
Visit again soon!
` + ``; return html; } // Filtering logic for sales rows const filteredRows = React.useMemo(() => { return rows.filter(s => { const saleDate = new Date(s.createdAt); const filterDate = dateFilter ? new Date(dateFilter) : null; const dateMatch = !filterDate || (saleDate.toDateString() === filterDate.toDateString()); // customer filter const customerMatch = !customerFilter || (s.customerNo || '').toLowerCase().includes(customerFilter.toLowerCase()); // ime filter: if provided, ensure at least one item in sale has this IME in its imes array const ime = (imeFilter || '').toString().trim(); let imeMatch = true; if (ime) { imeMatch = (s.items || []).some(it => { const ims = Array.isArray(it.imes) ? it.imes : (Array.isArray(it.selectedImes) ? it.selectedImes : []); return ims.some(x => String(x).toLowerCase().includes(ime.toLowerCase())); }); } return dateMatch && customerMatch && imeMatch; }); }, [rows, dateFilter, customerFilter, imeFilter]); return (
{showPreview ? (
setShowPreview(false)}>
e.stopPropagation()}>
Receipt Preview
) : null}

Sales History

{error ?
{error}
: null} {loading ?
Loading…
: (
{/* Filter Section */}
setDateFilter(e.target.value)} style={{ padding: '8px', width: '180px' }} /> setCustomerFilter(e.target.value)} style={{ padding: '8px', width: '180px' }} /> setImeFilter(e.target.value)} style={{ padding: '8px', width: '180px' }} />
{filteredRows.map(s => ( ))} {filteredRows.length === 0 ? ( ) : null}
Date Customer Product Discount Total Payment Actions
{new Date(s.createdAt).toLocaleString()} {s.customerNo || '-'} {(s.items || []).map(i => { const ims = Array.isArray(i.imes) ? i.imes : (Array.isArray(i.selectedImes) ? i.selectedImes : []); const imeCountStr = ims && ims.length ? ` [IMEs:${ims.length}]` : ''; return `${i.productName || i.productNo || 'item'} x${i.qty || i.sellingQty || 0}${imeCountStr}`; }).join(', ')} {s.discount ? `${s.discount}% (${Number(s.discountAmount||0).toFixed(2)})` : '-'} {Number(s.totalAmount || 0).toFixed(2)} {s.paymentMethod || '-'}
No sales found
)}
); } window.SalesTrack = SalesTrack;