Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
c1b5a78ee8
commit
5a37fbbd54
@@ -65,20 +65,52 @@ function InvoiceDetail() {
|
||||
|
||||
useEffect(() => { load(); }, [invoiceId]);
|
||||
|
||||
// Subsection keys & labels — order matters for display.
|
||||
const SUBSECTIONS: { key: string; label: string; isExpense: boolean; billable: boolean }[] = [
|
||||
{ key: "bill_fee", label: "Billable Fees", isExpense: false, billable: true },
|
||||
{ key: "bill_exp", label: "Billable Expenses", isExpense: true, billable: true },
|
||||
{ key: "nb_fee", label: "Non-Billable Fees", isExpense: false, billable: false },
|
||||
{ key: "nb_exp", label: "Non-Billable Expenses", isExpense: true, billable: false },
|
||||
];
|
||||
|
||||
const classifyItem = (it: any): string => {
|
||||
const isExpense = it.kind === "expense";
|
||||
const isNonBillable = Number(it.amount) === 0 && (it.time_entry_id || it.expense_id);
|
||||
if (isExpense) return isNonBillable ? "nb_exp" : "bill_exp";
|
||||
return isNonBillable ? "nb_fee" : "bill_fee";
|
||||
};
|
||||
|
||||
const groups = useMemo(() => {
|
||||
const map = new Map<string, { caseRow: any; items: any[]; subtotal: number }>();
|
||||
const map = new Map<
|
||||
string,
|
||||
{
|
||||
caseRow: any;
|
||||
items: any[];
|
||||
subtotal: number;
|
||||
subsections: { key: string; label: string; billable: boolean; items: any[]; subtotal: number }[];
|
||||
}
|
||||
>();
|
||||
for (const it of items) {
|
||||
const key = it.case?.id ?? "_none";
|
||||
if (!map.has(key)) map.set(key, { caseRow: it.case, items: [], subtotal: 0 });
|
||||
if (!map.has(key)) {
|
||||
map.set(key, {
|
||||
caseRow: it.case,
|
||||
items: [],
|
||||
subtotal: 0,
|
||||
subsections: SUBSECTIONS.map((s) => ({ key: s.key, label: s.label, billable: s.billable, items: [], subtotal: 0 })),
|
||||
});
|
||||
}
|
||||
const g = map.get(key)!;
|
||||
g.items.push(it);
|
||||
// Only billable rows contribute to the case subtotal. A line is
|
||||
// non-billable when it references a source time/expense entry but its
|
||||
// amount is zero (the generator stores it that way for transparency).
|
||||
const isNonBillable =
|
||||
Number(it.amount) === 0 && (it.time_entry_id || it.expense_id);
|
||||
if (!isNonBillable) g.subtotal += Number(it.amount);
|
||||
const subKey = classifyItem(it);
|
||||
const sub = g.subsections.find((s) => s.key === subKey)!;
|
||||
sub.items.push(it);
|
||||
const amt = Number(it.amount);
|
||||
sub.subtotal += amt;
|
||||
if (sub.billable) g.subtotal += amt;
|
||||
}
|
||||
// Drop empty subsections
|
||||
for (const g of map.values()) g.subsections = g.subsections.filter((s) => s.items.length > 0);
|
||||
return Array.from(map.values());
|
||||
}, [items]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user