fix(owner): replace native Team Management select with custom dark-mode-aware project picker
This commit is contained in:
@@ -101,3 +101,46 @@ export function stormAttribution(kanbanLeads = [], projects = []) {
|
||||
revenuePct: totalRevenue ? Math.round((stormRevenue / totalRevenue) * 100) : 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Owner Financial-Overview metrics, each carrying the underlying line items so the dashboard
|
||||
// cards can drill down to the real records. Pass `today` (ISO date) to flag payouts due soon.
|
||||
// - collected: cash received from clients (paid paymentSchedule entries)
|
||||
// - outstandingAR: client money still owed (unpaid paymentSchedule entries)
|
||||
// - pendingPayouts: money owed to vendors (pending invoices); dueSoon = within 7 days of `today`
|
||||
// - vendorSpend: cash paid out to vendors (paid invoices)
|
||||
export function financialOverview(projects = [], today) {
|
||||
const now = today ? new Date(today) : null;
|
||||
const within7 = (d) => {
|
||||
if (!now || !d) return false;
|
||||
const diff = (new Date(d) - now) / 86400000;
|
||||
return diff >= 0 && diff <= 7;
|
||||
};
|
||||
const collected = { total: 0, items: [] };
|
||||
const outstandingAR = { total: 0, count: 0, items: [] };
|
||||
const pendingPayouts = { total: 0, dueSoonCount: 0, dueSoonTotal: 0, items: [] };
|
||||
const vendorSpend = { total: 0, items: [] };
|
||||
for (const p of projects) {
|
||||
const project = p.customerName || p.projectType || p.id;
|
||||
for (const ps of p.paymentSchedule || []) {
|
||||
if (ps.status === 'paid') {
|
||||
collected.total += ps.amount || 0;
|
||||
collected.items.push({ projectId: p.id, project, label: ps.milestone, amount: ps.amount || 0, date: ps.paidDate || ps.dueDate });
|
||||
} else {
|
||||
outstandingAR.total += ps.amount || 0;
|
||||
outstandingAR.count += 1;
|
||||
outstandingAR.items.push({ projectId: p.id, project, label: ps.milestone, amount: ps.amount || 0, dueDate: ps.dueDate });
|
||||
}
|
||||
}
|
||||
for (const inv of p.invoices || []) {
|
||||
if (inv.status === 'pending') {
|
||||
pendingPayouts.total += inv.amount || 0;
|
||||
pendingPayouts.items.push({ projectId: p.id, project, label: inv.submittedBy, amount: inv.amount || 0, dueDate: inv.dueDate });
|
||||
if (within7(inv.dueDate)) { pendingPayouts.dueSoonCount += 1; pendingPayouts.dueSoonTotal += inv.amount || 0; }
|
||||
} else if (inv.status === 'paid') {
|
||||
vendorSpend.total += inv.amount || 0;
|
||||
vendorSpend.items.push({ projectId: p.id, project, label: inv.submittedBy, amount: inv.amount || 0, date: inv.datePaid });
|
||||
}
|
||||
}
|
||||
}
|
||||
return { collected, outstandingAR, pendingPayouts, vendorSpend };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user