@powerhousedao/contributor-billing 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/editors/billing-statement/lineItemTags/lineItemTags.js +1 -1
- package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts.map +1 -1
- package/dist/editors/expense-report/components/AddBillingStatementModal.js +47 -22
- package/dist/editors/expense-report/components/AggregatedExpensesTable.d.ts.map +1 -1
- package/dist/editors/expense-report/components/AggregatedExpensesTable.js +230 -76
- package/dist/editors/expense-report/components/ExpenseReportPDF.d.ts +10 -0
- package/dist/editors/expense-report/components/ExpenseReportPDF.d.ts.map +1 -0
- package/dist/editors/expense-report/components/ExpenseReportPDF.js +287 -0
- package/dist/editors/expense-report/components/WalletsTable.d.ts.map +1 -1
- package/dist/editors/expense-report/components/WalletsTable.js +25 -13
- package/dist/editors/expense-report/editor.d.ts.map +1 -1
- package/dist/editors/expense-report/editor.js +26 -40
- package/dist/editors/expense-report/hooks/useSyncWallet.d.ts.map +1 -1
- package/dist/editors/expense-report/hooks/useSyncWallet.js +38 -18
- package/dist/editors/expense-report/hooks/useWalletSync.d.ts.map +1 -1
- package/dist/editors/expense-report/hooks/useWalletSync.js +25 -27
- package/dist/editors/invoice/legalEntity/bankSection.d.ts.map +1 -1
- package/dist/editors/invoice/legalEntity/bankSection.js +18 -18
- package/dist/editors/invoice/lineItemTags/lineItemTags.js +1 -1
- package/dist/style.css +93 -21
- package/package.json +2 -1
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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,
|
|
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;
|
|
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
|
|
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
|
-
|
|
60
|
-
const categoryAggregation = new Map();
|
|
183
|
+
const grouped = new Map();
|
|
61
184
|
walletLineItems.forEach((item) => {
|
|
62
185
|
if (!item)
|
|
63
186
|
return;
|
|
64
|
-
const
|
|
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(
|
|
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.
|
|
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?.
|
|
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 = (
|
|
132
|
-
setEditingGroupId(
|
|
237
|
+
const handleStartEdit = (lineItemId, currentComment) => {
|
|
238
|
+
setEditingGroupId(lineItemId);
|
|
133
239
|
setEditingComment(currentComment);
|
|
240
|
+
setOriginalComment(currentComment);
|
|
134
241
|
};
|
|
135
|
-
// Handle saving comment
|
|
136
|
-
const handleSaveComment = (
|
|
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
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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: "
|
|
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:
|
|
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.
|
|
212
|
-
const
|
|
213
|
-
|
|
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
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
225
|
-
|
|
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
|
|
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"}
|