@powerhousedao/contributor-billing 0.1.3 → 0.1.4

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.
Files changed (159) hide show
  1. package/dist/document-models/expense-report/gen/actions.d.ts +4 -0
  2. package/dist/document-models/expense-report/gen/actions.d.ts.map +1 -0
  3. package/dist/document-models/expense-report/gen/actions.js +1 -0
  4. package/dist/document-models/expense-report/gen/creators.d.ts +2 -0
  5. package/dist/document-models/expense-report/gen/creators.d.ts.map +1 -0
  6. package/dist/document-models/expense-report/gen/creators.js +1 -0
  7. package/dist/document-models/expense-report/gen/document-model.d.ts +3 -0
  8. package/dist/document-models/expense-report/gen/document-model.d.ts.map +1 -0
  9. package/dist/document-models/expense-report/gen/document-model.js +202 -0
  10. package/dist/document-models/expense-report/gen/expense-report/actions.d.ts +8 -0
  11. package/dist/document-models/expense-report/gen/expense-report/actions.d.ts.map +1 -0
  12. package/dist/document-models/expense-report/gen/expense-report/actions.js +1 -0
  13. package/dist/document-models/expense-report/gen/expense-report/creators.d.ts +4 -0
  14. package/dist/document-models/expense-report/gen/expense-report/creators.d.ts.map +1 -0
  15. package/dist/document-models/expense-report/gen/expense-report/creators.js +3 -0
  16. package/dist/document-models/expense-report/gen/expense-report/error.d.ts +2 -0
  17. package/dist/document-models/expense-report/gen/expense-report/error.d.ts.map +1 -0
  18. package/dist/document-models/expense-report/gen/expense-report/error.js +1 -0
  19. package/dist/document-models/expense-report/gen/expense-report/object.d.ts +7 -0
  20. package/dist/document-models/expense-report/gen/expense-report/object.d.ts.map +1 -0
  21. package/dist/document-models/expense-report/gen/expense-report/object.js +7 -0
  22. package/dist/document-models/expense-report/gen/expense-report/operations.d.ts +7 -0
  23. package/dist/document-models/expense-report/gen/expense-report/operations.d.ts.map +1 -0
  24. package/dist/document-models/expense-report/gen/expense-report/operations.js +1 -0
  25. package/dist/document-models/expense-report/gen/index.d.ts +8 -0
  26. package/dist/document-models/expense-report/gen/index.d.ts.map +1 -0
  27. package/dist/document-models/expense-report/gen/index.js +6 -0
  28. package/dist/document-models/expense-report/gen/object.d.ts +15 -0
  29. package/dist/document-models/expense-report/gen/object.d.ts.map +1 -0
  30. package/dist/document-models/expense-report/gen/object.js +25 -0
  31. package/dist/document-models/expense-report/gen/ph-factories.d.ts +27 -0
  32. package/dist/document-models/expense-report/gen/ph-factories.d.ts.map +1 -0
  33. package/dist/document-models/expense-report/gen/ph-factories.js +189 -0
  34. package/dist/document-models/expense-report/gen/reducer.d.ts +5 -0
  35. package/dist/document-models/expense-report/gen/reducer.d.ts.map +1 -0
  36. package/dist/document-models/expense-report/gen/reducer.js +76 -0
  37. package/dist/document-models/expense-report/gen/schema/index.d.ts +3 -0
  38. package/dist/document-models/expense-report/gen/schema/index.d.ts.map +1 -0
  39. package/dist/document-models/expense-report/gen/schema/index.js +2 -0
  40. package/dist/document-models/expense-report/gen/schema/types.d.ts +254 -0
  41. package/dist/document-models/expense-report/gen/schema/types.d.ts.map +1 -0
  42. package/dist/document-models/expense-report/gen/schema/types.js +1 -0
  43. package/dist/document-models/expense-report/gen/schema/zod.d.ts +32 -0
  44. package/dist/document-models/expense-report/gen/schema/zod.d.ts.map +1 -0
  45. package/dist/document-models/expense-report/gen/schema/zod.js +216 -0
  46. package/dist/document-models/expense-report/gen/types.d.ts +10 -0
  47. package/dist/document-models/expense-report/gen/types.d.ts.map +1 -0
  48. package/dist/document-models/expense-report/gen/types.js +1 -0
  49. package/dist/document-models/expense-report/gen/utils.d.ts +22 -0
  50. package/dist/document-models/expense-report/gen/utils.d.ts.map +1 -0
  51. package/dist/document-models/expense-report/gen/utils.js +181 -0
  52. package/dist/document-models/expense-report/gen/wallet/actions.d.ts +64 -0
  53. package/dist/document-models/expense-report/gen/wallet/actions.d.ts.map +1 -0
  54. package/dist/document-models/expense-report/gen/wallet/actions.js +1 -0
  55. package/dist/document-models/expense-report/gen/wallet/creators.d.ts +18 -0
  56. package/dist/document-models/expense-report/gen/wallet/creators.d.ts.map +1 -0
  57. package/dist/document-models/expense-report/gen/wallet/creators.js +17 -0
  58. package/dist/document-models/expense-report/gen/wallet/error.d.ts +2 -0
  59. package/dist/document-models/expense-report/gen/wallet/error.d.ts.map +1 -0
  60. package/dist/document-models/expense-report/gen/wallet/error.js +1 -0
  61. package/dist/document-models/expense-report/gen/wallet/object.d.ts +21 -0
  62. package/dist/document-models/expense-report/gen/wallet/object.d.ts.map +1 -0
  63. package/dist/document-models/expense-report/gen/wallet/object.js +49 -0
  64. package/dist/document-models/expense-report/gen/wallet/operations.d.ts +21 -0
  65. package/dist/document-models/expense-report/gen/wallet/operations.d.ts.map +1 -0
  66. package/dist/document-models/expense-report/gen/wallet/operations.js +1 -0
  67. package/dist/document-models/expense-report/index.d.ts +39 -0
  68. package/dist/document-models/expense-report/index.d.ts.map +1 -0
  69. package/dist/document-models/expense-report/index.js +21 -0
  70. package/dist/document-models/expense-report/src/reducers/wallet.d.ts +3 -0
  71. package/dist/document-models/expense-report/src/reducers/wallet.d.ts.map +1 -0
  72. package/dist/document-models/expense-report/src/reducers/wallet.js +180 -0
  73. package/dist/document-models/expense-report/src/tests/document-model.test.d.ts +6 -0
  74. package/dist/document-models/expense-report/src/tests/document-model.test.d.ts.map +1 -0
  75. package/dist/document-models/expense-report/src/tests/document-model.test.js +18 -0
  76. package/dist/document-models/expense-report/src/tests/expense-report.test.d.ts +6 -0
  77. package/dist/document-models/expense-report/src/tests/expense-report.test.d.ts.map +1 -0
  78. package/dist/document-models/expense-report/src/tests/expense-report.test.js +24 -0
  79. package/dist/document-models/expense-report/src/tests/wallet.test.d.ts +6 -0
  80. package/dist/document-models/expense-report/src/tests/wallet.test.d.ts.map +1 -0
  81. package/dist/document-models/expense-report/src/tests/wallet.test.js +24 -0
  82. package/dist/document-models/expense-report/src/utils.d.ts +2 -0
  83. package/dist/document-models/expense-report/src/utils.d.ts.map +1 -0
  84. package/dist/document-models/expense-report/src/utils.js +1 -0
  85. package/dist/document-models/index.d.ts +1 -0
  86. package/dist/document-models/index.d.ts.map +1 -1
  87. package/dist/document-models/index.js +1 -0
  88. package/dist/document-models/integrations/gen/ph-factories.d.ts.map +1 -1
  89. package/dist/document-models/integrations/gen/ph-factories.js +2 -14
  90. package/dist/document-models/integrations/gen/utils.d.ts.map +1 -1
  91. package/dist/document-models/integrations/gen/utils.js +2 -14
  92. package/dist/document-models/invoice/gen/ph-factories.d.ts.map +1 -1
  93. package/dist/document-models/invoice/gen/ph-factories.js +2 -5
  94. package/dist/document-models/invoice/gen/schema/types.d.ts +1 -1
  95. package/dist/document-models/invoice/gen/schema/types.d.ts.map +1 -1
  96. package/dist/document-models/invoice/gen/utils.d.ts.map +1 -1
  97. package/dist/document-models/invoice/gen/utils.js +1 -4
  98. package/dist/editors/billing-statement/components/lineItemsTable.d.ts.map +1 -1
  99. package/dist/editors/billing-statement/components/lineItemsTable.js +71 -13
  100. package/dist/editors/billing-statement/editor.js +1 -1
  101. package/dist/editors/contributor-billing/components/DriveExplorer.d.ts.map +1 -1
  102. package/dist/editors/contributor-billing/components/DriveExplorer.js +8 -4
  103. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts +4 -1
  104. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts.map +1 -1
  105. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.js +2 -2
  106. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.d.ts.map +1 -1
  107. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.js +24 -1
  108. package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts +11 -0
  109. package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts.map +1 -0
  110. package/dist/editors/expense-report/components/AddBillingStatementModal.js +170 -0
  111. package/dist/editors/expense-report/components/AggregatedExpensesTable.d.ts +11 -0
  112. package/dist/editors/expense-report/components/AggregatedExpensesTable.d.ts.map +1 -0
  113. package/dist/editors/expense-report/components/AggregatedExpensesTable.js +232 -0
  114. package/dist/editors/expense-report/components/WalletsTable.d.ts +10 -0
  115. package/dist/editors/expense-report/components/WalletsTable.d.ts.map +1 -0
  116. package/dist/editors/expense-report/components/WalletsTable.js +164 -0
  117. package/dist/editors/expense-report/editor.d.ts +2 -0
  118. package/dist/editors/expense-report/editor.d.ts.map +1 -0
  119. package/dist/editors/expense-report/editor.js +88 -0
  120. package/dist/editors/expense-report/hooks/useSyncWallet.d.ts +5 -0
  121. package/dist/editors/expense-report/hooks/useSyncWallet.d.ts.map +1 -0
  122. package/dist/editors/expense-report/hooks/useSyncWallet.js +55 -0
  123. package/dist/editors/expense-report/hooks/useWalletSync.d.ts +9 -0
  124. package/dist/editors/expense-report/hooks/useWalletSync.d.ts.map +1 -0
  125. package/dist/editors/expense-report/hooks/useWalletSync.js +79 -0
  126. package/dist/editors/expense-report/index.d.ts +3 -0
  127. package/dist/editors/expense-report/index.d.ts.map +1 -0
  128. package/dist/editors/expense-report/index.js +11 -0
  129. package/dist/editors/hooks/useExpenseReportDocument.d.ts +4 -0
  130. package/dist/editors/hooks/useExpenseReportDocument.d.ts.map +1 -0
  131. package/dist/editors/hooks/useExpenseReportDocument.js +8 -0
  132. package/dist/editors/index.d.ts +1 -0
  133. package/dist/editors/index.d.ts.map +1 -1
  134. package/dist/editors/index.js +1 -0
  135. package/dist/editors/invoice/components/statusModalComponents.d.ts.map +1 -1
  136. package/dist/editors/invoice/components/statusModalComponents.js +4 -4
  137. package/dist/editors/invoice/editor.js +1 -1
  138. package/dist/editors/invoice/ingestPDF.d.ts.map +1 -1
  139. package/dist/editors/invoice/ingestPDF.js +3 -3
  140. package/dist/editors/invoice/invoiceToGnosis.js +1 -1
  141. package/dist/editors/invoice/requestFinance.js +1 -1
  142. package/dist/editors/invoice/uploadPdfChunked.js +1 -1
  143. package/dist/index.d.ts +1 -1
  144. package/dist/index.d.ts.map +1 -1
  145. package/dist/powerhouse.manifest.json +13 -2
  146. package/dist/style.css +525 -37
  147. package/dist/subgraphs/expense-report/index.d.ts +11 -0
  148. package/dist/subgraphs/expense-report/index.d.ts.map +1 -0
  149. package/dist/subgraphs/expense-report/index.js +11 -0
  150. package/dist/subgraphs/expense-report/resolvers.d.ts +3 -0
  151. package/dist/subgraphs/expense-report/resolvers.d.ts.map +1 -0
  152. package/dist/subgraphs/expense-report/resolvers.js +252 -0
  153. package/dist/subgraphs/expense-report/schema.d.ts +3 -0
  154. package/dist/subgraphs/expense-report/schema.d.ts.map +1 -0
  155. package/dist/subgraphs/expense-report/schema.js +228 -0
  156. package/dist/subgraphs/index.d.ts +1 -0
  157. package/dist/subgraphs/index.d.ts.map +1 -1
  158. package/dist/subgraphs/index.js +1 -0
  159. package/package.json +13 -13
@@ -0,0 +1,232 @@
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
+ // Format period for title
13
+ const periodTitle = useMemo(() => {
14
+ if (!periodStart)
15
+ return "Breakdown";
16
+ const date = new Date(periodStart);
17
+ const month = date.toLocaleDateString("en-US", { month: "short" });
18
+ const year = date.getFullYear();
19
+ return `${month} ${year} Breakdown`;
20
+ }, [periodStart]);
21
+ // Create a map of groups with their parent info
22
+ const groupsMap = useMemo(() => {
23
+ const map = new Map();
24
+ groups.forEach((group) => {
25
+ map.set(group.id, { group });
26
+ });
27
+ // Add parent references
28
+ groups.forEach((group) => {
29
+ if (group.parentId) {
30
+ const entry = map.get(group.id);
31
+ const parentEntry = map.get(group.parentId);
32
+ if (entry && parentEntry) {
33
+ entry.parent = parentEntry.group;
34
+ }
35
+ }
36
+ });
37
+ return map;
38
+ }, [groups]);
39
+ // Get line items for the active wallet with group information
40
+ const walletLineItems = useMemo(() => {
41
+ if (!wallets[activeWalletIndex])
42
+ return [];
43
+ const wallet = wallets[activeWalletIndex];
44
+ const lineItems = wallet.lineItems || [];
45
+ return lineItems
46
+ .filter((item) => item !== null && item !== undefined)
47
+ .map((item) => {
48
+ const groupInfo = item.group ? groupsMap.get(item.group) : undefined;
49
+ return {
50
+ ...item,
51
+ groupLabel: groupInfo?.group.label || undefined,
52
+ parentGroupId: groupInfo?.parent?.id || null,
53
+ parentGroupLabel: groupInfo?.parent?.label || undefined,
54
+ };
55
+ });
56
+ }, [wallets, activeWalletIndex, groupsMap]);
57
+ // Group line items by category and aggregate by parent category
58
+ const groupedAndAggregatedItems = useMemo(() => {
59
+ // First, aggregate line items by their category (group)
60
+ const categoryAggregation = new Map();
61
+ walletLineItems.forEach((item) => {
62
+ if (!item)
63
+ return;
64
+ const categoryKey = item.group || "uncategorized";
65
+ const existing = categoryAggregation.get(categoryKey);
66
+ if (existing) {
67
+ // Aggregate values for the same category
68
+ existing.budget += item.budget || 0;
69
+ existing.forecast += item.forecast || 0;
70
+ existing.actuals += item.actuals || 0;
71
+ existing.payments += item.payments || 0;
72
+ // Comment stays the same (first item's comment is used)
73
+ }
74
+ else {
75
+ // Create new category entry
76
+ categoryAggregation.set(categoryKey, {
77
+ groupId: categoryKey,
78
+ groupLabel: item.groupLabel || "Uncategorised",
79
+ parentGroupId: item.parentGroupId,
80
+ parentGroupLabel: item.parentGroupLabel,
81
+ budget: item.budget || 0,
82
+ forecast: item.forecast || 0,
83
+ actuals: item.actuals || 0,
84
+ payments: item.payments || 0,
85
+ comment: item.comments || "",
86
+ });
87
+ }
88
+ });
89
+ // Then, group aggregated categories by their parent
90
+ const grouped = new Map();
91
+ categoryAggregation.forEach((aggItem) => {
92
+ const parentKey = aggItem.parentGroupId || "uncategorized";
93
+ const items = grouped.get(parentKey) || [];
94
+ items.push(aggItem);
95
+ grouped.set(parentKey, items);
96
+ });
97
+ return grouped;
98
+ }, [walletLineItems]);
99
+ // Calculate subtotals for each parent group
100
+ const calculateSubtotal = (items) => {
101
+ return items.reduce((acc, item) => ({
102
+ budget: acc.budget + item.budget,
103
+ forecast: acc.forecast + item.forecast,
104
+ actuals: acc.actuals + item.actuals,
105
+ difference: acc.difference + (item.actuals - item.budget),
106
+ payments: acc.payments + item.payments,
107
+ }), { budget: 0, forecast: 0, actuals: 0, difference: 0, payments: 0 });
108
+ };
109
+ // Calculate grand totals
110
+ const grandTotals = useMemo(() => {
111
+ return walletLineItems.reduce((acc, item) => ({
112
+ budget: acc.budget + (item?.budget || 0),
113
+ forecast: acc.forecast + (item?.forecast || 0),
114
+ actuals: acc.actuals + (item?.actuals || 0),
115
+ difference: acc.difference + ((item?.actuals || 0) - (item?.budget || 0)),
116
+ payments: acc.payments + (item?.payments || 0),
117
+ }), { budget: 0, forecast: 0, actuals: 0, difference: 0, payments: 0 });
118
+ }, [walletLineItems]);
119
+ const formatNumber = (value) => {
120
+ return new Intl.NumberFormat("en-US", {
121
+ minimumFractionDigits: 2,
122
+ maximumFractionDigits: 2,
123
+ }).format(value);
124
+ };
125
+ const formatWalletAddress = (address) => {
126
+ if (!address || address.length < 13)
127
+ return address;
128
+ return `${address.substring(0, 6)}...${address.substring(address.length - 6)}`;
129
+ };
130
+ // Handle starting comment edit
131
+ const handleStartEdit = (groupId, currentComment) => {
132
+ setEditingGroupId(groupId);
133
+ setEditingComment(currentComment);
134
+ };
135
+ // Handle saving comment
136
+ const handleSaveComment = (groupId) => {
137
+ const wallet = wallets[activeWalletIndex];
138
+ if (!wallet || !wallet.wallet)
139
+ return;
140
+ // Find all line items with this group ID
141
+ const lineItemsToUpdate = wallet.lineItems?.filter((item) => item?.group === groupId) || [];
142
+ // Create all update actions
143
+ const updateActions = lineItemsToUpdate
144
+ .filter((item) => item?.id)
145
+ .map((item) => actions.updateLineItem({
146
+ wallet: wallet.wallet,
147
+ lineItemId: item.id,
148
+ comments: editingComment,
149
+ }));
150
+ // Dispatch all actions at once
151
+ if (updateActions.length > 0) {
152
+ dispatch(updateActions);
153
+ }
154
+ // Reset editing state
155
+ setEditingGroupId(null);
156
+ setEditingComment("");
157
+ };
158
+ // Handle canceling comment edit
159
+ const handleCancelEdit = () => {
160
+ setEditingGroupId(null);
161
+ setEditingComment("");
162
+ };
163
+ // Sort parent groups: Headcount first, then Non-Headcount, then others, then uncategorized
164
+ const sortedParentKeys = useMemo(() => {
165
+ const keys = Array.from(groupedAndAggregatedItems.keys());
166
+ // Find Headcount and Non-Headcount group IDs
167
+ const headcountGroup = groups.find(g => g.label === "Headcount Expenses");
168
+ const nonHeadcountGroup = groups.find(g => g.label === "Non-Headcount Expenses");
169
+ return keys.sort((a, b) => {
170
+ // Uncategorized always goes last
171
+ if (a === "uncategorized")
172
+ return 1;
173
+ if (b === "uncategorized")
174
+ return -1;
175
+ // Headcount Expenses always first
176
+ if (a === headcountGroup?.id)
177
+ return -1;
178
+ if (b === headcountGroup?.id)
179
+ return 1;
180
+ // Non-Headcount Expenses always second
181
+ if (a === nonHeadcountGroup?.id)
182
+ return -1;
183
+ if (b === nonHeadcountGroup?.id)
184
+ return 1;
185
+ // For other groups, maintain their original order
186
+ return 0;
187
+ });
188
+ }, [groupedAndAggregatedItems, groups]);
189
+ if (wallets.length === 0) {
190
+ return null;
191
+ }
192
+ 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) => {
193
+ const isActive = index === activeWalletIndex;
194
+ return (_jsx("button", { onClick: () => setActiveWalletIndex(index), className: `
195
+ whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors
196
+ ${isActive
197
+ ? "border-green-500 text-green-600 dark:text-green-400"
198
+ : "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"}
199
+ `, children: wallet.name || formatWalletAddress(wallet.wallet || "") }, wallet.wallet || index));
200
+ }) }) }), _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-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Comments" }), _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" })] }) }), _jsxs("tbody", { className: "divide-y divide-gray-200 dark:divide-gray-700", children: [sortedParentKeys.map((parentKey) => {
201
+ const items = groupedAndAggregatedItems.get(parentKey) || [];
202
+ if (items.length === 0)
203
+ return null;
204
+ const subtotals = calculateSubtotal(items);
205
+ const parentLabel = parentKey === "uncategorized"
206
+ ? "Uncategorised"
207
+ : items[0]?.parentGroupLabel || "Other";
208
+ 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) => {
209
+ if (!item)
210
+ return null;
211
+ const difference = item.actuals - item.budget;
212
+ const isEditing = editingGroupId === item.groupId;
213
+ return (_jsxs("tr", { className: "bg-white dark:bg-gray-900 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors", 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: formatNumber(item.budget) }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatNumber(item.forecast) }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatNumber(item.actuals) }), _jsx("td", { className: `px-6 py-3 whitespace-nowrap text-right text-sm font-medium ${difference > 0
214
+ ? "text-red-600 dark:text-red-400"
215
+ : difference < 0
216
+ ? "text-green-600 dark:text-green-400"
217
+ : "text-gray-900 dark:text-white"}`, children: formatNumber(difference) }), _jsx("td", { className: "px-6 py-3 text-sm", children: isEditing ? (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("div", { className: "flex-1", children: _jsx(Textarea, { value: editingComment, onChange: (e) => setEditingComment(e.target.value), placeholder: "Add comment...", autoExpand: true, multiline: true, onKeyDown: (e) => {
218
+ if (e.key === "Escape") {
219
+ handleCancelEdit();
220
+ }
221
+ } }) }), _jsxs("div", { className: "flex gap-1 mt-1", children: [_jsx("button", { onClick: () => handleSaveComment(item.groupId), className: "px-2 py-1 text-xs bg-green-600 text-white rounded hover:bg-green-700", title: "Save", children: "\u2713" }), _jsx("button", { onClick: handleCancelEdit, className: "px-2 py-1 text-xs bg-gray-400 text-white rounded hover:bg-gray-500", title: "Cancel", children: "\u2715" })] })] })) : (_jsxs("div", { className: "flex items-center gap-2 group", children: [_jsx("span", { className: "text-gray-600 dark:text-gray-400 italic flex-1 whitespace-pre-wrap", children: item.comment || "No comments" }), _jsx("button", { onClick: () => handleStartEdit(item.groupId, item.comment), className: "opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded", title: "Edit comment", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-4 w-4 text-gray-600 dark:text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" }) }) })] })) }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatNumber(item.payments) })] }, item.groupId));
222
+ }), _jsxs("tr", { className: "bg-gray-50 dark:bg-gray-800/50 font-semibold", 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: formatNumber(subtotals.budget) }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatNumber(subtotals.forecast) }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatNumber(subtotals.actuals) }), _jsx("td", { className: `px-6 py-3 whitespace-nowrap text-right text-sm font-bold ${subtotals.difference > 0
223
+ ? "text-red-600 dark:text-red-400"
224
+ : subtotals.difference < 0
225
+ ? "text-green-600 dark:text-green-400"
226
+ : "text-gray-900 dark:text-white"}`, children: formatNumber(subtotals.difference) }), _jsx("td", { className: "px-6 py-3" }), _jsx("td", { className: "px-6 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatNumber(subtotals.payments) })] })] }, parentKey));
227
+ }), _jsxs("tr", { className: "bg-gray-100 dark:bg-gray-800 font-bold", 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: formatNumber(grandTotals.budget) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatNumber(grandTotals.forecast) }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatNumber(grandTotals.actuals) }), _jsx("td", { className: `px-6 py-4 whitespace-nowrap text-right text-sm ${grandTotals.difference > 0
228
+ ? "text-red-600 dark:text-red-400"
229
+ : grandTotals.difference < 0
230
+ ? "text-green-600 dark:text-green-400"
231
+ : "text-gray-900 dark:text-white"}`, children: formatNumber(grandTotals.difference) }), _jsx("td", { className: "px-6 py-4" }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: formatNumber(grandTotals.payments) })] })] })] }) })] }));
232
+ }
@@ -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"}
@@ -0,0 +1,164 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Button, TextInput } from "@powerhousedao/document-engineering";
4
+ import { Plus, Trash2, Pencil, Check, X, Copy, CheckCheck, RefreshCw } from "lucide-react";
5
+ import { actions } from "../../../document-models/expense-report/index.js";
6
+ import { useWalletSync } from "../hooks/useWalletSync.js";
7
+ import { useSyncWallet } from "../hooks/useSyncWallet.js";
8
+ export function WalletsTable({ wallets, groups, onAddBillingStatement, dispatch, }) {
9
+ const [newWalletAddress, setNewWalletAddress] = useState("");
10
+ const [newWalletName, setNewWalletName] = useState("");
11
+ const [walletError, setWalletError] = useState("");
12
+ const [hoveredWallet, setHoveredWallet] = useState(null);
13
+ const [editingWallet, setEditingWallet] = useState(null);
14
+ const [editingName, setEditingName] = useState("");
15
+ const [copiedWallet, setCopiedWallet] = useState(null);
16
+ const [syncingWallet, setSyncingWallet] = useState(null);
17
+ // Check sync status
18
+ const { needsSync, outdatedWallets, tagChangedWallets } = useWalletSync(wallets);
19
+ const { syncWallet } = useSyncWallet();
20
+ const handleAddWallet = () => {
21
+ const trimmedAddress = newWalletAddress.trim();
22
+ if (trimmedAddress) {
23
+ // Check if wallet already exists
24
+ const walletExists = wallets.some(w => w.wallet === trimmedAddress);
25
+ if (walletExists) {
26
+ setWalletError("This wallet already exists");
27
+ return;
28
+ }
29
+ dispatch(actions.addWallet({
30
+ wallet: trimmedAddress,
31
+ name: newWalletName.trim() || undefined,
32
+ }));
33
+ setNewWalletAddress("");
34
+ setNewWalletName("");
35
+ setWalletError("");
36
+ }
37
+ };
38
+ const handleStartEditName = (wallet) => {
39
+ setEditingWallet(wallet.wallet || null);
40
+ setEditingName(wallet.name || "");
41
+ };
42
+ const handleSaveEditName = (walletAddress) => {
43
+ const wallet = wallets.find(w => w.wallet === walletAddress);
44
+ const trimmedName = editingName.trim();
45
+ // Only update if the name has changed
46
+ if (trimmedName && wallet && trimmedName !== (wallet.name || "")) {
47
+ dispatch(actions.updateWallet({
48
+ address: walletAddress,
49
+ name: trimmedName,
50
+ }));
51
+ }
52
+ setEditingWallet(null);
53
+ setEditingName("");
54
+ };
55
+ const handleCancelEditName = () => {
56
+ setEditingWallet(null);
57
+ setEditingName("");
58
+ };
59
+ const handleCopyAddress = (address) => {
60
+ navigator.clipboard.writeText(address);
61
+ setCopiedWallet(address);
62
+ setTimeout(() => setCopiedWallet(null), 2000);
63
+ };
64
+ const formatAddress = (address) => {
65
+ if (!address || address.length < 11)
66
+ return address;
67
+ return `${address.substring(0, 6)}...${address.substring(address.length - 5)}`;
68
+ };
69
+ const handleSyncWallet = async (wallet) => {
70
+ if (!wallet.wallet || !wallet.billingStatements || wallet.billingStatements.length === 0) {
71
+ return;
72
+ }
73
+ setSyncingWallet(wallet.wallet);
74
+ try {
75
+ // Remove all existing line items first
76
+ const lineItemsToRemove = [...(wallet.lineItems || [])];
77
+ lineItemsToRemove.forEach((item) => {
78
+ if (item?.id) {
79
+ dispatch(actions.removeLineItem({
80
+ wallet: wallet.wallet,
81
+ lineItemId: item.id,
82
+ }));
83
+ }
84
+ });
85
+ // Re-extract line items from billing statements
86
+ const billingStatementIds = wallet.billingStatements.filter((id) => id !== null && id !== undefined);
87
+ syncWallet(wallet.wallet, billingStatementIds, groups, dispatch);
88
+ // Small delay to show sync animation
89
+ setTimeout(() => {
90
+ setSyncingWallet(null);
91
+ }, 500);
92
+ }
93
+ catch (error) {
94
+ console.error("Error syncing wallet:", error);
95
+ setSyncingWallet(null);
96
+ }
97
+ };
98
+ const handleRemoveWallet = (walletAddress) => {
99
+ dispatch(actions.removeWallet({
100
+ wallet: walletAddress,
101
+ }));
102
+ };
103
+ // Calculate totals for a wallet
104
+ const calculateWalletTotals = (wallet) => {
105
+ const lineItems = wallet.lineItems || [];
106
+ return {
107
+ budget: lineItems.reduce((sum, item) => sum + (item?.budget || 0), 0),
108
+ forecast: lineItems.reduce((sum, item) => sum + (item?.forecast || 0), 0),
109
+ actuals: lineItems.reduce((sum, item) => sum + (item?.actuals || 0), 0),
110
+ difference: lineItems.reduce((sum, item) => {
111
+ const budget = item?.budget || 0;
112
+ const actuals = item?.actuals || 0;
113
+ return sum + (actuals - budget);
114
+ }, 0),
115
+ payments: lineItems.reduce((sum, item) => sum + (item?.payments || 0), 0),
116
+ };
117
+ };
118
+ const formatCurrency = (value) => {
119
+ return new Intl.NumberFormat("en-US", {
120
+ style: "currency",
121
+ currency: "USD",
122
+ minimumFractionDigits: 2,
123
+ }).format(value);
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: "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
+ const totals = calculateWalletTotals(wallet);
127
+ const isHovered = hoveredWallet === wallet.wallet;
128
+ 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) => {
129
+ if (e.key === "Enter") {
130
+ handleSaveEditName(wallet.wallet || "");
131
+ }
132
+ else if (e.key === "Escape") {
133
+ handleCancelEditName();
134
+ }
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 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
136
+ ? "text-red-600 dark:text-red-400"
137
+ : totals.difference < 0
138
+ ? "text-green-600 dark:text-green-400"
139
+ : "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: _jsxs("div", { className: "flex items-center justify-end gap-2", 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", children: [_jsx(Plus, { size: 16 }), _jsx("span", { children: "Add Bills" })] }), wallet.billingStatements && wallet.billingStatements.length > 0 && (_jsxs("button", { onClick: () => handleSyncWallet(wallet), disabled: syncingWallet === wallet.wallet, className: `inline-flex items-center gap-1 px-3 py-1 text-sm font-medium rounded-md transition-colors ${tagChangedWallets.includes(wallet.wallet || "")
140
+ ? "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
+ : outdatedWallets.includes(wallet.wallet || "")
142
+ ? "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
+ : "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 in billing statements - sync required!"
145
+ : outdatedWallets.includes(wallet.wallet || "")
146
+ ? "Sync needed - billing statements have been updated"
147
+ : "Sync with latest billing statements", children: [_jsx(RefreshCw, { size: 16, className: syncingWallet === wallet.wallet ? "animate-spin" : "" }), _jsx("span", { children: tagChangedWallets.includes(wallet.wallet || "")
148
+ ? "Tags Changed!"
149
+ : outdatedWallets.includes(wallet.wallet || "")
150
+ ? "Sync"
151
+ : "Synced" })] })), _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
+ }) })] }) })) : (_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
+ if (e.key === "Enter") {
154
+ handleAddWallet();
155
+ }
156
+ } })] }), _jsxs("div", { className: "flex-1 relative", children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: "Wallet Address" }), _jsx(TextInput, { value: newWalletAddress, onChange: (e) => {
157
+ setNewWalletAddress(e.target.value);
158
+ setWalletError(""); // Clear error when typing
159
+ }, placeholder: "Enter wallet address (e.g., 0x1234...)", onKeyDown: (e) => {
160
+ if (e.key === "Enter") {
161
+ handleAddWallet();
162
+ }
163
+ } }), walletError && (_jsx("div", { className: "absolute left-0 right-0 top-full mt-1 px-3 py-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md shadow-lg z-10", children: _jsx("p", { className: "text-sm text-red-600 dark:text-red-400", children: walletError }) }))] }), _jsx(Button, { onClick: handleAddWallet, disabled: !newWalletAddress.trim(), children: "Add Wallet" })] })] }));
164
+ }
@@ -0,0 +1,2 @@
1
+ export declare function Editor(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/expense-report/editor.tsx"],"names":[],"mappings":"AAUA,wBAAgB,MAAM,4CAiLrB"}
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useMemo, useEffect } from "react";
3
+ import { useSelectedExpenseReportDocument } from "../hooks/useExpenseReportDocument.js";
4
+ import { actions } from "../../document-models/expense-report/index.js";
5
+ import { DatePicker } from "@powerhousedao/document-engineering";
6
+ import { WalletsTable } from "./components/WalletsTable.js";
7
+ import { AggregatedExpensesTable } from "./components/AggregatedExpensesTable.js";
8
+ import { AddBillingStatementModal } from "./components/AddBillingStatementModal.js";
9
+ import { useWalletSync } from "./hooks/useWalletSync.js";
10
+ import { useSyncWallet } from "./hooks/useSyncWallet.js";
11
+ export function Editor() {
12
+ const [document, dispatch] = useSelectedExpenseReportDocument();
13
+ const [selectedWallet, setSelectedWallet] = useState(null);
14
+ const [isModalOpen, setIsModalOpen] = useState(false);
15
+ const [periodStart, setPeriodStart] = useState(document.state.global.periodStart || "");
16
+ const [periodEnd, setPeriodEnd] = useState(document.state.global.periodEnd || "");
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
+ // Handle period date changes
54
+ const handlePeriodStartChange = (e) => {
55
+ const value = e.target.value;
56
+ setPeriodStart(value);
57
+ if (value) {
58
+ dispatch(actions.setPeriodStart({ periodStart: value }));
59
+ }
60
+ };
61
+ const handlePeriodEndChange = (e) => {
62
+ const value = e.target.value;
63
+ setPeriodEnd(value);
64
+ if (value) {
65
+ dispatch(actions.setPeriodEnd({ periodEnd: value }));
66
+ }
67
+ };
68
+ // Handle wallet selection for adding billing statements
69
+ const handleAddBillingStatement = (walletAddress) => {
70
+ setSelectedWallet(walletAddress);
71
+ setIsModalOpen(true);
72
+ };
73
+ // Handle closing modal
74
+ const handleCloseModal = () => {
75
+ setIsModalOpen(false);
76
+ setSelectedWallet(null);
77
+ };
78
+ // Format period title for the breakdown section
79
+ const breakdownTitle = useMemo(() => {
80
+ if (!periodStart)
81
+ return "Breakdown";
82
+ const date = new Date(periodStart);
83
+ const month = date.toLocaleDateString("en-US", { month: "short" });
84
+ const year = date.getFullYear();
85
+ return `${month} ${year} Breakdown`;
86
+ }, [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 border-b border-gray-200 dark:border-gray-700 px-8 py-6", children: _jsx("div", { className: "max-w-7xl mx-auto", children: _jsxs("div", { className: "text-center mb-6", children: [_jsx("h1", { className: "text-3xl font-bold text-gray-900 dark:text-white mb-2", 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 })] }) })] }) }) }), _jsx("div", { className: "flex-1 overflow-auto px-8 py-6", children: _jsxs("div", { className: "max-w-7xl mx-auto space-y-8", children: [_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
+ }
@@ -0,0 +1,5 @@
1
+ import type { LineItemGroup } from "../../../document-models/expense-report/gen/types.js";
2
+ export declare function useSyncWallet(): {
3
+ syncWallet: (walletAddress: string, billingStatementIds: string[], groups: LineItemGroup[], dispatch: any) => void;
4
+ };
5
+ //# sourceMappingURL=useSyncWallet.d.ts.map
@@ -0,0 +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;EA8DhB"}
@@ -0,0 +1,55 @@
1
+ import { useSelectedDriveDocuments } from "@powerhousedao/reactor-browser";
2
+ import { actions } from "../../../document-models/expense-report/index.js";
3
+ import { generateId } from "document-model";
4
+ export function useSyncWallet() {
5
+ const documents = useSelectedDriveDocuments();
6
+ const syncWallet = (walletAddress, billingStatementIds, groups, dispatch) => {
7
+ if (!documents)
8
+ return;
9
+ // Get billing statement documents
10
+ const billingStatements = new Map();
11
+ documents
12
+ .filter((doc) => doc.header.documentType === "powerhouse/billing-statement")
13
+ .forEach((doc) => {
14
+ billingStatements.set(doc.header.id, doc);
15
+ });
16
+ // Helper function to map tag to group
17
+ const mapTagToGroup = (billingLineItem) => {
18
+ // Find expense-account tag
19
+ const expenseAccountTag = billingLineItem.lineItemTag?.find((tag) => tag.dimension === "expense-account");
20
+ if (!expenseAccountTag || !expenseAccountTag.label)
21
+ return null;
22
+ // Find matching group by label
23
+ const group = groups.find((g) => g.label === expenseAccountTag.label);
24
+ return group ? group.id : null;
25
+ };
26
+ // Clear existing line items for this wallet first
27
+ // Note: We'll need to add a new action for this, or remove items one by one
28
+ // For now, let's re-add all line items from billing statements
29
+ // Extract and add line items from all billing statements
30
+ billingStatementIds.forEach((statementId) => {
31
+ const statement = billingStatements.get(statementId);
32
+ if (!statement?.state?.global?.lineItems)
33
+ return;
34
+ const lineItems = statement.state.global.lineItems || [];
35
+ lineItems.forEach((billingLineItem) => {
36
+ const groupId = mapTagToGroup(billingLineItem);
37
+ const expenseLineItem = {
38
+ id: generateId(),
39
+ label: billingLineItem.description,
40
+ group: groupId,
41
+ budget: 0,
42
+ actuals: billingLineItem.totalPriceCash || 0,
43
+ forecast: 0,
44
+ payments: 0,
45
+ comments: null,
46
+ };
47
+ dispatch(actions.addLineItem({
48
+ wallet: walletAddress,
49
+ lineItem: expenseLineItem,
50
+ }));
51
+ });
52
+ });
53
+ };
54
+ return { syncWallet };
55
+ }
@@ -0,0 +1,9 @@
1
+ import type { Wallet } from "../../../document-models/expense-report/gen/types.js";
2
+ interface SyncStatus {
3
+ needsSync: boolean;
4
+ outdatedWallets: string[];
5
+ tagChangedWallets: string[];
6
+ }
7
+ export declare function useWalletSync(wallets: Wallet[]): SyncStatus;
8
+ export {};
9
+ //# sourceMappingURL=useWalletSync.d.ts.map
@@ -0,0 +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,CA2F3D"}