@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.
@@ -24,7 +24,7 @@ export function LineItemTagsTable({ lineItems, onClose, dispatch, }) {
24
24
  id: item.id,
25
25
  description: e.target.value,
26
26
  }));
27
- }, className: "w-full text-xs" }) }), _jsx("td", { className: "border-b border-gray-200 p-2 w-50", children: _jsx(DatePicker, { name: "period", dateFormat: "YYYY-MM", autoClose: true, placeholder: "Select Period", value: item.lineItemTag.find((tag) => tag.dimension === "accounting-period")?.label || "", onChange: (e) => dispatch(actions.editLineItemTag({
27
+ }, className: "w-full text-xs" }) }), _jsx("td", { className: "border-b border-gray-200 p-2 w-50", children: _jsx(DatePicker, { name: "period", dateFormat: "YYYY-MM-DD", autoClose: true, placeholder: "Select Period", value: item.lineItemTag.find((tag) => tag.dimension === "accounting-period")?.label || "", onChange: (e) => dispatch(actions.editLineItemTag({
28
28
  lineItemId: item.id,
29
29
  dimension: "accounting-period",
30
30
  value: new Date(e.target.value)
@@ -1 +1 @@
1
- {"version":3,"file":"AddBillingStatementModal.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/components/AddBillingStatementModal.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAO1F,UAAU,6BAA6B;IACrC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC;IACd,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AA+BD,wBAAgB,wBAAwB,CAAC,EACvC,MAAM,EACN,OAAO,EACP,aAAa,EACb,QAAQ,EACR,MAAM,GACP,EAAE,6BAA6B,kDAqS/B"}
1
+ {"version":3,"file":"AddBillingStatementModal.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/components/AddBillingStatementModal.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sDAAsD,CAAC;AAO1F,UAAU,6BAA6B;IACrC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC;IACd,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AA+BD,wBAAgB,wBAAwB,CAAC,EACvC,MAAM,EACN,OAAO,EACP,aAAa,EACb,QAAQ,EACR,MAAM,GACP,EAAE,6BAA6B,kDAuU/B"}
@@ -112,39 +112,64 @@ export function AddBillingStatementModal({ isOpen, onClose, walletAddress, dispa
112
112
  const handleAddStatements = () => {
113
113
  if (selectedStatements.size === 0)
114
114
  return;
115
+ // First, add all billing statement references
115
116
  selectedStatements.forEach((statementId) => {
116
- const statement = billingStatements.find((s) => s.id === statementId);
117
- if (!statement || !statement.document)
118
- return;
119
- console.log("Statement document:", statement.document);
120
- // Add billing statement reference to wallet
121
117
  dispatch(actions.addBillingStatement({
122
118
  wallet: walletAddress,
123
119
  billingStatementId: statementId,
124
120
  }));
125
- // Extract line items from billing statement and add to wallet
121
+ });
122
+ // Aggregate line items by category across all selected billing statements
123
+ const categoryAggregation = new Map();
124
+ selectedStatements.forEach((statementId) => {
125
+ const statement = billingStatements.find((s) => s.id === statementId);
126
+ if (!statement || !statement.document)
127
+ return;
128
+ console.log("Statement document:", statement.document);
129
+ // Extract line items from billing statement
126
130
  const billingState = statement.document;
127
131
  const lineItems = billingState.state?.global?.lineItems || [];
128
132
  console.log("Line items found:", lineItems.length, lineItems);
133
+ // Aggregate line items by category
129
134
  lineItems.forEach((billingLineItem) => {
130
135
  const groupId = mapTagToGroup(billingLineItem);
131
- // Create expense report line item from billing statement line item
132
- const expenseLineItem = {
133
- id: generateId(),
134
- label: billingLineItem.description,
135
- group: groupId,
136
- budget: 0, // Could be mapped if available
137
- actuals: billingLineItem.totalPriceCash || 0,
138
- forecast: 0, // Could be mapped if available
139
- payments: 0, // Could be set based on status
140
- comments: null,
141
- };
142
- dispatch(actions.addLineItem({
143
- wallet: walletAddress,
144
- lineItem: expenseLineItem,
145
- }));
136
+ const categoryKey = groupId || "uncategorized";
137
+ const existing = categoryAggregation.get(categoryKey);
138
+ if (existing) {
139
+ // Aggregate values for the same category
140
+ existing.actuals += billingLineItem.totalPriceCash || 0;
141
+ }
142
+ else {
143
+ // Create new category entry
144
+ const group = groups.find((g) => g.id === groupId);
145
+ categoryAggregation.set(categoryKey, {
146
+ groupId: groupId,
147
+ groupLabel: group?.label || "Uncategorised",
148
+ budget: 0,
149
+ actuals: billingLineItem.totalPriceCash || 0,
150
+ forecast: 0,
151
+ payments: 0,
152
+ });
153
+ }
146
154
  });
147
155
  });
156
+ // Now add aggregated line items to wallet
157
+ categoryAggregation.forEach((aggregatedItem) => {
158
+ const expenseLineItem = {
159
+ id: generateId(),
160
+ label: aggregatedItem.groupLabel,
161
+ group: aggregatedItem.groupId,
162
+ budget: aggregatedItem.budget,
163
+ actuals: aggregatedItem.actuals,
164
+ forecast: aggregatedItem.forecast,
165
+ payments: aggregatedItem.payments,
166
+ comments: null,
167
+ };
168
+ dispatch(actions.addLineItem({
169
+ wallet: walletAddress,
170
+ lineItem: expenseLineItem,
171
+ }));
172
+ });
148
173
  onClose();
149
174
  };
150
175
  if (!isOpen)
@@ -166,5 +191,5 @@ export function AddBillingStatementModal({ isOpen, onClose, walletAddress, dispa
166
191
  : "border-gray-300 dark:border-gray-600"}`, children: (isSelected || isAlreadyAdded) && (_jsx(Check, { className: "text-white", size: 14 })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-medium text-gray-900 dark:text-white truncate", children: statement.name }), _jsxs("span", { className: "text-xs text-gray-500 dark:text-gray-400 px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded", children: [lineItemCount, " items"] }), isAlreadyAdded && (_jsx("span", { className: "text-xs text-amber-600 dark:text-amber-400 px-2 py-1 bg-amber-50 dark:bg-amber-900/20 rounded font-medium", children: "Already included" }))] }), _jsxs("div", { className: "flex items-center gap-4 mt-1 text-sm text-gray-600 dark:text-gray-400", children: [_jsxs("span", { children: ["Total: ", formatCurrency(totalCash)] }), _jsxs("span", { className: "text-xs font-mono", children: [statement.id.substring(0, 8), "..."] })] })] })] }, statement.id));
167
192
  }) })) : (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-gray-500 dark:text-gray-400", children: [_jsx(FileText, { size: 48, className: "mb-4 opacity-50" }), _jsx("p", { className: "text-sm", children: searchTerm
168
193
  ? "No billing statements found matching your search"
169
- : "No billing statements available in this drive" })] })) }), _jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50", children: [_jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: [selectedStatements.size, " statement", selectedStatements.size !== 1 ? "s" : "", " selected"] }), _jsxs("div", { className: "flex gap-3", children: [_jsx(Button, { onClick: onClose, className: "px-4 py-2 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", children: "Cancel" }), _jsxs(Button, { onClick: handleAddStatements, disabled: selectedStatements.size === 0, className: "px-4 py-2", children: ["Add", " ", selectedStatements.size > 0 && `(${selectedStatements.size})`] })] })] })] })] }));
194
+ : "No billing statements available in this drive" })] })) }), _jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50", children: [_jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: [selectedStatements.size, " statement", selectedStatements.size !== 1 ? "s" : "", " selected"] }), _jsxs("div", { className: "flex gap-3", children: [_jsx(Button, { onClick: onClose, variant: "secondary", children: "CANCEL" }), _jsxs(Button, { onClick: handleAddStatements, disabled: selectedStatements.size === 0, className: "bg-green-600 hover:bg-green-700 text-white", children: ["ADD", " ", selectedStatements.size > 0 && `(${selectedStatements.size})`] })] })] })] })] }));
170
195
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AggregatedExpensesTable.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/components/AggregatedExpensesTable.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAY,MAAM,sDAAsD,CAAC;AAI5G,UAAU,4BAA4B;IACpC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CACjC;AAQD,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACP,MAAM,EACN,WAAW,EACX,SAAS,EACT,QAAQ,GACT,EAAE,4BAA4B,kDAye9B"}
1
+ {"version":3,"file":"AggregatedExpensesTable.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/components/AggregatedExpensesTable.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAY,MAAM,sDAAsD,CAAC;AAM5G,UAAU,4BAA4B;IACpC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CACjC;AAQD,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACP,MAAM,EACN,WAAW,EACX,SAAS,EACT,QAAQ,GACT,EAAE,4BAA4B,kDAmzB9B"}
@@ -1,14 +1,136 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo, useState } from "react";
2
+ import { useMemo, useState, useRef, useEffect } from "react";
3
3
  import React from "react";
4
4
  import { actions } from "../../../document-models/expense-report/index.js";
5
- import { Textarea } from "@powerhousedao/document-engineering";
5
+ import { Textarea, Select, Button } from "@powerhousedao/document-engineering";
6
+ import { Plus, Trash2 } from "lucide-react";
7
+ import { generateId } from "document-model";
6
8
  export function AggregatedExpensesTable({ wallets, groups, periodStart, periodEnd, dispatch, }) {
7
9
  // State for active tab (selected wallet)
8
10
  const [activeWalletIndex, setActiveWalletIndex] = useState(0);
9
11
  // State for editing comments
10
12
  const [editingGroupId, setEditingGroupId] = useState(null);
11
13
  const [editingComment, setEditingComment] = useState("");
14
+ const [originalComment, setOriginalComment] = useState("");
15
+ // State for editing numeric fields
16
+ const [editingField, setEditingField] = useState(null);
17
+ const [editingValue, setEditingValue] = useState("");
18
+ // State for adding new line item
19
+ const [isAddingLineItem, setIsAddingLineItem] = useState(false);
20
+ const [selectedGroupId, setSelectedGroupId] = useState("");
21
+ const [duplicateCategoryError, setDuplicateCategoryError] = useState("");
22
+ // State for delete confirmation modal
23
+ const [deleteModalOpen, setDeleteModalOpen] = useState(false);
24
+ const [lineItemToDelete, setLineItemToDelete] = useState(null);
25
+ // Ref for line item editor to scroll into view
26
+ const lineItemEditorRef = useRef(null);
27
+ // Scroll to bottom of page when editor opens
28
+ useEffect(() => {
29
+ if (isAddingLineItem && lineItemEditorRef.current) {
30
+ // Use a slight delay to ensure the editor is rendered
31
+ setTimeout(() => {
32
+ // Find the scrollable container by traversing up from the ref
33
+ let element = lineItemEditorRef.current?.parentElement;
34
+ while (element) {
35
+ const style = window.getComputedStyle(element);
36
+ const isScrollable = style.overflow === 'auto' || style.overflow === 'scroll' ||
37
+ style.overflowY === 'auto' || style.overflowY === 'scroll';
38
+ if (isScrollable && element.scrollHeight > element.clientHeight) {
39
+ // Found the scrollable container, scroll to bottom
40
+ element.scrollTo({ top: element.scrollHeight, behavior: "smooth" });
41
+ break;
42
+ }
43
+ element = element.parentElement;
44
+ }
45
+ }, 150);
46
+ }
47
+ }, [isAddingLineItem]);
48
+ // Get existing category IDs for the active wallet
49
+ const existingCategoryIds = useMemo(() => {
50
+ const wallet = wallets[activeWalletIndex];
51
+ if (!wallet || !wallet.lineItems)
52
+ return new Set();
53
+ return new Set(wallet.lineItems
54
+ .filter((item) => item !== null && item !== undefined)
55
+ .map(item => item.group)
56
+ .filter((group) => !!group));
57
+ }, [wallets, activeWalletIndex]);
58
+ // Create group options for Select component
59
+ const groupOptions = useMemo(() => {
60
+ return groups.map((group) => ({
61
+ value: group.id,
62
+ label: group.label || group.id,
63
+ }));
64
+ }, [groups]);
65
+ // Handle category selection with duplicate check
66
+ const handleCategoryChange = (value) => {
67
+ setSelectedGroupId(value);
68
+ // Check if category already exists
69
+ if (existingCategoryIds.has(value)) {
70
+ const categoryLabel = groups.find(g => g.id === value)?.label || value;
71
+ setDuplicateCategoryError(`"${categoryLabel}" already exists in this wallet. Please select a different category.`);
72
+ }
73
+ else {
74
+ setDuplicateCategoryError("");
75
+ }
76
+ };
77
+ // Handle saving new line item
78
+ const handleSaveLineItem = () => {
79
+ const wallet = wallets[activeWalletIndex];
80
+ if (!wallet || !wallet.wallet || !selectedGroupId)
81
+ return;
82
+ // Prevent saving if duplicate
83
+ if (existingCategoryIds.has(selectedGroupId)) {
84
+ return;
85
+ }
86
+ const newLineItem = {
87
+ id: generateId(),
88
+ label: groups.find(g => g.id === selectedGroupId)?.label || "",
89
+ group: selectedGroupId,
90
+ budget: 0,
91
+ actuals: 0,
92
+ forecast: 0,
93
+ payments: 0,
94
+ comments: "",
95
+ };
96
+ dispatch(actions.addLineItem({
97
+ wallet: wallet.wallet,
98
+ lineItem: newLineItem,
99
+ }));
100
+ // Reset state
101
+ setIsAddingLineItem(false);
102
+ setSelectedGroupId("");
103
+ setDuplicateCategoryError("");
104
+ };
105
+ // Handle canceling new line item
106
+ const handleCancelLineItem = () => {
107
+ setIsAddingLineItem(false);
108
+ setSelectedGroupId("");
109
+ setDuplicateCategoryError("");
110
+ };
111
+ // Handle opening delete confirmation modal
112
+ const handleDeleteLineItem = (lineItemId, lineItemLabel) => {
113
+ setLineItemToDelete({ id: lineItemId, label: lineItemLabel });
114
+ setDeleteModalOpen(true);
115
+ };
116
+ // Handle confirming deletion
117
+ const handleConfirmDelete = () => {
118
+ const wallet = wallets[activeWalletIndex];
119
+ if (!wallet || !wallet.wallet || !lineItemToDelete)
120
+ return;
121
+ dispatch(actions.removeLineItem({
122
+ wallet: wallet.wallet,
123
+ lineItemId: lineItemToDelete.id,
124
+ }));
125
+ // Close modal and reset state
126
+ setDeleteModalOpen(false);
127
+ setLineItemToDelete(null);
128
+ };
129
+ // Handle canceling deletion
130
+ const handleCancelDelete = () => {
131
+ setDeleteModalOpen(false);
132
+ setLineItemToDelete(null);
133
+ };
12
134
  // Format period for title
13
135
  const periodTitle = useMemo(() => {
14
136
  if (!periodStart)
@@ -37,6 +159,7 @@ export function AggregatedExpensesTable({ wallets, groups, periodStart, periodEn
37
159
  return map;
38
160
  }, [groups]);
39
161
  // Get line items for the active wallet with group information
162
+ // Line items are now already aggregated by category
40
163
  const walletLineItems = useMemo(() => {
41
164
  if (!wallets[activeWalletIndex])
42
165
  return [];
@@ -48,50 +171,33 @@ export function AggregatedExpensesTable({ wallets, groups, periodStart, periodEn
48
171
  const groupInfo = item.group ? groupsMap.get(item.group) : undefined;
49
172
  return {
50
173
  ...item,
51
- groupLabel: groupInfo?.group.label || undefined,
174
+ groupLabel: groupInfo?.group.label || item.label || undefined,
52
175
  parentGroupId: groupInfo?.parent?.id || null,
53
176
  parentGroupLabel: groupInfo?.parent?.label || undefined,
54
177
  };
55
178
  });
56
179
  }, [wallets, activeWalletIndex, groupsMap]);
57
- // Group line items by category and aggregate by parent category
180
+ // Group line items by parent category
181
+ // Line items are already aggregated by category, so we just need to group them by parent
58
182
  const groupedAndAggregatedItems = useMemo(() => {
59
- // First, aggregate line items by their category (group)
60
- const categoryAggregation = new Map();
183
+ const grouped = new Map();
61
184
  walletLineItems.forEach((item) => {
62
185
  if (!item)
63
186
  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";
187
+ const parentKey = item.parentGroupId || "uncategorized";
93
188
  const items = grouped.get(parentKey) || [];
94
- items.push(aggItem);
189
+ items.push({
190
+ lineItemId: item.id || "",
191
+ groupId: item.group || "uncategorized",
192
+ groupLabel: item.groupLabel || "Uncategorised",
193
+ parentGroupId: item.parentGroupId,
194
+ parentGroupLabel: item.parentGroupLabel,
195
+ budget: item.budget || 0,
196
+ forecast: item.forecast || 0,
197
+ actuals: item.actuals || 0,
198
+ payments: item.payments || 0,
199
+ comment: item.comments || "",
200
+ });
95
201
  grouped.set(parentKey, items);
96
202
  });
97
203
  return grouped;
@@ -102,7 +208,7 @@ export function AggregatedExpensesTable({ wallets, groups, periodStart, periodEn
102
208
  budget: acc.budget + item.budget,
103
209
  forecast: acc.forecast + item.forecast,
104
210
  actuals: acc.actuals + item.actuals,
105
- difference: acc.difference + (item.actuals - item.budget),
211
+ difference: acc.difference + (item.forecast - item.actuals),
106
212
  payments: acc.payments + item.payments,
107
213
  }), { budget: 0, forecast: 0, actuals: 0, difference: 0, payments: 0 });
108
214
  };
@@ -112,7 +218,7 @@ export function AggregatedExpensesTable({ wallets, groups, periodStart, periodEn
112
218
  budget: acc.budget + (item?.budget || 0),
113
219
  forecast: acc.forecast + (item?.forecast || 0),
114
220
  actuals: acc.actuals + (item?.actuals || 0),
115
- difference: acc.difference + ((item?.actuals || 0) - (item?.budget || 0)),
221
+ difference: acc.difference + ((item?.forecast || 0) - (item?.actuals || 0)),
116
222
  payments: acc.payments + (item?.payments || 0),
117
223
  }), { budget: 0, forecast: 0, actuals: 0, difference: 0, payments: 0 });
118
224
  }, [walletLineItems]);
@@ -128,37 +234,67 @@ export function AggregatedExpensesTable({ wallets, groups, periodStart, periodEn
128
234
  return `${address.substring(0, 6)}...${address.substring(address.length - 6)}`;
129
235
  };
130
236
  // Handle starting comment edit
131
- const handleStartEdit = (groupId, currentComment) => {
132
- setEditingGroupId(groupId);
237
+ const handleStartEdit = (lineItemId, currentComment) => {
238
+ setEditingGroupId(lineItemId);
133
239
  setEditingComment(currentComment);
240
+ setOriginalComment(currentComment);
134
241
  };
135
- // Handle saving comment
136
- const handleSaveComment = (groupId) => {
242
+ // Handle saving comment for a single line item
243
+ const handleSaveComment = () => {
137
244
  const wallet = wallets[activeWalletIndex];
138
- if (!wallet || !wallet.wallet)
245
+ if (!wallet || !wallet.wallet || !editingGroupId)
139
246
  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);
247
+ // Only dispatch if the comment has actually changed
248
+ if (editingComment !== originalComment) {
249
+ dispatch(actions.updateLineItem({
250
+ wallet: wallet.wallet,
251
+ lineItemId: editingGroupId,
252
+ comments: editingComment,
253
+ }));
153
254
  }
154
255
  // Reset editing state
155
256
  setEditingGroupId(null);
156
257
  setEditingComment("");
258
+ setOriginalComment("");
157
259
  };
158
260
  // Handle canceling comment edit
159
261
  const handleCancelEdit = () => {
160
262
  setEditingGroupId(null);
161
263
  setEditingComment("");
264
+ setOriginalComment("");
265
+ };
266
+ // Handle starting numeric field edit
267
+ const handleStartFieldEdit = (lineItemId, field, currentValue) => {
268
+ setEditingField({ lineItemId, field, originalValue: currentValue });
269
+ setEditingValue(currentValue.toString());
270
+ };
271
+ // Handle saving numeric field
272
+ const handleSaveField = () => {
273
+ const wallet = wallets[activeWalletIndex];
274
+ if (!wallet || !wallet.wallet || !editingField)
275
+ return;
276
+ const numericValue = parseFloat(editingValue);
277
+ if (isNaN(numericValue)) {
278
+ // Invalid number, cancel edit
279
+ handleCancelFieldEdit();
280
+ return;
281
+ }
282
+ // Only dispatch if the value has actually changed
283
+ if (numericValue !== editingField.originalValue) {
284
+ dispatch(actions.updateLineItem({
285
+ wallet: wallet.wallet,
286
+ lineItemId: editingField.lineItemId,
287
+ [editingField.field]: numericValue,
288
+ }));
289
+ }
290
+ // Reset editing state
291
+ setEditingField(null);
292
+ setEditingValue("");
293
+ };
294
+ // Handle canceling numeric field edit
295
+ const handleCancelFieldEdit = () => {
296
+ setEditingField(null);
297
+ setEditingValue("");
162
298
  };
163
299
  // Sort parent groups: Headcount first, then Non-Headcount, then others, then uncategorized
164
300
  const sortedParentKeys = useMemo(() => {
@@ -197,7 +333,7 @@ export function AggregatedExpensesTable({ wallets, groups, periodStart, periodEn
197
333
  ? "border-green-500 text-green-600 dark:text-green-400"
198
334
  : "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
335
  `, 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) => {
336
+ }) }) }), _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: "pl-6 pr-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { onClick: () => setIsAddingLineItem(true), className: "inline-flex items-center justify-center w-6 h-6 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", children: _jsx(Plus, { size: 14 }) }), _jsx("span", { children: "Expense Category" })] }) }), _jsx("th", { className: "px-3 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-3 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Forecast" }), _jsx("th", { className: "px-3 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Actuals" }), _jsx("th", { className: "px-3 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider", children: "Difference" }), _jsx("th", { className: "px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-96", children: "Comments" }), _jsx("th", { className: "px-3 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-32", children: "Payments" }), _jsx("th", { className: "px-2 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-16", children: "Actions" })] }) }), _jsxs("tbody", { className: "divide-y divide-gray-200 dark:divide-gray-700", children: [sortedParentKeys.map((parentKey) => {
201
337
  const items = groupedAndAggregatedItems.get(parentKey) || [];
202
338
  if (items.length === 0)
203
339
  return null;
@@ -205,28 +341,46 @@ export function AggregatedExpensesTable({ wallets, groups, periodStart, periodEn
205
341
  const parentLabel = parentKey === "uncategorized"
206
342
  ? "Uncategorised"
207
343
  : 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) => {
344
+ return (_jsxs(React.Fragment, { children: [_jsx("tr", { className: "bg-gray-100 dark:bg-gray-800", children: _jsx("td", { colSpan: 8, className: "px-6 py-3 text-sm font-bold text-gray-900 dark:text-white", children: parentLabel }) }), items.map((item) => {
209
345
  if (!item)
210
346
  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
347
+ const difference = item.forecast - item.actuals;
348
+ const isEditingComment = editingGroupId === item.lineItemId;
349
+ // Helper function to render editable numeric cell
350
+ const renderEditableCell = (field, value) => {
351
+ const isEditingThis = editingField?.lineItemId === item.lineItemId &&
352
+ editingField?.field === field;
353
+ if (isEditingThis) {
354
+ return (_jsx("div", { className: "flex items-center gap-1", children: _jsx("input", { type: "number", step: "0.01", value: editingValue, onChange: (e) => setEditingValue(e.target.value), onKeyDown: (e) => {
355
+ if (e.key === "Enter") {
356
+ handleSaveField();
357
+ }
358
+ else if (e.key === "Escape") {
359
+ handleCancelFieldEdit();
360
+ }
361
+ }, onBlur: handleSaveField, autoFocus: true, className: "w-full px-2 py-1 text-right text-sm border border-blue-500 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-white" }) }));
362
+ }
363
+ return (_jsx("div", { className: "group cursor-pointer text-right", onClick: () => handleStartFieldEdit(item.lineItemId, field, value), children: _jsx("span", { className: "group-hover:bg-blue-50 dark:group-hover:bg-blue-900/20 inline-block px-1 py-0.5 rounded transition-colors min-w-[4rem]", children: formatNumber(value) }) }));
364
+ };
365
+ return (_jsxs("tr", { className: "bg-white dark:bg-gray-900 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors align-top", children: [_jsx("td", { className: "pl-6 pr-3 py-3 whitespace-nowrap text-sm text-gray-900 dark:text-white", children: item.groupLabel }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: renderEditableCell("budget", item.budget) }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: renderEditableCell("forecast", item.forecast) }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: renderEditableCell("actuals", item.actuals) }), _jsx("td", { className: `px-3 py-3 whitespace-nowrap text-right text-sm font-medium ${difference < 0
214
366
  ? "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
367
+ : "text-gray-900 dark:text-white"}`, children: formatNumber(difference) }), _jsx("td", { className: "px-3 py-3 text-sm w-96", children: isEditingComment ? (_jsx(Textarea, { value: editingComment, onChange: (e) => setEditingComment(e.target.value), placeholder: "Add comment...", autoExpand: true, multiline: true, onKeyDown: (e) => {
368
+ if (e.key === "Enter" && !e.shiftKey) {
369
+ e.preventDefault();
370
+ handleSaveComment();
371
+ }
372
+ else if (e.key === "Escape") {
373
+ handleCancelEdit();
374
+ }
375
+ else if (e.key === "Tab") {
376
+ e.preventDefault();
377
+ handleSaveComment();
378
+ }
379
+ }, onBlur: handleSaveComment, autoFocus: true, className: "w-full px-2 py-1 text-sm border border-blue-500 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-white max-h-32 overflow-y-auto" })) : (_jsx("div", { className: "group cursor-pointer w-full max-h-20 overflow-hidden", onClick: () => handleStartEdit(item.lineItemId, item.comment), title: item.comment || "No comments", children: _jsx("span", { className: "group-hover:bg-blue-50 dark:group-hover:bg-blue-900/20 px-1 py-0.5 rounded transition-colors block text-gray-600 dark:text-gray-400 break-words", children: item.comment || "No comments" }) })) }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white w-32", children: renderEditableCell("payments", item.payments) }), _jsx("td", { className: "px-2 py-3 whitespace-nowrap text-center w-16", children: _jsx("button", { onClick: () => handleDeleteLineItem(item.lineItemId, item.groupLabel), className: "inline-flex items-center justify-center p-0.5 text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors", title: "Delete line item", children: _jsx(Trash2, { size: 14 }) }) })] }, item.lineItemId));
380
+ }), _jsxs("tr", { className: "bg-gray-50 dark:bg-gray-800/50 font-semibold align-top", children: [_jsx("td", { className: "pl-6 pr-3 py-3 whitespace-nowrap text-sm text-gray-900 dark:text-white", children: "Subtotal" }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(subtotals.budget) }) }) }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(subtotals.forecast) }) }) }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(subtotals.actuals) }) }) }), _jsx("td", { className: `px-3 py-3 whitespace-nowrap text-right text-sm font-bold ${subtotals.difference < 0
223
381
  ? "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
382
+ : "text-gray-900 dark:text-white"}`, children: formatNumber(subtotals.difference) }), _jsx("td", { className: "px-3 py-3" }), _jsx("td", { className: "px-3 py-3 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white w-32", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(subtotals.payments) }) }) }), _jsx("td", { className: "px-2 py-3" })] })] }, parentKey));
383
+ }), _jsxs("tr", { className: "bg-gray-100 dark:bg-gray-800 font-bold align-top", children: [_jsx("td", { className: "pl-6 pr-3 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white", children: "Total" }), _jsx("td", { className: "px-3 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(grandTotals.budget) }) }) }), _jsx("td", { className: "px-3 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(grandTotals.forecast) }) }) }), _jsx("td", { className: "px-3 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(grandTotals.actuals) }) }) }), _jsx("td", { className: `px-3 py-4 whitespace-nowrap text-right text-sm ${grandTotals.difference < 0
228
384
  ? "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) })] })] })] }) })] }));
385
+ : "text-gray-900 dark:text-white"}`, children: formatNumber(grandTotals.difference) }), _jsx("td", { className: "px-3 py-4" }), _jsx("td", { className: "px-3 py-4 whitespace-nowrap text-right text-sm text-gray-900 dark:text-white w-32", children: _jsx("div", { className: "text-right", children: _jsx("span", { className: "inline-block px-1 py-0.5 min-w-[4rem]", children: formatNumber(grandTotals.payments) }) }) }), _jsx("td", { className: "px-2 py-4" })] }), isAddingLineItem && (_jsx("tr", { ref: lineItemEditorRef, className: "bg-white dark:bg-gray-900 border-t border-gray-900 dark:border-gray-100", children: _jsx("td", { colSpan: 8, className: "px-6 py-4", children: _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-end gap-4", children: [_jsxs("div", { className: "flex-1", children: [_jsx("label", { className: "block text-xs font-medium text-gray-700 dark:text-gray-300 mb-2", children: "Select Category" }), _jsx(Select, { name: "category", searchable: true, value: selectedGroupId, onChange: (value) => handleCategoryChange(value), options: groupOptions, placeholder: "Choose a category...", className: "w-full" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: handleSaveLineItem, disabled: !selectedGroupId || !!duplicateCategoryError, className: "bg-green-600 hover:bg-green-700 text-white", children: "SAVE" }), _jsx(Button, { onClick: handleCancelLineItem, variant: "secondary", children: "CANCEL" })] })] }), duplicateCategoryError && (_jsx("div", { className: "flex items-start gap-2 p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-md", children: _jsx("span", { className: "text-sm text-amber-800 dark:text-amber-200", children: duplicateCategoryError }) }))] }) }) }))] })] }) }), deleteModalOpen && lineItemToDelete && (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [_jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: handleCancelDelete }), _jsxs("div", { className: "relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full mx-4 overflow-hidden", children: [_jsx("div", { className: "px-6 py-4 border-b border-gray-200 dark:border-gray-700", children: _jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: "Delete Line Item" }) }), _jsx("div", { className: "px-6 py-4", children: _jsxs("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: ["Are you sure you want to delete the line item", " ", _jsxs("span", { className: "font-semibold text-gray-900 dark:text-white", children: ["\"", lineItemToDelete.label, "\""] }), "? This action cannot be undone."] }) }), _jsxs("div", { className: "px-6 py-4 bg-gray-50 dark:bg-gray-800/50 border-t border-gray-200 dark:border-gray-700 flex justify-end gap-3", children: [_jsx(Button, { onClick: handleCancelDelete, variant: "secondary", children: "CANCEL" }), _jsx(Button, { onClick: handleConfirmDelete, className: "bg-red-600 hover:bg-red-700 text-white", children: "DELETE" })] })] })] }))] }));
232
386
  }
@@ -0,0 +1,10 @@
1
+ import type { Wallet, LineItemGroup } from "../../../document-models/expense-report/gen/types.js";
2
+ interface ExpenseReportPDFProps {
3
+ periodStart?: string | null;
4
+ periodEnd?: string | null;
5
+ wallets: Wallet[];
6
+ groups: LineItemGroup[];
7
+ }
8
+ export declare function ExpenseReportPDF({ periodStart, periodEnd, wallets, groups, }: ExpenseReportPDFProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=ExpenseReportPDF.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpenseReportPDF.d.ts","sourceRoot":"","sources":["../../../../editors/expense-report/components/ExpenseReportPDF.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,MAAM,EACN,aAAa,EAEd,MAAM,sDAAsD,CAAC;AAE9D,UAAU,qBAAqB;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAmKD,wBAAgB,gBAAgB,CAAC,EAC/B,WAAW,EACX,SAAS,EACT,OAAO,EACP,MAAM,GACP,EAAE,qBAAqB,2CAqVvB"}