@powerhousedao/contributor-billing 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/document-models/expense-report/gen/actions.d.ts +4 -0
- package/dist/document-models/expense-report/gen/actions.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/actions.js +1 -0
- package/dist/document-models/expense-report/gen/creators.d.ts +2 -0
- package/dist/document-models/expense-report/gen/creators.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/creators.js +1 -0
- package/dist/document-models/expense-report/gen/document-model.d.ts +3 -0
- package/dist/document-models/expense-report/gen/document-model.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/document-model.js +202 -0
- package/dist/document-models/expense-report/gen/expense-report/actions.d.ts +8 -0
- package/dist/document-models/expense-report/gen/expense-report/actions.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/expense-report/actions.js +1 -0
- package/dist/document-models/expense-report/gen/expense-report/creators.d.ts +4 -0
- package/dist/document-models/expense-report/gen/expense-report/creators.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/expense-report/creators.js +3 -0
- package/dist/document-models/expense-report/gen/expense-report/error.d.ts +2 -0
- package/dist/document-models/expense-report/gen/expense-report/error.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/expense-report/error.js +1 -0
- package/dist/document-models/expense-report/gen/expense-report/object.d.ts +7 -0
- package/dist/document-models/expense-report/gen/expense-report/object.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/expense-report/object.js +7 -0
- package/dist/document-models/expense-report/gen/expense-report/operations.d.ts +7 -0
- package/dist/document-models/expense-report/gen/expense-report/operations.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/expense-report/operations.js +1 -0
- package/dist/document-models/expense-report/gen/index.d.ts +8 -0
- package/dist/document-models/expense-report/gen/index.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/index.js +6 -0
- package/dist/document-models/expense-report/gen/object.d.ts +15 -0
- package/dist/document-models/expense-report/gen/object.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/object.js +25 -0
- package/dist/document-models/expense-report/gen/ph-factories.d.ts +27 -0
- package/dist/document-models/expense-report/gen/ph-factories.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/ph-factories.js +189 -0
- package/dist/document-models/expense-report/gen/reducer.d.ts +5 -0
- package/dist/document-models/expense-report/gen/reducer.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/reducer.js +76 -0
- package/dist/document-models/expense-report/gen/schema/index.d.ts +3 -0
- package/dist/document-models/expense-report/gen/schema/index.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/schema/index.js +2 -0
- package/dist/document-models/expense-report/gen/schema/types.d.ts +254 -0
- package/dist/document-models/expense-report/gen/schema/types.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/schema/types.js +1 -0
- package/dist/document-models/expense-report/gen/schema/zod.d.ts +32 -0
- package/dist/document-models/expense-report/gen/schema/zod.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/schema/zod.js +216 -0
- package/dist/document-models/expense-report/gen/types.d.ts +10 -0
- package/dist/document-models/expense-report/gen/types.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/types.js +1 -0
- package/dist/document-models/expense-report/gen/utils.d.ts +22 -0
- package/dist/document-models/expense-report/gen/utils.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/utils.js +181 -0
- package/dist/document-models/expense-report/gen/wallet/actions.d.ts +64 -0
- package/dist/document-models/expense-report/gen/wallet/actions.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/wallet/actions.js +1 -0
- package/dist/document-models/expense-report/gen/wallet/creators.d.ts +18 -0
- package/dist/document-models/expense-report/gen/wallet/creators.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/wallet/creators.js +17 -0
- package/dist/document-models/expense-report/gen/wallet/error.d.ts +2 -0
- package/dist/document-models/expense-report/gen/wallet/error.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/wallet/error.js +1 -0
- package/dist/document-models/expense-report/gen/wallet/object.d.ts +21 -0
- package/dist/document-models/expense-report/gen/wallet/object.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/wallet/object.js +49 -0
- package/dist/document-models/expense-report/gen/wallet/operations.d.ts +21 -0
- package/dist/document-models/expense-report/gen/wallet/operations.d.ts.map +1 -0
- package/dist/document-models/expense-report/gen/wallet/operations.js +1 -0
- package/dist/document-models/expense-report/index.d.ts +39 -0
- package/dist/document-models/expense-report/index.d.ts.map +1 -0
- package/dist/document-models/expense-report/index.js +21 -0
- package/dist/document-models/expense-report/src/reducers/wallet.d.ts +3 -0
- package/dist/document-models/expense-report/src/reducers/wallet.d.ts.map +1 -0
- package/dist/document-models/expense-report/src/reducers/wallet.js +180 -0
- package/dist/document-models/expense-report/src/tests/document-model.test.d.ts +6 -0
- package/dist/document-models/expense-report/src/tests/document-model.test.d.ts.map +1 -0
- package/dist/document-models/expense-report/src/tests/document-model.test.js +18 -0
- package/dist/document-models/expense-report/src/tests/expense-report.test.d.ts +6 -0
- package/dist/document-models/expense-report/src/tests/expense-report.test.d.ts.map +1 -0
- package/dist/document-models/expense-report/src/tests/expense-report.test.js +24 -0
- package/dist/document-models/expense-report/src/tests/wallet.test.d.ts +6 -0
- package/dist/document-models/expense-report/src/tests/wallet.test.d.ts.map +1 -0
- package/dist/document-models/expense-report/src/tests/wallet.test.js +24 -0
- package/dist/document-models/expense-report/src/utils.d.ts +2 -0
- package/dist/document-models/expense-report/src/utils.d.ts.map +1 -0
- package/dist/document-models/expense-report/src/utils.js +1 -0
- package/dist/document-models/index.d.ts +1 -0
- package/dist/document-models/index.d.ts.map +1 -1
- package/dist/document-models/index.js +1 -0
- package/dist/document-models/integrations/gen/ph-factories.d.ts.map +1 -1
- package/dist/document-models/integrations/gen/ph-factories.js +2 -14
- package/dist/document-models/integrations/gen/utils.d.ts.map +1 -1
- package/dist/document-models/integrations/gen/utils.js +2 -14
- package/dist/document-models/invoice/gen/ph-factories.d.ts.map +1 -1
- package/dist/document-models/invoice/gen/ph-factories.js +2 -5
- package/dist/document-models/invoice/gen/schema/types.d.ts +1 -1
- package/dist/document-models/invoice/gen/schema/types.d.ts.map +1 -1
- package/dist/document-models/invoice/gen/utils.d.ts.map +1 -1
- package/dist/document-models/invoice/gen/utils.js +1 -4
- package/dist/editors/billing-statement/components/lineItemsTable.d.ts.map +1 -1
- package/dist/editors/billing-statement/components/lineItemsTable.js +71 -13
- package/dist/editors/billing-statement/editor.js +1 -1
- package/dist/editors/contributor-billing/components/DriveExplorer.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/DriveExplorer.js +8 -4
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts +4 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.js +2 -2
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.js +24 -1
- package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts +11 -0
- package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts.map +1 -0
- package/dist/editors/expense-report/components/AddBillingStatementModal.js +195 -0
- package/dist/editors/expense-report/components/AggregatedExpensesTable.d.ts +11 -0
- package/dist/editors/expense-report/components/AggregatedExpensesTable.d.ts.map +1 -0
- package/dist/editors/expense-report/components/AggregatedExpensesTable.js +268 -0
- package/dist/editors/expense-report/components/ExpenseReportPDF.d.ts +10 -0
- package/dist/editors/expense-report/components/ExpenseReportPDF.d.ts.map +1 -0
- package/dist/editors/expense-report/components/ExpenseReportPDF.js +287 -0
- package/dist/editors/expense-report/components/WalletsTable.d.ts +10 -0
- package/dist/editors/expense-report/components/WalletsTable.d.ts.map +1 -0
- package/dist/editors/expense-report/components/WalletsTable.js +164 -0
- package/dist/editors/expense-report/editor.d.ts +2 -0
- package/dist/editors/expense-report/editor.d.ts.map +1 -0
- package/dist/editors/expense-report/editor.js +74 -0
- package/dist/editors/expense-report/hooks/useSyncWallet.d.ts +5 -0
- package/dist/editors/expense-report/hooks/useSyncWallet.d.ts.map +1 -0
- package/dist/editors/expense-report/hooks/useSyncWallet.js +75 -0
- package/dist/editors/expense-report/hooks/useWalletSync.d.ts +9 -0
- package/dist/editors/expense-report/hooks/useWalletSync.d.ts.map +1 -0
- package/dist/editors/expense-report/hooks/useWalletSync.js +77 -0
- package/dist/editors/expense-report/index.d.ts +3 -0
- package/dist/editors/expense-report/index.d.ts.map +1 -0
- package/dist/editors/expense-report/index.js +11 -0
- package/dist/editors/hooks/useExpenseReportDocument.d.ts +4 -0
- package/dist/editors/hooks/useExpenseReportDocument.d.ts.map +1 -0
- package/dist/editors/hooks/useExpenseReportDocument.js +8 -0
- package/dist/editors/index.d.ts +1 -0
- package/dist/editors/index.d.ts.map +1 -1
- package/dist/editors/index.js +1 -0
- package/dist/editors/invoice/components/statusModalComponents.d.ts.map +1 -1
- package/dist/editors/invoice/components/statusModalComponents.js +4 -4
- package/dist/editors/invoice/editor.js +1 -1
- package/dist/editors/invoice/ingestPDF.d.ts.map +1 -1
- package/dist/editors/invoice/ingestPDF.js +3 -3
- package/dist/editors/invoice/invoiceToGnosis.js +1 -1
- package/dist/editors/invoice/requestFinance.js +1 -1
- package/dist/editors/invoice/uploadPdfChunked.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/powerhouse.manifest.json +13 -2
- package/dist/style.css +539 -37
- package/dist/subgraphs/expense-report/index.d.ts +11 -0
- package/dist/subgraphs/expense-report/index.d.ts.map +1 -0
- package/dist/subgraphs/expense-report/index.js +11 -0
- package/dist/subgraphs/expense-report/resolvers.d.ts +3 -0
- package/dist/subgraphs/expense-report/resolvers.d.ts.map +1 -0
- package/dist/subgraphs/expense-report/resolvers.js +252 -0
- package/dist/subgraphs/expense-report/schema.d.ts +3 -0
- package/dist/subgraphs/expense-report/schema.d.ts.map +1 -0
- package/dist/subgraphs/expense-report/schema.js +228 -0
- package/dist/subgraphs/index.d.ts +1 -0
- package/dist/subgraphs/index.d.ts.map +1 -1
- package/dist/subgraphs/index.js +1 -0
- package/package.json +13 -13
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { actions } from "../../../document-models/expense-report/index.js";
|
|
5
|
+
import { Textarea } from "@powerhousedao/document-engineering";
|
|
6
|
+
export function AggregatedExpensesTable({ wallets, groups, periodStart, periodEnd, dispatch, }) {
|
|
7
|
+
// State for active tab (selected wallet)
|
|
8
|
+
const [activeWalletIndex, setActiveWalletIndex] = useState(0);
|
|
9
|
+
// State for editing comments
|
|
10
|
+
const [editingGroupId, setEditingGroupId] = useState(null);
|
|
11
|
+
const [editingComment, setEditingComment] = useState("");
|
|
12
|
+
const [originalComment, setOriginalComment] = useState("");
|
|
13
|
+
// State for editing numeric fields
|
|
14
|
+
const [editingField, setEditingField] = useState(null);
|
|
15
|
+
const [editingValue, setEditingValue] = useState("");
|
|
16
|
+
// Format period for title
|
|
17
|
+
const periodTitle = useMemo(() => {
|
|
18
|
+
if (!periodStart)
|
|
19
|
+
return "Breakdown";
|
|
20
|
+
const date = new Date(periodStart);
|
|
21
|
+
const month = date.toLocaleDateString("en-US", { month: "short" });
|
|
22
|
+
const year = date.getFullYear();
|
|
23
|
+
return `${month} ${year} Breakdown`;
|
|
24
|
+
}, [periodStart]);
|
|
25
|
+
// Create a map of groups with their parent info
|
|
26
|
+
const groupsMap = useMemo(() => {
|
|
27
|
+
const map = new Map();
|
|
28
|
+
groups.forEach((group) => {
|
|
29
|
+
map.set(group.id, { group });
|
|
30
|
+
});
|
|
31
|
+
// Add parent references
|
|
32
|
+
groups.forEach((group) => {
|
|
33
|
+
if (group.parentId) {
|
|
34
|
+
const entry = map.get(group.id);
|
|
35
|
+
const parentEntry = map.get(group.parentId);
|
|
36
|
+
if (entry && parentEntry) {
|
|
37
|
+
entry.parent = parentEntry.group;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return map;
|
|
42
|
+
}, [groups]);
|
|
43
|
+
// Get line items for the active wallet with group information
|
|
44
|
+
// Line items are now already aggregated by category
|
|
45
|
+
const walletLineItems = useMemo(() => {
|
|
46
|
+
if (!wallets[activeWalletIndex])
|
|
47
|
+
return [];
|
|
48
|
+
const wallet = wallets[activeWalletIndex];
|
|
49
|
+
const lineItems = wallet.lineItems || [];
|
|
50
|
+
return lineItems
|
|
51
|
+
.filter((item) => item !== null && item !== undefined)
|
|
52
|
+
.map((item) => {
|
|
53
|
+
const groupInfo = item.group ? groupsMap.get(item.group) : undefined;
|
|
54
|
+
return {
|
|
55
|
+
...item,
|
|
56
|
+
groupLabel: groupInfo?.group.label || item.label || undefined,
|
|
57
|
+
parentGroupId: groupInfo?.parent?.id || null,
|
|
58
|
+
parentGroupLabel: groupInfo?.parent?.label || undefined,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
}, [wallets, activeWalletIndex, groupsMap]);
|
|
62
|
+
// Group line items by parent category
|
|
63
|
+
// Line items are already aggregated by category, so we just need to group them by parent
|
|
64
|
+
const groupedAndAggregatedItems = useMemo(() => {
|
|
65
|
+
const grouped = new Map();
|
|
66
|
+
walletLineItems.forEach((item) => {
|
|
67
|
+
if (!item)
|
|
68
|
+
return;
|
|
69
|
+
const parentKey = item.parentGroupId || "uncategorized";
|
|
70
|
+
const items = grouped.get(parentKey) || [];
|
|
71
|
+
items.push({
|
|
72
|
+
lineItemId: item.id || "",
|
|
73
|
+
groupId: item.group || "uncategorized",
|
|
74
|
+
groupLabel: item.groupLabel || "Uncategorised",
|
|
75
|
+
parentGroupId: item.parentGroupId,
|
|
76
|
+
parentGroupLabel: item.parentGroupLabel,
|
|
77
|
+
budget: item.budget || 0,
|
|
78
|
+
forecast: item.forecast || 0,
|
|
79
|
+
actuals: item.actuals || 0,
|
|
80
|
+
payments: item.payments || 0,
|
|
81
|
+
comment: item.comments || "",
|
|
82
|
+
});
|
|
83
|
+
grouped.set(parentKey, items);
|
|
84
|
+
});
|
|
85
|
+
return grouped;
|
|
86
|
+
}, [walletLineItems]);
|
|
87
|
+
// Calculate subtotals for each parent group
|
|
88
|
+
const calculateSubtotal = (items) => {
|
|
89
|
+
return items.reduce((acc, item) => ({
|
|
90
|
+
budget: acc.budget + item.budget,
|
|
91
|
+
forecast: acc.forecast + item.forecast,
|
|
92
|
+
actuals: acc.actuals + item.actuals,
|
|
93
|
+
difference: acc.difference + (item.forecast - item.actuals),
|
|
94
|
+
payments: acc.payments + item.payments,
|
|
95
|
+
}), { budget: 0, forecast: 0, actuals: 0, difference: 0, payments: 0 });
|
|
96
|
+
};
|
|
97
|
+
// Calculate grand totals
|
|
98
|
+
const grandTotals = useMemo(() => {
|
|
99
|
+
return walletLineItems.reduce((acc, item) => ({
|
|
100
|
+
budget: acc.budget + (item?.budget || 0),
|
|
101
|
+
forecast: acc.forecast + (item?.forecast || 0),
|
|
102
|
+
actuals: acc.actuals + (item?.actuals || 0),
|
|
103
|
+
difference: acc.difference + ((item?.forecast || 0) - (item?.actuals || 0)),
|
|
104
|
+
payments: acc.payments + (item?.payments || 0),
|
|
105
|
+
}), { budget: 0, forecast: 0, actuals: 0, difference: 0, payments: 0 });
|
|
106
|
+
}, [walletLineItems]);
|
|
107
|
+
const formatNumber = (value) => {
|
|
108
|
+
return new Intl.NumberFormat("en-US", {
|
|
109
|
+
minimumFractionDigits: 2,
|
|
110
|
+
maximumFractionDigits: 2,
|
|
111
|
+
}).format(value);
|
|
112
|
+
};
|
|
113
|
+
const formatWalletAddress = (address) => {
|
|
114
|
+
if (!address || address.length < 13)
|
|
115
|
+
return address;
|
|
116
|
+
return `${address.substring(0, 6)}...${address.substring(address.length - 6)}`;
|
|
117
|
+
};
|
|
118
|
+
// Handle starting comment edit
|
|
119
|
+
const handleStartEdit = (lineItemId, currentComment) => {
|
|
120
|
+
setEditingGroupId(lineItemId);
|
|
121
|
+
setEditingComment(currentComment);
|
|
122
|
+
setOriginalComment(currentComment);
|
|
123
|
+
};
|
|
124
|
+
// Handle saving comment for a single line item
|
|
125
|
+
const handleSaveComment = () => {
|
|
126
|
+
const wallet = wallets[activeWalletIndex];
|
|
127
|
+
if (!wallet || !wallet.wallet || !editingGroupId)
|
|
128
|
+
return;
|
|
129
|
+
// Only dispatch if the comment has actually changed
|
|
130
|
+
if (editingComment !== originalComment) {
|
|
131
|
+
dispatch(actions.updateLineItem({
|
|
132
|
+
wallet: wallet.wallet,
|
|
133
|
+
lineItemId: editingGroupId,
|
|
134
|
+
comments: editingComment,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
// Reset editing state
|
|
138
|
+
setEditingGroupId(null);
|
|
139
|
+
setEditingComment("");
|
|
140
|
+
setOriginalComment("");
|
|
141
|
+
};
|
|
142
|
+
// Handle canceling comment edit
|
|
143
|
+
const handleCancelEdit = () => {
|
|
144
|
+
setEditingGroupId(null);
|
|
145
|
+
setEditingComment("");
|
|
146
|
+
setOriginalComment("");
|
|
147
|
+
};
|
|
148
|
+
// Handle starting numeric field edit
|
|
149
|
+
const handleStartFieldEdit = (lineItemId, field, currentValue) => {
|
|
150
|
+
setEditingField({ lineItemId, field, originalValue: currentValue });
|
|
151
|
+
setEditingValue(currentValue.toString());
|
|
152
|
+
};
|
|
153
|
+
// Handle saving numeric field
|
|
154
|
+
const handleSaveField = () => {
|
|
155
|
+
const wallet = wallets[activeWalletIndex];
|
|
156
|
+
if (!wallet || !wallet.wallet || !editingField)
|
|
157
|
+
return;
|
|
158
|
+
const numericValue = parseFloat(editingValue);
|
|
159
|
+
if (isNaN(numericValue)) {
|
|
160
|
+
// Invalid number, cancel edit
|
|
161
|
+
handleCancelFieldEdit();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// Only dispatch if the value has actually changed
|
|
165
|
+
if (numericValue !== editingField.originalValue) {
|
|
166
|
+
dispatch(actions.updateLineItem({
|
|
167
|
+
wallet: wallet.wallet,
|
|
168
|
+
lineItemId: editingField.lineItemId,
|
|
169
|
+
[editingField.field]: numericValue,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
// Reset editing state
|
|
173
|
+
setEditingField(null);
|
|
174
|
+
setEditingValue("");
|
|
175
|
+
};
|
|
176
|
+
// Handle canceling numeric field edit
|
|
177
|
+
const handleCancelFieldEdit = () => {
|
|
178
|
+
setEditingField(null);
|
|
179
|
+
setEditingValue("");
|
|
180
|
+
};
|
|
181
|
+
// Sort parent groups: Headcount first, then Non-Headcount, then others, then uncategorized
|
|
182
|
+
const sortedParentKeys = useMemo(() => {
|
|
183
|
+
const keys = Array.from(groupedAndAggregatedItems.keys());
|
|
184
|
+
// Find Headcount and Non-Headcount group IDs
|
|
185
|
+
const headcountGroup = groups.find(g => g.label === "Headcount Expenses");
|
|
186
|
+
const nonHeadcountGroup = groups.find(g => g.label === "Non-Headcount Expenses");
|
|
187
|
+
return keys.sort((a, b) => {
|
|
188
|
+
// Uncategorized always goes last
|
|
189
|
+
if (a === "uncategorized")
|
|
190
|
+
return 1;
|
|
191
|
+
if (b === "uncategorized")
|
|
192
|
+
return -1;
|
|
193
|
+
// Headcount Expenses always first
|
|
194
|
+
if (a === headcountGroup?.id)
|
|
195
|
+
return -1;
|
|
196
|
+
if (b === headcountGroup?.id)
|
|
197
|
+
return 1;
|
|
198
|
+
// Non-Headcount Expenses always second
|
|
199
|
+
if (a === nonHeadcountGroup?.id)
|
|
200
|
+
return -1;
|
|
201
|
+
if (b === nonHeadcountGroup?.id)
|
|
202
|
+
return 1;
|
|
203
|
+
// For other groups, maintain their original order
|
|
204
|
+
return 0;
|
|
205
|
+
});
|
|
206
|
+
}, [groupedAndAggregatedItems, groups]);
|
|
207
|
+
if (wallets.length === 0) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "border-b border-gray-200 dark:border-gray-700", children: _jsx("nav", { className: "-mb-px flex space-x-8", "aria-label": "Tabs", children: wallets.map((wallet, index) => {
|
|
211
|
+
const isActive = index === activeWalletIndex;
|
|
212
|
+
return (_jsx("button", { onClick: () => setActiveWalletIndex(index), className: `
|
|
213
|
+
whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors
|
|
214
|
+
${isActive
|
|
215
|
+
? "border-green-500 text-green-600 dark:text-green-400"
|
|
216
|
+
: "border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600"}
|
|
217
|
+
`, children: wallet.name || formatWalletAddress(wallet.wallet || "") }, wallet.wallet || index));
|
|
218
|
+
}) }) }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "min-w-full divide-y divide-gray-200 dark:divide-gray-700", children: [_jsx("thead", { className: "bg-gray-50 dark:bg-gray-800", children: _jsxs("tr", { children: [_jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Expense Category" }), _jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Mthly Budget" }), _jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Forecast" }), _jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Actuals" }), _jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Difference" }), _jsx("th", { className: "px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-96", children: "Comments" }), _jsx("th", { className: "px-3 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-32", children: "Payments" })] }) }), _jsxs("tbody", { className: "divide-y divide-gray-200 dark:divide-gray-700", children: [sortedParentKeys.map((parentKey) => {
|
|
219
|
+
const items = groupedAndAggregatedItems.get(parentKey) || [];
|
|
220
|
+
if (items.length === 0)
|
|
221
|
+
return null;
|
|
222
|
+
const subtotals = calculateSubtotal(items);
|
|
223
|
+
const parentLabel = parentKey === "uncategorized"
|
|
224
|
+
? "Uncategorised"
|
|
225
|
+
: items[0]?.parentGroupLabel || "Other";
|
|
226
|
+
return (_jsxs(React.Fragment, { children: [_jsx("tr", { className: "bg-gray-100 dark:bg-gray-800", children: _jsx("td", { colSpan: 7, className: "px-6 py-3 text-sm font-bold text-gray-900 dark:text-white", children: parentLabel }) }), items.map((item) => {
|
|
227
|
+
if (!item)
|
|
228
|
+
return null;
|
|
229
|
+
const difference = item.forecast - item.actuals;
|
|
230
|
+
const isEditingComment = editingGroupId === item.lineItemId;
|
|
231
|
+
// Helper function to render editable numeric cell
|
|
232
|
+
const renderEditableCell = (field, value) => {
|
|
233
|
+
const isEditingThis = editingField?.lineItemId === item.lineItemId &&
|
|
234
|
+
editingField?.field === field;
|
|
235
|
+
if (isEditingThis) {
|
|
236
|
+
return (_jsx("div", { className: "flex items-center gap-1", children: _jsx("input", { type: "number", step: "0.01", value: editingValue, onChange: (e) => setEditingValue(e.target.value), onKeyDown: (e) => {
|
|
237
|
+
if (e.key === "Enter") {
|
|
238
|
+
handleSaveField();
|
|
239
|
+
}
|
|
240
|
+
else if (e.key === "Escape") {
|
|
241
|
+
handleCancelFieldEdit();
|
|
242
|
+
}
|
|
243
|
+
}, onBlur: handleSaveField, autoFocus: true, className: "w-full px-2 py-1 text-right text-sm border border-blue-500 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-white" }) }));
|
|
244
|
+
}
|
|
245
|
+
return (_jsx("div", { className: "group cursor-pointer text-right", onClick: () => handleStartFieldEdit(item.lineItemId, field, value), children: _jsx("span", { className: "group-hover:bg-blue-50 dark:group-hover:bg-blue-900/20 inline-block px-1 py-0.5 rounded transition-colors min-w-[4rem]", children: formatNumber(value) }) }));
|
|
246
|
+
};
|
|
247
|
+
return (_jsxs("tr", { className: "bg-white dark:bg-gray-900 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors align-top", children: [_jsx("td", { className: "px-6 py-3 whitespace-nowrap text-sm text-gray-900 dark:text-white", children: item.groupLabel }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: renderEditableCell("budget", item.budget) }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: renderEditableCell("forecast", item.forecast) }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: renderEditableCell("actuals", item.actuals) }), _jsx("td", { className: `px-6 py-3 whitespace-nowrap text-right text-sm font-medium ${difference < 0
|
|
248
|
+
? "text-red-600 dark:text-red-400"
|
|
249
|
+
: "text-gray-900 dark:text-white"}`, children: formatNumber(difference) }), _jsx("td", { className: "px-3 py-3 text-sm w-96", children: isEditingComment ? (_jsx(Textarea, { value: editingComment, onChange: (e) => setEditingComment(e.target.value), placeholder: "Add comment...", autoExpand: true, multiline: true, onKeyDown: (e) => {
|
|
250
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
251
|
+
e.preventDefault();
|
|
252
|
+
handleSaveComment();
|
|
253
|
+
}
|
|
254
|
+
else if (e.key === "Escape") {
|
|
255
|
+
handleCancelEdit();
|
|
256
|
+
}
|
|
257
|
+
else if (e.key === "Tab") {
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
handleSaveComment();
|
|
260
|
+
}
|
|
261
|
+
}, onBlur: handleSaveComment, autoFocus: true, className: "w-full px-2 py-1 text-sm border border-blue-500 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-white max-h-32 overflow-y-auto" })) : (_jsx("div", { className: "group cursor-pointer w-full max-h-20 overflow-hidden", onClick: () => handleStartEdit(item.lineItemId, item.comment), title: item.comment || "No comments", children: _jsx("span", { className: "group-hover:bg-blue-50 dark:group-hover:bg-blue-900/20 px-1 py-0.5 rounded transition-colors block text-gray-600 dark:text-gray-400 break-words", children: item.comment || "No comments" }) })) }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white w-32", children: renderEditableCell("payments", item.payments) })] }, item.lineItemId));
|
|
262
|
+
}), _jsxs("tr", { className: "bg-gray-50 dark:bg-gray-800/50 font-semibold align-top", children: [_jsx("td", { className: "px-6 py-3 whitespace-nowrap text-sm text-gray-900 dark:text-white", children: "Subtotal" }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(subtotals.budget) }) }) }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(subtotals.forecast) }) }) }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(subtotals.actuals) }) }) }), _jsx("td", { className: `px-6 py-3 whitespace-nowrap text-right text-sm font-bold ${subtotals.difference < 0
|
|
263
|
+
? "text-red-600 dark:text-red-400"
|
|
264
|
+
: "text-gray-900 dark:text-white"}`, children: formatNumber(subtotals.difference) }), _jsx("td", { className: "px-3 py-3" }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white w-32", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(subtotals.payments) }) }) })] })] }, parentKey));
|
|
265
|
+
}), _jsxs("tr", { className: "bg-gray-100 dark:bg-gray-800 font-bold align-top", children: [_jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white", children: "Total" }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(grandTotals.budget) }) }) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(grandTotals.forecast) }) }) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(grandTotals.actuals) }) }) }), _jsx("td", { className: `px-6 py-4 whitespace-nowrap text-right text-sm ${grandTotals.difference < 0
|
|
266
|
+
? "text-red-600 dark:text-red-400"
|
|
267
|
+
: "text-gray-900 dark:text-white"}`, children: formatNumber(grandTotals.difference) }), _jsx("td", { className: "px-3 py-4" }), _jsx("td", { className: "px-3 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white w-32", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(grandTotals.payments) }) }) })] })] })] }) })] }));
|
|
268
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Wallet, LineItemGroup } from "../../../document-models/expense-report/gen/types.js";
|
|
2
|
+
interface ExpenseReportPDFProps {
|
|
3
|
+
periodStart?: string | null;
|
|
4
|
+
periodEnd?: string | null;
|
|
5
|
+
wallets: Wallet[];
|
|
6
|
+
groups: LineItemGroup[];
|
|
7
|
+
}
|
|
8
|
+
export declare function ExpenseReportPDF({ periodStart, periodEnd, wallets, groups, }: ExpenseReportPDFProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=ExpenseReportPDF.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpenseReportPDF.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/components/ExpenseReportPDF.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,MAAM,EACN,aAAa,EAEd,MAAM,sDAAsD,CAAC;AAE9D,UAAU,qBAAqB;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAmKD,wBAAgB,gBAAgB,CAAC,EAC/B,WAAW,EACX,SAAS,EACT,OAAO,EACP,MAAM,GACP,EAAE,qBAAqB,2CAiVvB"}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Document, Page, Text, View, StyleSheet } from "@react-pdf/renderer";
|
|
3
|
+
// Tailwind-inspired styles for PDF
|
|
4
|
+
const styles = StyleSheet.create({
|
|
5
|
+
page: {
|
|
6
|
+
padding: 32,
|
|
7
|
+
fontSize: 8,
|
|
8
|
+
fontFamily: "Helvetica",
|
|
9
|
+
backgroundColor: "#ffffff",
|
|
10
|
+
},
|
|
11
|
+
header: {
|
|
12
|
+
marginBottom: 20,
|
|
13
|
+
borderBottom: "1pt solid #e5e7eb",
|
|
14
|
+
paddingBottom: 12,
|
|
15
|
+
},
|
|
16
|
+
title: {
|
|
17
|
+
fontSize: 18,
|
|
18
|
+
fontWeight: "bold",
|
|
19
|
+
marginBottom: 6,
|
|
20
|
+
color: "#111827",
|
|
21
|
+
textAlign: "center",
|
|
22
|
+
},
|
|
23
|
+
period: {
|
|
24
|
+
fontSize: 9,
|
|
25
|
+
color: "#6b7280",
|
|
26
|
+
textAlign: "center",
|
|
27
|
+
marginTop: 2,
|
|
28
|
+
},
|
|
29
|
+
sectionTitle: {
|
|
30
|
+
fontSize: 12,
|
|
31
|
+
fontWeight: "bold",
|
|
32
|
+
marginTop: 16,
|
|
33
|
+
marginBottom: 8,
|
|
34
|
+
color: "#111827",
|
|
35
|
+
},
|
|
36
|
+
walletInfo: {
|
|
37
|
+
fontSize: 8,
|
|
38
|
+
color: "#6b7280",
|
|
39
|
+
marginBottom: 8,
|
|
40
|
+
},
|
|
41
|
+
table: {
|
|
42
|
+
marginBottom: 16,
|
|
43
|
+
},
|
|
44
|
+
tableHeader: {
|
|
45
|
+
flexDirection: "row",
|
|
46
|
+
borderBottom: "1pt solid #e5e7eb",
|
|
47
|
+
paddingBottom: 6,
|
|
48
|
+
marginBottom: 6,
|
|
49
|
+
backgroundColor: "#f9fafb",
|
|
50
|
+
paddingTop: 6,
|
|
51
|
+
paddingHorizontal: 6,
|
|
52
|
+
},
|
|
53
|
+
tableRow: {
|
|
54
|
+
flexDirection: "row",
|
|
55
|
+
paddingVertical: 4,
|
|
56
|
+
paddingHorizontal: 6,
|
|
57
|
+
borderBottom: "0.5pt solid #f3f4f6",
|
|
58
|
+
minHeight: 20,
|
|
59
|
+
},
|
|
60
|
+
tableRowAlt: {
|
|
61
|
+
flexDirection: "row",
|
|
62
|
+
paddingVertical: 4,
|
|
63
|
+
paddingHorizontal: 6,
|
|
64
|
+
backgroundColor: "#f9fafb",
|
|
65
|
+
borderBottom: "0.5pt solid #f3f4f6",
|
|
66
|
+
minHeight: 20,
|
|
67
|
+
},
|
|
68
|
+
subtotalRow: {
|
|
69
|
+
flexDirection: "row",
|
|
70
|
+
paddingVertical: 6,
|
|
71
|
+
paddingHorizontal: 6,
|
|
72
|
+
borderTop: "1pt solid #d1d5db",
|
|
73
|
+
marginTop: 3,
|
|
74
|
+
fontWeight: "bold",
|
|
75
|
+
backgroundColor: "#fafafa",
|
|
76
|
+
},
|
|
77
|
+
totalRow: {
|
|
78
|
+
flexDirection: "row",
|
|
79
|
+
paddingVertical: 6,
|
|
80
|
+
paddingHorizontal: 6,
|
|
81
|
+
borderTop: "2pt solid #111827",
|
|
82
|
+
marginTop: 3,
|
|
83
|
+
fontWeight: "bold",
|
|
84
|
+
backgroundColor: "#f3f4f6",
|
|
85
|
+
},
|
|
86
|
+
headerCell: {
|
|
87
|
+
fontSize: 7,
|
|
88
|
+
fontWeight: "bold",
|
|
89
|
+
color: "#374151",
|
|
90
|
+
textTransform: "uppercase",
|
|
91
|
+
letterSpacing: 0.3,
|
|
92
|
+
},
|
|
93
|
+
cell: {
|
|
94
|
+
fontSize: 8,
|
|
95
|
+
color: "#111827",
|
|
96
|
+
},
|
|
97
|
+
cellRight: {
|
|
98
|
+
fontSize: 8,
|
|
99
|
+
color: "#111827",
|
|
100
|
+
textAlign: "right",
|
|
101
|
+
},
|
|
102
|
+
cellBold: {
|
|
103
|
+
fontSize: 8,
|
|
104
|
+
color: "#111827",
|
|
105
|
+
fontWeight: "bold",
|
|
106
|
+
},
|
|
107
|
+
// Breakdown table columns - adjusted to match AggregatedExpensesTable
|
|
108
|
+
categoryCol: { width: "20%" },
|
|
109
|
+
budgetCol: { width: "11%", textAlign: "right" },
|
|
110
|
+
forecastCol: { width: "11%", textAlign: "right" },
|
|
111
|
+
actualsCol: { width: "11%", textAlign: "right" },
|
|
112
|
+
differenceCol: { width: "11%", textAlign: "right" },
|
|
113
|
+
commentsCol: { width: "25%", paddingLeft: 4 },
|
|
114
|
+
paymentsCol: { width: "11%", textAlign: "right" },
|
|
115
|
+
commentsText: {
|
|
116
|
+
fontSize: 7,
|
|
117
|
+
color: "#6b7280",
|
|
118
|
+
lineHeight: 1.4,
|
|
119
|
+
},
|
|
120
|
+
// Color styles for difference column
|
|
121
|
+
differenceNegative: {
|
|
122
|
+
color: "#dc2626", // red-600 for negative values
|
|
123
|
+
},
|
|
124
|
+
differenceNormal: {
|
|
125
|
+
color: "#111827", // gray-900 for positive or zero
|
|
126
|
+
},
|
|
127
|
+
pageNumber: {
|
|
128
|
+
position: "absolute",
|
|
129
|
+
bottom: 20,
|
|
130
|
+
left: 0,
|
|
131
|
+
right: 0,
|
|
132
|
+
textAlign: "center",
|
|
133
|
+
fontSize: 7,
|
|
134
|
+
color: "#6b7280",
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
// Format number as currency
|
|
138
|
+
const formatNumber = (value) => {
|
|
139
|
+
if (value === null || value === undefined)
|
|
140
|
+
return "0.00";
|
|
141
|
+
return value.toLocaleString("en-US", {
|
|
142
|
+
minimumFractionDigits: 2,
|
|
143
|
+
maximumFractionDigits: 2,
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
// Format date
|
|
147
|
+
const formatDate = (dateString) => {
|
|
148
|
+
if (!dateString)
|
|
149
|
+
return "";
|
|
150
|
+
const date = new Date(dateString);
|
|
151
|
+
return date.toLocaleDateString("en-US", {
|
|
152
|
+
month: "short",
|
|
153
|
+
day: "numeric",
|
|
154
|
+
year: "numeric",
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
export function ExpenseReportPDF({ periodStart, periodEnd, wallets, groups, }) {
|
|
158
|
+
// Create a map of groups with their parent info
|
|
159
|
+
const groupsMap = new Map();
|
|
160
|
+
groups.forEach((group) => {
|
|
161
|
+
groupsMap.set(group.id, { group });
|
|
162
|
+
});
|
|
163
|
+
groups.forEach((group) => {
|
|
164
|
+
if (group.parentId) {
|
|
165
|
+
const entry = groupsMap.get(group.id);
|
|
166
|
+
const parentEntry = groupsMap.get(group.parentId);
|
|
167
|
+
if (entry && parentEntry) {
|
|
168
|
+
entry.parent = parentEntry.group;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
// Get line items for a wallet with group information
|
|
173
|
+
const getWalletLineItems = (wallet) => {
|
|
174
|
+
const lineItems = wallet.lineItems || [];
|
|
175
|
+
return lineItems
|
|
176
|
+
.filter((item) => item !== null && item !== undefined)
|
|
177
|
+
.map((item) => {
|
|
178
|
+
const groupInfo = item.group ? groupsMap.get(item.group) : undefined;
|
|
179
|
+
return {
|
|
180
|
+
...item,
|
|
181
|
+
groupLabel: groupInfo?.group.label || item.label || undefined,
|
|
182
|
+
parentGroupId: groupInfo?.parent?.id || null,
|
|
183
|
+
parentGroupLabel: groupInfo?.parent?.label || undefined,
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
// Group line items by parent category
|
|
188
|
+
const groupLineItemsByParent = (lineItems) => {
|
|
189
|
+
const grouped = new Map();
|
|
190
|
+
lineItems.forEach((item) => {
|
|
191
|
+
const key = item.parentGroupId || "uncategorized";
|
|
192
|
+
if (!grouped.has(key)) {
|
|
193
|
+
grouped.set(key, []);
|
|
194
|
+
}
|
|
195
|
+
grouped.get(key).push(item);
|
|
196
|
+
});
|
|
197
|
+
// Convert to array and sort by hierarchy: Headcount, Non-Headcount, others, then uncategorized
|
|
198
|
+
const entries = Array.from(grouped.entries());
|
|
199
|
+
// Find Headcount and Non-Headcount group IDs
|
|
200
|
+
const headcountGroup = groups.find((g) => g.label === "Headcount Expenses");
|
|
201
|
+
const nonHeadcountGroup = groups.find((g) => g.label === "Non-Headcount Expenses");
|
|
202
|
+
entries.sort(([keyA], [keyB]) => {
|
|
203
|
+
// Uncategorized always goes last
|
|
204
|
+
if (keyA === "uncategorized")
|
|
205
|
+
return 1;
|
|
206
|
+
if (keyB === "uncategorized")
|
|
207
|
+
return -1;
|
|
208
|
+
// Headcount Expenses always first
|
|
209
|
+
if (keyA === headcountGroup?.id)
|
|
210
|
+
return -1;
|
|
211
|
+
if (keyB === headcountGroup?.id)
|
|
212
|
+
return 1;
|
|
213
|
+
// Non-Headcount Expenses always second
|
|
214
|
+
if (keyA === nonHeadcountGroup?.id)
|
|
215
|
+
return -1;
|
|
216
|
+
if (keyB === nonHeadcountGroup?.id)
|
|
217
|
+
return 1;
|
|
218
|
+
// For other groups, maintain their original order
|
|
219
|
+
return 0;
|
|
220
|
+
});
|
|
221
|
+
return entries.map(([key, items]) => ({
|
|
222
|
+
parentLabel: key === "uncategorized"
|
|
223
|
+
? "Uncategorised"
|
|
224
|
+
: items[0]?.parentGroupLabel || "Unknown",
|
|
225
|
+
items,
|
|
226
|
+
}));
|
|
227
|
+
};
|
|
228
|
+
return (_jsx(Document, { children: _jsxs(Page, { size: "A4", style: styles.page, children: [_jsxs(View, { style: styles.header, children: [_jsx(Text, { style: styles.title, children: "Expense Report" }), periodStart && (_jsxs(Text, { style: styles.period, children: ["Period: ", formatDate(periodStart), " to ", formatDate(periodEnd)] }))] }), wallets.map((wallet, walletIndex) => {
|
|
229
|
+
const lineItems = getWalletLineItems(wallet);
|
|
230
|
+
const groupedItems = groupLineItemsByParent(lineItems);
|
|
231
|
+
// Calculate grand totals
|
|
232
|
+
const grandTotals = lineItems.reduce((acc, item) => ({
|
|
233
|
+
budget: acc.budget + (item.budget || 0),
|
|
234
|
+
forecast: acc.forecast + (item.forecast || 0),
|
|
235
|
+
actuals: acc.actuals + (item.actuals || 0),
|
|
236
|
+
payments: acc.payments + (item.payments || 0),
|
|
237
|
+
}), { budget: 0, forecast: 0, actuals: 0, payments: 0 });
|
|
238
|
+
return (_jsxs(View, { wrap: false, break: walletIndex > 0, children: [_jsxs(Text, { style: styles.sectionTitle, children: [periodStart &&
|
|
239
|
+
new Date(periodStart).toLocaleDateString("en-US", {
|
|
240
|
+
month: "short",
|
|
241
|
+
year: "numeric",
|
|
242
|
+
}), " ", "Breakdown"] }), _jsxs(Text, { style: styles.walletInfo, children: [wallet.name && `${wallet.name} • `, wallet.wallet || "Unknown Wallet"] }), _jsxs(View, { style: styles.table, children: [_jsxs(View, { style: styles.tableHeader, children: [_jsx(Text, { style: [styles.headerCell, styles.categoryCol], children: "Category" }), _jsx(Text, { style: [styles.headerCell, styles.budgetCol], children: "Budget" }), _jsx(Text, { style: [styles.headerCell, styles.forecastCol], children: "Forecast" }), _jsx(Text, { style: [styles.headerCell, styles.actualsCol], children: "Actuals" }), _jsx(Text, { style: [styles.headerCell, styles.differenceCol], children: "Difference" }), _jsx(Text, { style: [styles.headerCell, styles.commentsCol], children: "Comments" }), _jsx(Text, { style: [styles.headerCell, styles.paymentsCol], children: "Payments" })] }), groupedItems.map((group, groupIndex) => {
|
|
243
|
+
const subtotals = group.items.reduce((acc, item) => ({
|
|
244
|
+
budget: acc.budget + (item.budget || 0),
|
|
245
|
+
forecast: acc.forecast + (item.forecast || 0),
|
|
246
|
+
actuals: acc.actuals + (item.actuals || 0),
|
|
247
|
+
payments: acc.payments + (item.payments || 0),
|
|
248
|
+
}), { budget: 0, forecast: 0, actuals: 0, payments: 0 });
|
|
249
|
+
const subtotalDifference = subtotals.forecast - subtotals.actuals;
|
|
250
|
+
return (_jsxs(View, { children: [_jsx(View, { style: {
|
|
251
|
+
paddingVertical: 6,
|
|
252
|
+
paddingHorizontal: 6,
|
|
253
|
+
backgroundColor: "#f9fafb",
|
|
254
|
+
borderBottom: "1pt solid #e5e7eb",
|
|
255
|
+
marginTop: groupIndex > 0 ? 8 : 0,
|
|
256
|
+
}, children: _jsx(Text, { style: {
|
|
257
|
+
fontSize: 9,
|
|
258
|
+
fontWeight: "bold",
|
|
259
|
+
color: "#111827",
|
|
260
|
+
}, children: group.parentLabel }) }), group.items.map((item, itemIndex) => {
|
|
261
|
+
const difference = (item.forecast || 0) - (item.actuals || 0);
|
|
262
|
+
const differenceStyle = difference < 0
|
|
263
|
+
? styles.differenceNegative
|
|
264
|
+
: styles.differenceNormal;
|
|
265
|
+
return (_jsxs(View, { style: itemIndex % 2 === 0
|
|
266
|
+
? styles.tableRow
|
|
267
|
+
: styles.tableRowAlt, children: [_jsx(Text, { style: [styles.cell, styles.categoryCol], children: item.groupLabel || item.label || "Uncategorised" }), _jsx(Text, { style: [styles.cellRight, styles.budgetCol], children: formatNumber(item.budget) }), _jsx(Text, { style: [styles.cellRight, styles.forecastCol], children: formatNumber(item.forecast) }), _jsx(Text, { style: [styles.cellRight, styles.actualsCol], children: formatNumber(item.actuals) }), _jsx(Text, { style: [
|
|
268
|
+
styles.cellRight,
|
|
269
|
+
styles.differenceCol,
|
|
270
|
+
differenceStyle,
|
|
271
|
+
], children: formatNumber(difference) }), _jsx(View, { style: styles.commentsCol, children: item.comments && (_jsx(Text, { style: styles.commentsText, children: item.comments })) }), _jsx(Text, { style: [styles.cellRight, styles.paymentsCol], children: formatNumber(item.payments) })] }, item.id));
|
|
272
|
+
}), _jsxs(View, { style: styles.subtotalRow, children: [_jsx(Text, { style: [styles.cellBold, styles.categoryCol], children: "Subtotal" }), _jsx(Text, { style: [styles.cellRight, styles.budgetCol], children: formatNumber(subtotals.budget) }), _jsx(Text, { style: [styles.cellRight, styles.forecastCol], children: formatNumber(subtotals.forecast) }), _jsx(Text, { style: [styles.cellRight, styles.actualsCol], children: formatNumber(subtotals.actuals) }), _jsx(Text, { style: [
|
|
273
|
+
styles.cellRight,
|
|
274
|
+
styles.differenceCol,
|
|
275
|
+
subtotalDifference < 0
|
|
276
|
+
? styles.differenceNegative
|
|
277
|
+
: styles.differenceNormal,
|
|
278
|
+
], children: formatNumber(subtotalDifference) }), _jsx(View, { style: styles.commentsCol }), _jsx(Text, { style: [styles.cellRight, styles.paymentsCol], children: formatNumber(subtotals.payments) })] })] }, group.parentLabel));
|
|
279
|
+
}), _jsxs(View, { style: styles.totalRow, children: [_jsx(Text, { style: [styles.cellBold, styles.categoryCol], children: "Total" }), _jsx(Text, { style: [styles.cellRight, styles.budgetCol], children: formatNumber(grandTotals.budget) }), _jsx(Text, { style: [styles.cellRight, styles.forecastCol], children: formatNumber(grandTotals.forecast) }), _jsx(Text, { style: [styles.cellRight, styles.actualsCol], children: formatNumber(grandTotals.actuals) }), _jsx(Text, { style: [
|
|
280
|
+
styles.cellRight,
|
|
281
|
+
styles.differenceCol,
|
|
282
|
+
grandTotals.forecast - grandTotals.actuals < 0
|
|
283
|
+
? styles.differenceNegative
|
|
284
|
+
: styles.differenceNormal,
|
|
285
|
+
], children: formatNumber(grandTotals.forecast - grandTotals.actuals) }), _jsx(View, { style: styles.commentsCol }), _jsx(Text, { style: [styles.cellRight, styles.paymentsCol], children: formatNumber(grandTotals.payments) })] })] })] }, wallet.wallet || walletIndex));
|
|
286
|
+
}), _jsx(Text, { style: styles.pageNumber, render: ({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`, fixed: true })] }) }));
|
|
287
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Wallet, LineItemGroup } from "../../../document-models/expense-report/gen/types.js";
|
|
2
|
+
interface WalletsTableProps {
|
|
3
|
+
wallets: Wallet[];
|
|
4
|
+
groups: LineItemGroup[];
|
|
5
|
+
onAddBillingStatement: (walletAddress: string) => void;
|
|
6
|
+
dispatch: any;
|
|
7
|
+
}
|
|
8
|
+
export declare function WalletsTable({ wallets, groups, onAddBillingStatement, dispatch, }: WalletsTableProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=WalletsTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WalletsTable.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/components/WalletsTable.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAKlG,UAAU,iBAAiB;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,qBAAqB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IACvD,QAAQ,EAAE,GAAG,CAAC;CACf;AAED,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,MAAM,EACN,qBAAqB,EACrB,QAAQ,GACT,EAAE,iBAAiB,2CA+XnB"}
|