function MobileBranchExpense({ salesUrl, token, adminUrl, branchUser }) { const [form, setForm] = React.useState({ title: '', amount: '', date: '', bank_id: '', branch_id: '' }); const [banks, setBanks] = React.useState([]); const [branches, setBranches] = React.useState([]); const [selectedBank, setSelectedBank] = React.useState(null); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(''); const [rows, setRows] = React.useState([]); const [sales, setSales] = React.useState([]); const [selectedDate, setSelectedDate] = React.useState(''); const [summary, setSummary] = React.useState({ salesRevenue: 0, stockRevenue: 0, totalRevenue: 0, totalExpense: 0, netRevenue: 0 }); const currency = (n) => new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', maximumFractionDigits: 2 }).format(n || 0); const onChange = (e) => { const { name, value } = e.target; setForm((prev) => ({ ...prev, [name]: value })); }; const load = React.useCallback(async () => { try { setError(''); const url = new URL(salesUrl + '/api/branch-expenses'); if (form.branch_id) url.searchParams.set('branch_id', form.branch_id); 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 expenses'); setRows(Array.isArray(data.expenses) ? data.expenses : []); } catch (e) { setError(e.message || 'Failed to load expenses'); } }, [salesUrl, token, form.branch_id]); React.useEffect(() => { load(); }, [load]); React.useEffect(() => { const fetchBanks = async () => { try { const url = new URL(salesUrl + '/api/banks'); 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 banks'); setBanks(Array.isArray(data.banks) ? data.banks : []); } catch (_e) { setBanks([]); } }; fetchBanks(); }, [salesUrl, token]); React.useEffect(() => { const fetchBranches = async () => { try { const url = new URL(salesUrl + '/api/branches'); 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 branches'); setBranches(Array.isArray(data.branches) ? data.branches : []); } catch (_e) { setBranches([]); } }; // Desktop loads branches for admins; branch users don't need selector if (!branchUser) fetchBranches(); }, [salesUrl, token, branchUser]); React.useEffect(() => { if (!form.bank_id) { setSelectedBank(null); return; } const bank = banks.find((b) => b._id === form.bank_id); setSelectedBank(bank || null); }, [form.bank_id, banks]); React.useEffect(() => { const loadSales = async () => { try { const url = new URL(salesUrl + '/api/sales'); url.searchParams.set('pageSize', '200'); if (form.branch_id) url.searchParams.set('branch_id', form.branch_id); const res = await fetch(url, { headers: { Authorization: 'Bearer ' + token } }); const data = await res.json(); if (res.ok) setSales(Array.isArray(data.sales) ? data.sales : []); } catch (_e) { // ignore } }; loadSales(); }, [salesUrl, token, form.branch_id]); const getRangeForDay = (day) => { if (!day) return null; const parts = String(day).split('-').map(Number); const start = new Date(parts[0], parts[1] - 1, parts[2], 0, 0, 0, 0); const end = new Date(parts[0], parts[1] - 1, parts[2], 23, 59, 59, 999); return { start, end }; }; const filteredRows = React.useMemo(() => { const range = selectedDate ? getRangeForDay(selectedDate) : null; if (!range) return rows || []; return (rows || []).filter((r) => { const t = r.date ? new Date(r.date) : (r.createdAt ? new Date(r.createdAt) : null); return t && t >= range.start && t <= range.end; }); }, [rows, selectedDate]); React.useEffect(() => { try { let range = null; if (selectedDate) { range = getRangeForDay(selectedDate); } else { const start = new Date(); start.setHours(0, 0, 0, 0); const end = new Date(); end.setHours(23, 59, 59, 999); range = { start, end }; } const todaysSales = (sales || []).filter((s) => { const t = s.createdAt ? new Date(s.createdAt) : null; return t && t >= range.start && t <= range.end; }); const salesRevenue = todaysSales.reduce((sum, x) => sum + (Number(x.totalAmount) || 0), 0); let stockRevenue = 0; for (const s of todaysSales) { const items = Array.isArray(s.items) ? s.items : []; for (const it of items) { const qty = Number(it.qty || it.sellingQty || 0); const costUnit = Number(it.costPrice || it.cost || it.unitCost || 0); const totalCost = Number(it.totalCostPrice || (costUnit * qty) || 0); stockRevenue += totalCost; } } const totalExpense = (filteredRows || []).reduce((sum, r) => sum + (Number(r.amount) || 0), 0); const totalRevenue = salesRevenue - stockRevenue; const netRevenue = totalRevenue - totalExpense; setSummary({ salesRevenue, stockRevenue, totalRevenue, totalExpense, netRevenue }); } catch (_e) { // ignore } }, [sales, filteredRows, selectedDate]); const submit = async (e) => { e.preventDefault(); setError(''); const amt = Number(form.amount); if (!form.title || String(form.title).trim() === '') return setError('Enter an expense title'); if (!amt || amt <= 0) return setError('Enter a valid amount'); if (!form.bank_id) return setError('Select a bank'); if (!selectedBank) return setError('Invalid bank selected'); if (amt > Number(selectedBank.accountBalance)) return setError('Amount exceeds selected bank balance'); setLoading(true); try { const postTitle = form.title; const postDate = form.date || new Date().toISOString(); const postBranchId = form.branch_id || undefined; const res = await fetch(salesUrl + '/api/branch-expenses', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token }, body: JSON.stringify({ title: postTitle, amount: amt, date: postDate, bank_id: form.bank_id, branch_id: postBranchId }) }); const data = await res.json(); if (!res.ok) throw new Error(data.message || 'Create failed'); setForm((prev) => ({ ...prev, title: '', amount: '', date: '' })); await load(); if (adminUrl) { try { const adminBody = { shop_id: (data.expense && data.expense.shop_id) || (window && window.shopId) || '', title: postTitle, amount: amt, createdAt: postDate }; fetch((adminUrl || '') + '/api/expenses/add', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token }, body: JSON.stringify(adminBody) }).catch(() => {}); } catch (_e) {} } try { window.dispatchEvent(new Event('branch-expense-created')); } catch (_e) {} } catch (e2) { setError(e2.message || 'Failed to create expense'); } finally { setLoading(false); } }; const todayISO = () => { const d = new Date(); const yyyy = d.getFullYear(); const mm = String(d.getMonth() + 1).padStart(2, '0'); const dd = String(d.getDate()).padStart(2, '0'); return `${yyyy}-${mm}-${dd}`; }; return (
💸 Expenses
Add and track expenses with daily summary
setSelectedDate(e.target.value)} />
{!branchUser ? (
) : null}
{branchUser ? (
Sales Revenue
{currency(summary.salesRevenue)}
) : null} {branchUser ? (
Net Revenue
{currency(summary.netRevenue)}
) : null}
Total Expense {selectedDate ? '(filtered)' : '(today)'}
{currency(summary.totalExpense)}
Add Expense
{selectedBank ? (
Balance: ₹{Number(selectedBank.accountBalance || 0).toLocaleString()}
) : null}
{error ? (
{error}
) : null}
Recent Expenses
{filteredRows.length} record{filteredRows.length === 1 ? '' : 's'}
{filteredRows.length === 0 ? (
💸
No expenses found for selected date
) : (
{filteredRows.map((r, i) => { const dt = r.date ? new Date(r.date) : (r.createdAt ? new Date(r.createdAt) : null); return (
{r.title || '-'}
{dt ? dt.toLocaleString() : '-'}
{currency(Number(r.amount) || 0)}
#{i + 1}
{selectedDate ? 'Filtered' : 'Today view'}
); })}
)}
); } window.MobileBranchExpense = MobileBranchExpense;