@powerhousedao/contributor-billing 0.1.4 → 0.1.6
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/editors/billing-statement/lineItemTags/lineItemTags.js +1 -1
- package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts.map +1 -1
- package/dist/editors/expense-report/components/AddBillingStatementModal.js +47 -22
- package/dist/editors/expense-report/components/AggregatedExpensesTable.d.ts.map +1 -1
- package/dist/editors/expense-report/components/AggregatedExpensesTable.js +230 -76
- 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.map +1 -1
- package/dist/editors/expense-report/components/WalletsTable.js +25 -13
- package/dist/editors/expense-report/editor.d.ts.map +1 -1
- package/dist/editors/expense-report/editor.js +26 -40
- package/dist/editors/expense-report/hooks/useSyncWallet.d.ts.map +1 -1
- package/dist/editors/expense-report/hooks/useSyncWallet.js +38 -18
- package/dist/editors/expense-report/hooks/useWalletSync.d.ts.map +1 -1
- package/dist/editors/expense-report/hooks/useWalletSync.js +25 -27
- package/dist/editors/invoice/legalEntity/bankSection.d.ts.map +1 -1
- package/dist/editors/invoice/legalEntity/bankSection.js +18 -18
- package/dist/editors/invoice/lineItemTags/lineItemTags.js +1 -1
- package/dist/style.css +93 -21
- package/package.json +2 -1
|
@@ -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, { break: walletIndex > 0, children: [_jsxs(View, { wrap: false, 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: [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: [groupIndex === 0 && (_jsxs(View, { style: styles.tableHeader, wrap: false, 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" })] })), _jsx(View, { style: {
|
|
251
|
+
paddingVertical: 6,
|
|
252
|
+
paddingHorizontal: 6,
|
|
253
|
+
backgroundColor: "#f9fafb",
|
|
254
|
+
borderBottom: "1pt solid #e5e7eb",
|
|
255
|
+
marginTop: groupIndex > 0 ? 8 : 0,
|
|
256
|
+
}, wrap: false, 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
|
+
}
|
|
@@ -1 +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,
|
|
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,2CAwanB"}
|
|
@@ -122,7 +122,19 @@ export function WalletsTable({ wallets, groups, onAddBillingStatement, dispatch,
|
|
|
122
122
|
minimumFractionDigits: 2,
|
|
123
123
|
}).format(value);
|
|
124
124
|
};
|
|
125
|
-
return (_jsxs("div", { className: "space-y-4", children: [wallets.length > 0 ? (_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: "Wallet" }), _jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Monthly 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:
|
|
125
|
+
return (_jsxs("div", { className: "space-y-4", children: [wallets.length > 0 ? (_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: "Wallet" }), _jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Monthly 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: _jsxs("div", { className: "flex items-center justify-end gap-2", children: [needsSync && (_jsx("button", { onClick: () => {
|
|
126
|
+
// Sync all outdated wallets
|
|
127
|
+
[...tagChangedWallets, ...outdatedWallets].forEach((walletAddress) => {
|
|
128
|
+
const wallet = wallets.find(w => w.wallet === walletAddress);
|
|
129
|
+
if (wallet) {
|
|
130
|
+
handleSyncWallet(wallet);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}, disabled: syncingWallet !== null, className: `inline-flex items-center justify-center w-8 h-8 rounded-md transition-colors ${tagChangedWallets.length > 0
|
|
134
|
+
? "text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 hover:bg-red-100 dark:hover:bg-red-900/30 animate-pulse"
|
|
135
|
+
: "text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 hover:bg-amber-100 dark:hover:bg-amber-900/30 animate-pulse"} disabled:opacity-50 disabled:cursor-not-allowed`, title: tagChangedWallets.length > 0
|
|
136
|
+
? "ALERT: Tags have changed in billing statements - sync all wallets!"
|
|
137
|
+
: "Sync all wallets with latest billing statements", children: _jsx(RefreshCw, { size: 16, className: syncingWallet !== null ? "animate-spin" : "" }) })), _jsx("span", { 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-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Payments" }), _jsx("th", { className: "px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Actions" })] }) }), _jsx("tbody", { className: "bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700", children: wallets.map((wallet) => {
|
|
126
138
|
const totals = calculateWalletTotals(wallet);
|
|
127
139
|
const isHovered = hoveredWallet === wallet.wallet;
|
|
128
140
|
return (_jsxs("tr", { onMouseEnter: () => setHoveredWallet(wallet.wallet || null), onMouseLeave: () => setHoveredWallet(null), className: "hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors", children: [_jsx("td", { className: "px-6 py-4 whitespace-nowrap", children: editingWallet === wallet.wallet ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(TextInput, { value: editingName, onChange: (e) => setEditingName(e.target.value), placeholder: "Enter wallet name", className: "flex-1", onKeyDown: (e) => {
|
|
@@ -132,23 +144,23 @@ export function WalletsTable({ wallets, groups, onAddBillingStatement, dispatch,
|
|
|
132
144
|
else if (e.key === "Escape") {
|
|
133
145
|
handleCancelEditName();
|
|
134
146
|
}
|
|
135
|
-
}, autoFocus: true }), _jsx("button", { onClick: () => handleSaveEditName(wallet.wallet || ""), className: "inline-flex items-center justify-center w-7 h-7 text-green-600 dark:text-green-400 hover:bg-green-50 dark:hover:bg-green-900/20 rounded-md transition-colors", title: "Save", children: _jsx(Check, { size: 14 }) }), _jsx("button", { onClick: handleCancelEditName, className: "inline-flex items-center justify-center w-7 h-7 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-900/20 rounded-md transition-colors", title: "Cancel", children: _jsx(X, { size: 14 }) })] })) : (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-white", children: wallet.name || "Unnamed Wallet" }), _jsx("button", { onClick: () => handleStartEditName(wallet), className: "inline-flex items-center justify-center w-6 h-6 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors", title: "Edit name", children: _jsx(Pencil, { size: 12 }) }), _jsxs("button", { onClick: () => handleCopyAddress(wallet.wallet || ""), className: "inline-flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 font-mono hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors", title: `Copy address: ${wallet.wallet}`, children: [formatAddress(wallet.wallet || ""), copiedWallet === wallet.wallet ? (_jsx(CheckCheck, { size: 12, className: "text-green-500" })) : (_jsx(Copy, { size: 12 }))] })] })) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatCurrency(totals.budget) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatCurrency(totals.forecast) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
}, autoFocus: true }), _jsx("button", { onClick: () => handleSaveEditName(wallet.wallet || ""), className: "inline-flex items-center justify-center w-7 h-7 text-green-600 dark:text-green-400 hover:bg-green-50 dark:hover:bg-green-900/20 rounded-md transition-colors", title: "Save", children: _jsx(Check, { size: 14 }) }), _jsx("button", { onClick: handleCancelEditName, className: "inline-flex items-center justify-center w-7 h-7 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-900/20 rounded-md transition-colors", title: "Cancel", children: _jsx(X, { size: 14 }) })] })) : (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-white", children: wallet.name || "Unnamed Wallet" }), _jsx("button", { onClick: () => handleStartEditName(wallet), className: "inline-flex items-center justify-center w-6 h-6 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors", title: "Edit name", children: _jsx(Pencil, { size: 12 }) }), _jsxs("button", { onClick: () => handleCopyAddress(wallet.wallet || ""), className: "inline-flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 font-mono hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors", title: `Copy address: ${wallet.wallet}`, children: [formatAddress(wallet.wallet || ""), copiedWallet === wallet.wallet ? (_jsx(CheckCheck, { size: 12, className: "text-green-500" })) : (_jsx(Copy, { size: 12 }))] })] })) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatCurrency(totals.budget) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatCurrency(totals.forecast) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm", children: totals.actuals === 0 && (!wallet.billingStatements || wallet.billingStatements.length === 0) ? (
|
|
148
|
+
// When actuals is 0 and no billing statements, only show the Add Bills button
|
|
149
|
+
_jsx("div", { className: "flex items-center justify-end", children: _jsxs("button", { onClick: () => onAddBillingStatement(wallet.wallet || ""), className: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 bg-blue-50 dark:bg-blue-900/20 hover:bg-blue-100 dark:hover:bg-blue-900/30 rounded-md transition-colors", title: "Add billing statement for this wallet", children: [_jsx(Plus, { size: 16 }), _jsx("span", { children: "Add Bills" })] }) })) : (
|
|
150
|
+
// When actuals is not 0 or has billing statements, show compact buttons + value horizontally
|
|
151
|
+
_jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { onClick: () => onAddBillingStatement(wallet.wallet || ""), className: "inline-flex items-center justify-center w-8 h-8 text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 bg-blue-50 dark:bg-blue-900/20 hover:bg-blue-100 dark:hover:bg-blue-900/30 rounded-md transition-colors", title: "Add billing statement for this wallet", children: _jsx(Plus, { size: 16 }) }), wallet.billingStatements && wallet.billingStatements.length > 0 && (_jsx("button", { onClick: () => handleSyncWallet(wallet), disabled: syncingWallet === wallet.wallet, className: `inline-flex items-center justify-center w-8 h-8 rounded-md transition-colors ${tagChangedWallets.includes(wallet.wallet || "")
|
|
140
152
|
? "text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 hover:bg-red-100 dark:hover:bg-red-900/30 animate-pulse"
|
|
141
153
|
: outdatedWallets.includes(wallet.wallet || "")
|
|
142
154
|
? "text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 hover:bg-amber-100 dark:hover:bg-amber-900/30 animate-pulse"
|
|
143
155
|
: "text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700"} disabled:opacity-50 disabled:cursor-not-allowed`, title: tagChangedWallets.includes(wallet.wallet || "")
|
|
144
|
-
? "ALERT: Tags have changed
|
|
156
|
+
? "ALERT: Tags have changed - sync required!"
|
|
145
157
|
: outdatedWallets.includes(wallet.wallet || "")
|
|
146
|
-
? "Sync needed - billing statements
|
|
147
|
-
: "Sync with latest billing statements", children:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
158
|
+
? "Sync needed - billing statements updated"
|
|
159
|
+
: "Sync with latest billing statements", children: _jsx(RefreshCw, { size: 16, className: syncingWallet === wallet.wallet ? "animate-spin" : "" }) })), _jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-white", children: formatCurrency(totals.actuals) })] })) }), _jsx("td", { className: `px-6 py-4 whitespace-nowrap text-right text-sm font-medium ${totals.difference > 0
|
|
160
|
+
? "text-red-600 dark:text-red-400"
|
|
161
|
+
: totals.difference < 0
|
|
162
|
+
? "text-green-600 dark:text-green-400"
|
|
163
|
+
: "text-gray-900 dark:text-white"}`, children: formatCurrency(totals.difference) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatCurrency(totals.payments) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm", children: _jsx("div", { className: "flex items-center justify-end gap-2", children: _jsx("button", { onClick: () => handleRemoveWallet(wallet.wallet || ""), className: "inline-flex items-center justify-center w-8 h-8 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-colors", title: "Remove wallet", children: _jsx(Trash2, { size: 16 }) }) }) })] }, wallet.wallet));
|
|
152
164
|
}) })] }) })) : (_jsx("div", { className: "text-center py-12 text-gray-500 dark:text-gray-400", children: _jsx("p", { className: "text-sm", children: "No wallets added yet. Add a wallet to get started." }) })), _jsxs("div", { className: "flex items-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700", children: [_jsxs("div", { className: "flex-1", children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: "Wallet Name" }), _jsx(TextInput, { value: newWalletName, onChange: (e) => setNewWalletName(e.target.value), placeholder: "Enter wallet name (optional)", onKeyDown: (e) => {
|
|
153
165
|
if (e.key === "Enter") {
|
|
154
166
|
handleAddWallet();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/expense-report/editor.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/expense-report/editor.tsx"],"names":[],"mappings":"AAWA,wBAAgB,MAAM,4CAmMrB"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useMemo
|
|
2
|
+
import { useState, useMemo } from "react";
|
|
3
3
|
import { useSelectedExpenseReportDocument } from "../hooks/useExpenseReportDocument.js";
|
|
4
4
|
import { actions } from "../../document-models/expense-report/index.js";
|
|
5
|
-
import { DatePicker } from "@powerhousedao/document-engineering";
|
|
5
|
+
import { DatePicker, Icon, Button } from "@powerhousedao/document-engineering";
|
|
6
6
|
import { WalletsTable } from "./components/WalletsTable.js";
|
|
7
7
|
import { AggregatedExpensesTable } from "./components/AggregatedExpensesTable.js";
|
|
8
8
|
import { AddBillingStatementModal } from "./components/AddBillingStatementModal.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { ExpenseReportPDF } from "./components/ExpenseReportPDF.js";
|
|
10
|
+
import { pdf } from "@react-pdf/renderer";
|
|
11
11
|
export function Editor() {
|
|
12
12
|
const [document, dispatch] = useSelectedExpenseReportDocument();
|
|
13
13
|
const [selectedWallet, setSelectedWallet] = useState(null);
|
|
@@ -15,41 +15,6 @@ export function Editor() {
|
|
|
15
15
|
const [periodStart, setPeriodStart] = useState(document.state.global.periodStart || "");
|
|
16
16
|
const [periodEnd, setPeriodEnd] = useState(document.state.global.periodEnd || "");
|
|
17
17
|
const { wallets, groups } = document.state.global;
|
|
18
|
-
// Check sync status
|
|
19
|
-
const { needsSync, outdatedWallets, tagChangedWallets } = useWalletSync(wallets);
|
|
20
|
-
const { syncWallet } = useSyncWallet();
|
|
21
|
-
// Auto-sync on component mount
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (needsSync && outdatedWallets.length > 0) {
|
|
24
|
-
if (tagChangedWallets.length > 0) {
|
|
25
|
-
console.warn("⚠️ Tag changes detected in wallets:", tagChangedWallets);
|
|
26
|
-
console.log("Auto-syncing wallets with tag changes:", outdatedWallets);
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
console.log("Auto-syncing wallets:", outdatedWallets);
|
|
30
|
-
}
|
|
31
|
-
// Sync each outdated wallet
|
|
32
|
-
outdatedWallets.forEach((walletAddress) => {
|
|
33
|
-
const wallet = wallets.find((w) => w.wallet === walletAddress);
|
|
34
|
-
if (!wallet || !wallet.billingStatements || wallet.billingStatements.length === 0) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
// Remove all existing line items first
|
|
38
|
-
const lineItemsToRemove = [...(wallet.lineItems || [])];
|
|
39
|
-
lineItemsToRemove.forEach((item) => {
|
|
40
|
-
if (item?.id) {
|
|
41
|
-
dispatch(actions.removeLineItem({
|
|
42
|
-
wallet: wallet.wallet,
|
|
43
|
-
lineItemId: item.id,
|
|
44
|
-
}));
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
// Re-extract line items from billing statements
|
|
48
|
-
const billingStatementIds = wallet.billingStatements.filter((id) => id !== null && id !== undefined);
|
|
49
|
-
syncWallet(wallet.wallet, billingStatementIds, groups, dispatch);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}, [needsSync, outdatedWallets, wallets, groups, dispatch, syncWallet]);
|
|
53
18
|
// Handle period date changes
|
|
54
19
|
const handlePeriodStartChange = (e) => {
|
|
55
20
|
const value = e.target.value;
|
|
@@ -75,6 +40,27 @@ export function Editor() {
|
|
|
75
40
|
setIsModalOpen(false);
|
|
76
41
|
setSelectedWallet(null);
|
|
77
42
|
};
|
|
43
|
+
// Handle PDF export
|
|
44
|
+
const handleExportPDF = async () => {
|
|
45
|
+
try {
|
|
46
|
+
const blob = await pdf(_jsx(ExpenseReportPDF, { periodStart: periodStart, periodEnd: periodEnd, wallets: wallets, groups: groups })).toBlob();
|
|
47
|
+
// Create download link
|
|
48
|
+
const url = URL.createObjectURL(blob);
|
|
49
|
+
const link = window.document.createElement("a");
|
|
50
|
+
link.href = url;
|
|
51
|
+
// Generate filename with period
|
|
52
|
+
const filename = periodStart
|
|
53
|
+
? `expense-report-${new Date(periodStart).toISOString().split('T')[0]}.pdf`
|
|
54
|
+
: "expense-report.pdf";
|
|
55
|
+
link.download = filename;
|
|
56
|
+
link.click();
|
|
57
|
+
// Cleanup
|
|
58
|
+
URL.revokeObjectURL(url);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error("Error generating PDF:", error);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
78
64
|
// Format period title for the breakdown section
|
|
79
65
|
const breakdownTitle = useMemo(() => {
|
|
80
66
|
if (!periodStart)
|
|
@@ -84,5 +70,5 @@ export function Editor() {
|
|
|
84
70
|
const year = date.getFullYear();
|
|
85
71
|
return `${month} ${year} Breakdown`;
|
|
86
72
|
}, [periodStart]);
|
|
87
|
-
return (_jsxs("div", { className: "ph-default-styles flex flex-col h-full w-full bg-gray-50 dark:bg-gray-900", children: [_jsx("div", { className: "bg-white dark:bg-gray-800
|
|
73
|
+
return (_jsxs("div", { className: "ph-default-styles flex flex-col h-full w-full bg-gray-50 dark:bg-gray-900", children: [_jsx("div", { className: "flex-1 overflow-auto px-8 py-6", children: _jsxs("div", { className: "max-w-7xl mx-auto space-y-8", children: [_jsx("section", { className: "bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700", children: _jsx("div", { className: "px-6 py-6", children: _jsxs("div", { className: "relative", children: [_jsxs("div", { className: "text-center", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900 dark:text-white mb-4", children: "Expense Report" }), _jsx("div", { className: "flex items-center justify-center gap-4 text-sm text-gray-600 dark:text-gray-400", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium", children: "Period:" }), _jsx(DatePicker, { name: "periodStart", value: periodStart, onChange: handlePeriodStartChange }), _jsx("span", { children: "to" }), _jsx(DatePicker, { name: "periodEnd", value: periodEnd, onChange: handlePeriodEndChange })] }) })] }), _jsxs(Button, { variant: "ghost", onClick: handleExportPDF, className: "absolute top-0 right-0 flex items-center gap-2", children: [_jsx(Icon, { name: "ExportPdf", size: 18 }), "Export to PDF"] })] }) }) }), _jsxs("section", { className: "bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700", children: [_jsx("div", { className: "px-6 py-4 border-b border-gray-200 dark:border-gray-700", children: _jsx("h2", { className: "text-xl font-semibold text-gray-900 dark:text-white", children: "Wallets" }) }), _jsx("div", { className: "p-6", children: _jsx(WalletsTable, { wallets: wallets, groups: groups, onAddBillingStatement: handleAddBillingStatement, dispatch: dispatch }) })] }), wallets.length > 0 && (_jsxs("section", { className: "bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700", children: [_jsx("div", { className: "px-6 py-4 border-b border-gray-200 dark:border-gray-700", children: _jsx("h2", { className: "text-xl font-semibold text-gray-900 dark:text-white", children: breakdownTitle }) }), _jsx("div", { className: "p-6", children: _jsx(AggregatedExpensesTable, { wallets: wallets, groups: groups, periodStart: periodStart, periodEnd: periodEnd, dispatch: dispatch }) })] }))] }) }), isModalOpen && selectedWallet && (_jsx(AddBillingStatementModal, { isOpen: isModalOpen, onClose: handleCloseModal, walletAddress: selectedWallet, dispatch: dispatch, groups: groups }))] }));
|
|
88
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSyncWallet.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/hooks/useSyncWallet.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAkB1F,wBAAgB,aAAa;gCAIV,MAAM,uBACA,MAAM,EAAE,UACrB,aAAa,EAAE,YACb,GAAG;
|
|
1
|
+
{"version":3,"file":"useSyncWallet.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/hooks/useSyncWallet.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAkB1F,wBAAgB,aAAa;gCAIV,MAAM,uBACA,MAAM,EAAE,UACrB,aAAa,EAAE,YACb,GAAG;EA0FhB"}
|
|
@@ -23,10 +23,9 @@ export function useSyncWallet() {
|
|
|
23
23
|
const group = groups.find((g) => g.label === expenseAccountTag.label);
|
|
24
24
|
return group ? group.id : null;
|
|
25
25
|
};
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
// Extract and add line items from all billing statements
|
|
26
|
+
// Aggregate line items by category
|
|
27
|
+
const categoryAggregation = new Map();
|
|
28
|
+
// Extract and aggregate line items from all billing statements
|
|
30
29
|
billingStatementIds.forEach((statementId) => {
|
|
31
30
|
const statement = billingStatements.get(statementId);
|
|
32
31
|
if (!statement?.state?.global?.lineItems)
|
|
@@ -34,22 +33,43 @@ export function useSyncWallet() {
|
|
|
34
33
|
const lineItems = statement.state.global.lineItems || [];
|
|
35
34
|
lineItems.forEach((billingLineItem) => {
|
|
36
35
|
const groupId = mapTagToGroup(billingLineItem);
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
const categoryKey = groupId || "uncategorized";
|
|
37
|
+
const existing = categoryAggregation.get(categoryKey);
|
|
38
|
+
if (existing) {
|
|
39
|
+
// Aggregate values for the same category
|
|
40
|
+
existing.actuals += billingLineItem.totalPriceCash || 0;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// Create new category entry
|
|
44
|
+
const group = groups.find((g) => g.id === groupId);
|
|
45
|
+
categoryAggregation.set(categoryKey, {
|
|
46
|
+
groupId: groupId,
|
|
47
|
+
groupLabel: group?.label || "Uncategorised",
|
|
48
|
+
budget: 0,
|
|
49
|
+
actuals: billingLineItem.totalPriceCash || 0,
|
|
50
|
+
forecast: 0,
|
|
51
|
+
payments: 0,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
51
54
|
});
|
|
52
55
|
});
|
|
56
|
+
// Now add aggregated line items to wallet
|
|
57
|
+
categoryAggregation.forEach((aggregatedItem) => {
|
|
58
|
+
const expenseLineItem = {
|
|
59
|
+
id: generateId(),
|
|
60
|
+
label: aggregatedItem.groupLabel,
|
|
61
|
+
group: aggregatedItem.groupId,
|
|
62
|
+
budget: aggregatedItem.budget,
|
|
63
|
+
actuals: aggregatedItem.actuals,
|
|
64
|
+
forecast: aggregatedItem.forecast,
|
|
65
|
+
payments: aggregatedItem.payments,
|
|
66
|
+
comments: null,
|
|
67
|
+
};
|
|
68
|
+
dispatch(actions.addLineItem({
|
|
69
|
+
wallet: walletAddress,
|
|
70
|
+
lineItem: expenseLineItem,
|
|
71
|
+
}));
|
|
72
|
+
});
|
|
53
73
|
};
|
|
54
74
|
return { syncWallet };
|
|
55
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWalletSync.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/hooks/useWalletSync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sDAAsD,CAAC;AAEnF,UAAU,UAAU;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"useWalletSync.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/hooks/useWalletSync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sDAAsD,CAAC;AAEnF,UAAU,UAAU;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,UAAU,CAoG3D"}
|