@powerhousedao/contributor-billing 0.0.65 → 0.0.66

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.
@@ -35,6 +35,7 @@ export const reducer = {
35
35
  ...sanitizedInput,
36
36
  };
37
37
  validatePrices(nextItem);
38
+ applyInvariants(state, nextItem);
38
39
  Object.assign(stateItem, nextItem);
39
40
  updateTotals(state);
40
41
  }
@@ -142,3 +143,53 @@ function validatePrices(item) {
142
143
  throw new Error("Tax inclusive/exclusive totals failed comparison.");
143
144
  }
144
145
  }
146
+ const applyInvariants = (state, nextItem) => {
147
+ const EPSILON = 0.00001; // Small value for floating point comparisons
148
+ // Helper function to compare floating point numbers
149
+ const isClose = (a, b) => Math.abs(a - b) < EPSILON;
150
+ // Helper function to check if a value has changed significantly
151
+ const hasChanged = (oldValue, newValue) => !isClose(oldValue, newValue);
152
+ // Find the current state of this line item
153
+ const currentItem = state.lineItems.find(item => item.id === nextItem.id);
154
+ if (!currentItem) {
155
+ // New item, no comparison needed
156
+ return;
157
+ }
158
+ const taxRate = nextItem.taxPercent / 100;
159
+ // Check if totalPriceTaxExcl was changed and update unitPriceTaxExcl accordingly
160
+ const expectedTotalPriceTaxExcl = nextItem.quantity * nextItem.unitPriceTaxExcl;
161
+ if (hasChanged(expectedTotalPriceTaxExcl, nextItem.totalPriceTaxExcl)) {
162
+ // Total was changed, update unit price
163
+ nextItem.unitPriceTaxExcl = nextItem.totalPriceTaxExcl / nextItem.quantity;
164
+ // Update tax-inclusive unit price to maintain tax relationship
165
+ nextItem.unitPriceTaxIncl = nextItem.unitPriceTaxExcl * (1 + taxRate);
166
+ // Update tax-inclusive total to maintain consistency
167
+ nextItem.totalPriceTaxIncl = nextItem.quantity * nextItem.unitPriceTaxIncl;
168
+ }
169
+ // Check if totalPriceTaxIncl was changed and update unitPriceTaxIncl accordingly
170
+ const expectedTotalPriceTaxIncl = nextItem.quantity * nextItem.unitPriceTaxIncl;
171
+ if (hasChanged(expectedTotalPriceTaxIncl, nextItem.totalPriceTaxIncl)) {
172
+ // Total was changed, update unit price
173
+ nextItem.unitPriceTaxIncl = nextItem.totalPriceTaxIncl / nextItem.quantity;
174
+ // Update tax-exclusive unit price to maintain tax relationship
175
+ nextItem.unitPriceTaxExcl = nextItem.unitPriceTaxIncl / (1 + taxRate);
176
+ // Update tax-exclusive total to maintain consistency
177
+ nextItem.totalPriceTaxExcl = nextItem.quantity * nextItem.unitPriceTaxExcl;
178
+ }
179
+ // Check if unitPriceTaxExcl was changed and update totals accordingly
180
+ const expectedUnitPriceTaxIncl = nextItem.unitPriceTaxExcl * (1 + taxRate);
181
+ if (hasChanged(expectedUnitPriceTaxIncl, nextItem.unitPriceTaxIncl)) {
182
+ // Unit price was changed, update tax-inclusive unit price and totals
183
+ nextItem.unitPriceTaxIncl = nextItem.unitPriceTaxExcl * (1 + taxRate);
184
+ nextItem.totalPriceTaxExcl = nextItem.quantity * nextItem.unitPriceTaxExcl;
185
+ nextItem.totalPriceTaxIncl = nextItem.quantity * nextItem.unitPriceTaxIncl;
186
+ }
187
+ // Check if unitPriceTaxIncl was changed and update totals accordingly
188
+ const expectedUnitPriceTaxExcl = nextItem.unitPriceTaxIncl / (1 + taxRate);
189
+ if (hasChanged(expectedUnitPriceTaxExcl, nextItem.unitPriceTaxExcl)) {
190
+ // Unit price was changed, update tax-exclusive unit price and totals
191
+ nextItem.unitPriceTaxExcl = nextItem.unitPriceTaxIncl / (1 + taxRate);
192
+ nextItem.totalPriceTaxExcl = nextItem.quantity * nextItem.unitPriceTaxExcl;
193
+ nextItem.totalPriceTaxIncl = nextItem.quantity * nextItem.unitPriceTaxIncl;
194
+ }
195
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/invoice/editor.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,wCAAwC,CAAC;AA6ChD,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;AAElD,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,KAAK,EAAE,MAAM,2CAu0B3C"}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/invoice/editor.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EACL,KAAK,eAAe,EAIrB,MAAM,wCAAwC,CAAC;AA6ChD,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;AAElD,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,KAAK,EAAE,MAAM,2CAu2B3C"}
@@ -62,6 +62,7 @@ export default function Editor(props) {
62
62
  const [txnRef, setTxnRef] = useState("");
63
63
  const [paymentIssue, setPaymentIssue] = useState("");
64
64
  const [paymentAmount, setPaymentAmount] = useState("");
65
+ const [editingItemValues, setEditingItemValues] = useState(null);
65
66
  const prevStatus = useRef(state.status);
66
67
  useEffect(() => {
67
68
  setFiatMode(isFiatCurrency(state.currency));
@@ -84,15 +85,35 @@ export default function Editor(props) {
84
85
  };
85
86
  }, []);
86
87
  const itemsTotalTaxExcl = useMemo(() => {
87
- return state.lineItems.reduce((total, lineItem) => {
88
- return total + lineItem.quantity * lineItem.unitPriceTaxExcl;
88
+ let total = state.lineItems.reduce((sum, lineItem) => {
89
+ return sum + lineItem.quantity * lineItem.unitPriceTaxExcl;
89
90
  }, 0.0);
90
- }, [state.lineItems]);
91
+ // If there's an item being edited, replace its contribution with the edited values
92
+ if (editingItemValues) {
93
+ const originalItem = state.lineItems.find(item => item.id === editingItemValues.id);
94
+ if (originalItem) {
95
+ // Subtract the original contribution and add the edited contribution
96
+ total = total - (originalItem.quantity * originalItem.unitPriceTaxExcl) +
97
+ (editingItemValues.quantity * editingItemValues.unitPriceTaxExcl);
98
+ }
99
+ }
100
+ return total;
101
+ }, [state.lineItems, editingItemValues]);
91
102
  const itemsTotalTaxIncl = useMemo(() => {
92
- return state.lineItems.reduce((total, lineItem) => {
93
- return total + lineItem.quantity * lineItem.unitPriceTaxIncl;
103
+ let total = state.lineItems.reduce((sum, lineItem) => {
104
+ return sum + lineItem.quantity * lineItem.unitPriceTaxIncl;
94
105
  }, 0.0);
95
- }, [state.lineItems]);
106
+ // If there's an item being edited, replace its contribution with the edited values
107
+ if (editingItemValues) {
108
+ const originalItem = state.lineItems.find(item => item.id === editingItemValues.id);
109
+ if (originalItem) {
110
+ // Subtract the original contribution and add the edited contribution
111
+ total = total - (originalItem.quantity * originalItem.unitPriceTaxIncl) +
112
+ (editingItemValues.quantity * editingItemValues.unitPriceTaxIncl);
113
+ }
114
+ }
115
+ return total;
116
+ }, [state.lineItems, editingItemValues]);
96
117
  const STATUS_OPTIONS = [
97
118
  "DRAFT",
98
119
  "ISSUED",
@@ -354,7 +375,7 @@ export default function Editor(props) {
354
375
  })), onAddItem: (item) => dispatch(actions.addLineItem(item)), onDeleteItem: (input) => dispatch(actions.deleteLineItem(input)), onUpdateCurrency: (input) => {
355
376
  setFiatMode(input.currency !== "USDS");
356
377
  dispatch(actions.editInvoice(input));
357
- }, onUpdateItem: (item) => dispatch(actions.editLineItem(item)), dispatch: dispatch, paymentAccounts: state.invoiceTags || [] }) }), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-2 gap-4", children: [_jsx("div", { className: "col-span-1", children: _jsx("div", { className: "", children: _jsx(Textarea, { label: "Notes", placeholder: "Add notes", autoExpand: false, rows: 4, multiline: true, value: notes, onBlur: (e) => {
378
+ }, onUpdateItem: (item) => dispatch(actions.editLineItem(item)), onEditingItemChange: setEditingItemValues, dispatch: dispatch, paymentAccounts: state.invoiceTags || [] }) }), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-2 gap-4", children: [_jsx("div", { className: "col-span-1", children: _jsx("div", { className: "", children: _jsx(Textarea, { label: "Notes", placeholder: "Add notes", autoExpand: false, rows: 4, multiline: true, value: notes, onBlur: (e) => {
358
379
  const newValue = e.target.value;
359
380
  if (newValue !== state.notes) {
360
381
  dispatch(actions.editInvoice({ notes: newValue }));
@@ -6,7 +6,7 @@ import { uploadPdfChunked } from "./uploadPdfChunked.js";
6
6
  import { getCountryCodeFromName } from "./utils/utils.js";
7
7
  let GRAPHQL_URL = 'http://localhost:4001/graphql/invoice';
8
8
  if (!window.document.baseURI.includes('localhost')) {
9
- GRAPHQL_URL = 'https://switchboard.powerhouse.xyz/graphql/invoice';
9
+ GRAPHQL_URL = 'https://switchboard-dev.powerhouse.xyz/graphql/invoice';
10
10
  }
11
11
  export async function loadPDFFile({ file, dispatch, }) {
12
12
  if (!file)
@@ -1 +1 @@
1
- {"version":3,"file":"invoiceToGnosis.d.ts","sourceRoot":"","sources":["../../../editors/invoice/invoiceToGnosis.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAYxC,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,GAAG,CAAC;IACd,QAAQ,EAAE,GAAG,CAAC;CACf;AAED,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA6LnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"invoiceToGnosis.d.ts","sourceRoot":"","sources":["../../../editors/invoice/invoiceToGnosis.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAUxC,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,GAAG,CAAC;IACd,QAAQ,EAAE,GAAG,CAAC;CACf;AAED,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAmQnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -1,12 +1,12 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
- import { actions, } from "../../document-models/invoice/index.js";
3
+ import { actions } from "../../document-models/invoice/index.js";
4
4
  import { generateId } from "document-model";
5
5
  let GRAPHQL_URL = "http://localhost:4001/graphql/invoice";
6
- if (!window.document.baseURI.includes('localhost')) {
7
- GRAPHQL_URL = 'https://switchboard.powerhouse.xyz/graphql/invoice';
6
+ if (!window.document.baseURI.includes("localhost")) {
7
+ GRAPHQL_URL = "https://switchboard-dev.powerhouse.xyz/graphql/invoice";
8
8
  }
9
- const InvoiceToGnosis = ({ docState, dispatch }) => {
9
+ const InvoiceToGnosis = ({ docState, dispatch, }) => {
10
10
  const [isLoading, setIsLoading] = useState(false);
11
11
  const [error, setError] = useState(null);
12
12
  const [responseData, setResponseData] = useState(null);
@@ -14,6 +14,7 @@ const InvoiceToGnosis = ({ docState, dispatch }) => {
14
14
  const [safeTxHash, setsafeTxHash] = useState(null);
15
15
  const currency = docState.currency;
16
16
  const chainName = docState.issuer.paymentRouting.wallet.chainName;
17
+ const invoiceStatus = docState.status;
17
18
  const TOKEN_ADDRESSES = {
18
19
  BASE: {
19
20
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -92,11 +93,19 @@ const InvoiceToGnosis = ({ docState, dispatch }) => {
92
93
  if (data.success) {
93
94
  const dataObj = typeof data.data === "string" ? JSON.parse(data.data) : data.data;
94
95
  setsafeTxHash(dataObj.txHash);
95
- // add gnosis tx hash to invoice
96
- dispatch(actions.schedulePayment({
97
- id: generateId(),
98
- processorRef: dataObj.txHash,
99
- }));
96
+ if (invoiceStatus === "ACCEPTED") {
97
+ dispatch(actions.schedulePayment({
98
+ id: generateId(),
99
+ processorRef: dataObj.txHash,
100
+ }));
101
+ }
102
+ else {
103
+ dispatch(actions.addPayment({
104
+ id: generateId(),
105
+ processorRef: dataObj.txHash,
106
+ confirmed: false,
107
+ }));
108
+ }
100
109
  if (dataObj.paymentDetails) {
101
110
  // Format the payment details for better readability
102
111
  const formattedDetails = {
@@ -109,12 +118,24 @@ const InvoiceToGnosis = ({ docState, dispatch }) => {
109
118
  }
110
119
  else {
111
120
  setError(data.error);
121
+ dispatch(actions.addPayment({
122
+ id: generateId(),
123
+ processorRef: "",
124
+ confirmed: false,
125
+ issue: data.error.message,
126
+ }));
112
127
  }
113
128
  setIsLoading(false);
114
129
  }
115
130
  catch (error) {
116
- console.error("Error during transfer:", error);
117
131
  setIsLoading(false);
132
+ console.error("Error during transfer:", error);
133
+ dispatch(actions.addPayment({
134
+ id: generateId(),
135
+ processorRef: "",
136
+ confirmed: false,
137
+ issue: error.message,
138
+ }));
118
139
  }
119
140
  };
120
141
  if (!currency || !chainName || currency === "" || chainName === "") {
@@ -131,6 +152,15 @@ const InvoiceToGnosis = ({ docState, dispatch }) => {
131
152
  }
132
153
  };
133
154
  const urlChainName = parseChainName(chainName);
134
- return (_jsxs("div", { className: "space-y-4", children: [currency && chainName && currency !== "" && chainName !== "" && (_jsx("button", { className: "bg-blue-500 text-black px-4 py-2 rounded-md hover:bg-blue-600", onClick: handleInvoiceToGnosis, disabled: isLoading, children: isLoading ? "Processing..." : "Send Payment to Gnosis >" })), error && (_jsx("div", { className: "text-red-500 bg-red-50 p-3 rounded-md", children: error })), safeTxHash && (_jsxs("div", { className: "bg-gray-50 p-4 rounded-md space-y-2", children: [_jsxs("p", { className: "font-medium", children: ["Safe Transaction Hash:", _jsx("span", { className: "font-mono text-sm ml-2 break-all", children: safeTxHash })] }), _jsx("a", { href: `https://app.safe.global/transactions/queue?safe=${urlChainName}:0xF130f741d4E3185b29412c65397363f8c23A0460`, target: "_blank", rel: "noopener noreferrer", className: "text-blue-500 hover:text-blue-600 underline block", children: "View Transaction" })] })), invoiceStatusResponse && (_jsxs("div", { className: "bg-gray-50 p-4 rounded-md", children: [_jsx("p", { className: "font-medium", children: "Payment Details:" }), _jsx("p", { className: "text-gray-700", children: invoiceStatusResponse })] }))] }));
155
+ const linkText = "Gnosis Safe [>]";
156
+ return (_jsxs("div", { className: "space-y-4", children: [currency && chainName && currency !== "" && chainName !== "" && (_jsx("button", { className: "bg-blue-500 text-black px-4 py-2 rounded-md hover:bg-blue-600", onClick: handleInvoiceToGnosis, disabled: isLoading, children: isLoading
157
+ ? "Processing..."
158
+ : invoiceStatus === "ACCEPTED"
159
+ ? "Schedule Payment in Gnosis Safe"
160
+ : "Reschedule Payment in Gnosis Safe" })), error && (_jsx("div", { className: "text-red-500 bg-red-50 p-3 rounded-md", children: error })), safeTxHash && (_jsxs("div", { className: "bg-gray-50 mt-4 rounded-md space-y-2", children: [_jsx("a", { href: `https://app.safe.global/transactions/queue?safe=${urlChainName}:0x1FB6bEF04230d67aF0e3455B997a28AFcCe1F45e`, target: "_blank", rel: "noopener noreferrer", className: "text-blue-500 hover:text-blue-600 underline block", children: linkText }), _jsxs("p", { className: "font-medium", children: ["Safe Transaction Hash:", _jsx("span", { className: "font-mono text-sm ml-2 break-all", children: safeTxHash })] })] })), !safeTxHash &&
161
+ !error &&
162
+ invoiceStatus === "PAYMENTSCHEDULED" &&
163
+ docState.payments.length > 0 && (_jsx(_Fragment, { children: docState.payments[docState.payments.length - 1].issue !== "" ? (_jsx("div", { className: "mt-4", children: _jsxs("p", { className: "text-red-700 font-medium", children: ["Issue: ", docState.payments[docState.payments.length - 1].issue] }) })) : (_jsxs("div", { className: "mt-4", children: [_jsx("div", { className: "invoice-link text-blue-900 hover:text-blue-600", children: _jsx("a", { className: "view-invoice-button", href: `https://app.safe.global/transactions/queue?safe=${urlChainName}:0x1FB6bEF04230d67aF0e3455B997a28AFcCe1F45e`, target: "_blank", rel: "noopener noreferrer", children: linkText }) }), _jsxs("p", { className: "mt-4 font-medium", children: ["Safe Transaction Hash:", _jsx("span", { className: "font-mono text-sm ml-2 break-all", children: docState.payments[docState.payments.length - 1]
164
+ .processorRef })] })] })) })), invoiceStatusResponse && (_jsxs("div", { className: "bg-gray-50 p-4 rounded-md", children: [_jsx("p", { className: "font-medium", children: "Payment Details:" }), _jsx("p", { className: "text-gray-700", children: invoiceStatusResponse })] }))] }));
135
165
  };
136
166
  export default InvoiceToGnosis;
@@ -20,9 +20,15 @@ type LineItemsTableProps = {
20
20
  readonly onUpdateItem: (item: LineItem) => void;
21
21
  readonly onDeleteItem: (input: DeleteLineItemInput) => void;
22
22
  readonly onUpdateCurrency: (input: EditInvoiceInput) => void;
23
+ readonly onEditingItemChange?: (values: {
24
+ id: string;
25
+ quantity: number;
26
+ unitPriceTaxExcl: number;
27
+ unitPriceTaxIncl: number;
28
+ } | null) => void;
23
29
  readonly dispatch: Dispatch<any>;
24
30
  readonly paymentAccounts: InvoiceTag[];
25
31
  };
26
- export declare function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, onDeleteItem, onUpdateCurrency, dispatch, paymentAccounts, }: LineItemsTableProps): import("react/jsx-runtime").JSX.Element;
32
+ export declare function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, onDeleteItem, onUpdateCurrency, onEditingItemChange, dispatch, paymentAccounts, }: LineItemsTableProps): import("react/jsx-runtime").JSX.Element;
27
33
  export {};
28
34
  //# sourceMappingURL=lineItems.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lineItems.d.ts","sourceRoot":"","sources":["../../../editors/invoice/lineItems.tsx"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EAChB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAKL,KAAK,QAAQ,EACd,MAAM,OAAO,CAAC;AAaf,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAsBlD;AAED,KAAK,QAAQ,GAAG;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,CAAC;AAwNF,KAAK,mBAAmB,GAAG;IACzB,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7D,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,UAAU,EAAE,CAAC;CACxC,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,QAAQ,EACR,eAAe,GAChB,EAAE,mBAAmB,2CA6LrB"}
1
+ {"version":3,"file":"lineItems.d.ts","sourceRoot":"","sources":["../../../editors/invoice/lineItems.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EAChB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAKL,KAAK,QAAQ,EAEd,MAAM,OAAO,CAAC;AAYf,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED,KAAK,QAAQ,GAAG;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,CAAC;AA+aF,KAAK,mBAAmB,GAAG;IACzB,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAC7B,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;QACzB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,GAAG,IAAI,KACL,IAAI,CAAC;IACV,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,UAAU,EAAE,CAAC;CACxC,CAAC;AAEF,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,QAAQ,EACR,eAAe,GAChB,EAAE,mBAAmB,2CA+LrB"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { RWAButton } from "@powerhousedao/design-system";
3
- import { forwardRef, useState, useMemo, useRef, } from "react";
3
+ import { forwardRef, useState, useMemo, useRef, useEffect, } from "react";
4
4
  import { generateId } from "document-model";
5
5
  import { Tag } from "lucide-react";
6
6
  import { NumberForm } from "./components/numberForm.js";
@@ -11,33 +11,21 @@ function getCurrencyPrecision(currency) {
11
11
  return currency === "USDS" || currency === "DAI" ? 6 : 2;
12
12
  }
13
13
  export function formatNumber(value) {
14
- // Check if the value has decimal places
15
- const hasDecimals = value % 1 !== 0;
16
- // If no decimals or only trailing zeros after 2 decimal places, show 2 decimal places
17
- if (!hasDecimals || value.toFixed(5).endsWith("000")) {
18
- return value.toLocaleString("en-US", {
19
- minimumFractionDigits: 2,
20
- maximumFractionDigits: 2,
21
- });
22
- }
23
- // Otherwise, show actual decimal places up to 5
24
- const stringValue = value.toString();
25
- const decimalPart = stringValue.split(".")[1] || "";
26
- // Determine how many decimal places to show (up to 5)
27
- const decimalPlaces = Math.min(Math.max(2, decimalPart.length), 5);
28
14
  return value.toLocaleString("en-US", {
29
- minimumFractionDigits: decimalPlaces,
30
- maximumFractionDigits: decimalPlaces,
15
+ minimumFractionDigits: 2,
16
+ maximumFractionDigits: 2,
31
17
  });
32
18
  }
33
19
  const EditableLineItem = forwardRef(function EditableLineItem(props, ref) {
34
- const { item, onSave, onCancel, currency } = props;
20
+ const { item, onSave, onCancel, currency, onEditingItemChange } = props;
35
21
  const [editedItem, setEditedItem] = useState({
36
22
  ...item,
37
23
  currency,
38
24
  quantity: item.quantity ?? "",
39
25
  taxPercent: item.taxPercent ?? "",
40
26
  unitPriceTaxExcl: item.unitPriceTaxExcl ?? "",
27
+ totalPriceTaxExcl: item.totalPriceTaxExcl ?? "",
28
+ totalPriceTaxIncl: item.totalPriceTaxIncl ?? "",
41
29
  });
42
30
  const calculatedValues = useMemo(() => {
43
31
  const quantity = typeof editedItem.quantity === "string"
@@ -55,16 +43,103 @@ const EditableLineItem = forwardRef(function EditableLineItem(props, ref) {
55
43
  ? 0
56
44
  : Number(editedItem.taxPercent)
57
45
  : (editedItem.taxPercent ?? 0);
58
- const totalPriceTaxExcl = quantity * unitPriceTaxExcl;
59
- const taxAmount = totalPriceTaxExcl * (taxPercent / 100);
60
- const totalPriceTaxIncl = totalPriceTaxExcl + taxAmount;
61
- const unitPriceTaxIncl = unitPriceTaxExcl * (1 + taxPercent / 100);
46
+ const totalPriceTaxExcl = typeof editedItem.totalPriceTaxExcl === "string"
47
+ ? editedItem.totalPriceTaxExcl === ""
48
+ ? 0
49
+ : Number(editedItem.totalPriceTaxExcl)
50
+ : (editedItem.totalPriceTaxExcl ?? 0);
51
+ const totalPriceTaxIncl = typeof editedItem.totalPriceTaxIncl === "string"
52
+ ? editedItem.totalPriceTaxIncl === ""
53
+ ? 0
54
+ : Number(editedItem.totalPriceTaxIncl)
55
+ : (editedItem.totalPriceTaxIncl ?? 0);
56
+ const taxRate = taxPercent / 100;
57
+ const EPSILON = 0.00001;
58
+ // Helper function to compare floating point numbers
59
+ const isClose = (a, b) => Math.abs(a - b) < EPSILON;
60
+ // Check if user explicitly edited any fields
61
+ const userEditedQuantity = editedItem.quantity !== undefined &&
62
+ editedItem.quantity !== item.quantity;
63
+ const userEditedUnitPriceTaxExcl = editedItem.unitPriceTaxExcl !== undefined &&
64
+ editedItem.unitPriceTaxExcl !== item.unitPriceTaxExcl;
65
+ const userEditedTotalPriceTaxExcl = editedItem.totalPriceTaxExcl !== undefined &&
66
+ editedItem.totalPriceTaxExcl !== item.totalPriceTaxExcl;
67
+ const userEditedTotalPriceTaxIncl = editedItem.totalPriceTaxIncl !== undefined &&
68
+ editedItem.totalPriceTaxIncl !== item.totalPriceTaxIncl;
69
+ let finalUnitPriceTaxExcl = unitPriceTaxExcl;
70
+ let finalUnitPriceTaxIncl = unitPriceTaxExcl * (1 + taxRate);
71
+ let finalTotalPriceTaxExcl = quantity * unitPriceTaxExcl;
72
+ let finalTotalPriceTaxIncl = quantity * finalUnitPriceTaxIncl;
73
+ // If user explicitly edited totalPriceTaxExcl, use that value and calculate unit price
74
+ if (userEditedTotalPriceTaxExcl && totalPriceTaxExcl !== 0) {
75
+ finalTotalPriceTaxExcl = totalPriceTaxExcl;
76
+ finalUnitPriceTaxExcl = totalPriceTaxExcl / quantity;
77
+ finalUnitPriceTaxIncl = finalUnitPriceTaxExcl * (1 + taxRate);
78
+ finalTotalPriceTaxIncl = quantity * finalUnitPriceTaxIncl;
79
+ }
80
+ // If user explicitly edited totalPriceTaxIncl, use that value and calculate unit price
81
+ else if (userEditedTotalPriceTaxIncl && totalPriceTaxIncl !== 0) {
82
+ finalTotalPriceTaxIncl = totalPriceTaxIncl;
83
+ finalUnitPriceTaxIncl = totalPriceTaxIncl / quantity;
84
+ finalUnitPriceTaxExcl = finalUnitPriceTaxIncl / (1 + taxRate);
85
+ finalTotalPriceTaxExcl = quantity * finalUnitPriceTaxExcl;
86
+ }
87
+ // If user explicitly edited unitPriceTaxExcl, use that value and calculate totals
88
+ else if (userEditedUnitPriceTaxExcl && unitPriceTaxExcl !== 0) {
89
+ finalUnitPriceTaxExcl = unitPriceTaxExcl;
90
+ finalUnitPriceTaxIncl = unitPriceTaxExcl * (1 + taxRate);
91
+ finalTotalPriceTaxExcl = quantity * finalUnitPriceTaxExcl;
92
+ finalTotalPriceTaxIncl = quantity * finalUnitPriceTaxIncl;
93
+ }
94
+ // If user explicitly edited quantity, recalculate totals
95
+ else if (userEditedQuantity) {
96
+ finalTotalPriceTaxExcl = quantity * finalUnitPriceTaxExcl;
97
+ finalTotalPriceTaxIncl = quantity * finalUnitPriceTaxIncl;
98
+ }
62
99
  return {
63
- totalPriceTaxExcl,
64
- totalPriceTaxIncl,
65
- unitPriceTaxIncl,
100
+ quantity: quantity,
101
+ taxPercent: taxPercent,
102
+ totalPriceTaxExcl: finalTotalPriceTaxExcl,
103
+ totalPriceTaxIncl: finalTotalPriceTaxIncl,
104
+ unitPriceTaxIncl: finalUnitPriceTaxIncl,
105
+ unitPriceTaxExcl: finalUnitPriceTaxExcl,
66
106
  };
67
- }, [editedItem.quantity, editedItem.unitPriceTaxExcl, editedItem.taxPercent]);
107
+ }, [
108
+ editedItem.quantity,
109
+ editedItem.unitPriceTaxExcl,
110
+ editedItem.taxPercent,
111
+ editedItem.totalPriceTaxExcl,
112
+ editedItem.totalPriceTaxIncl,
113
+ item.quantity,
114
+ item.unitPriceTaxExcl,
115
+ item.totalPriceTaxExcl,
116
+ item.totalPriceTaxIncl,
117
+ ]);
118
+ // Update parent component with current editing values for live totals
119
+ useEffect(() => {
120
+ if (onEditingItemChange && item.id) {
121
+ onEditingItemChange({
122
+ id: item.id,
123
+ quantity: calculatedValues.quantity,
124
+ unitPriceTaxExcl: calculatedValues.unitPriceTaxExcl,
125
+ unitPriceTaxIncl: calculatedValues.unitPriceTaxIncl,
126
+ });
127
+ }
128
+ }, [
129
+ calculatedValues.quantity,
130
+ calculatedValues.unitPriceTaxExcl,
131
+ calculatedValues.unitPriceTaxIncl,
132
+ onEditingItemChange,
133
+ item.id,
134
+ ]);
135
+ // Clean up when component unmounts
136
+ useEffect(() => {
137
+ return () => {
138
+ if (onEditingItemChange && item.id) {
139
+ onEditingItemChange(null);
140
+ }
141
+ };
142
+ }, [onEditingItemChange, item.id]);
68
143
  function handleInputChange(field) {
69
144
  return function handleChange(event) {
70
145
  const value = event.target.value;
@@ -99,6 +174,24 @@ const EditableLineItem = forwardRef(function EditableLineItem(props, ref) {
99
174
  setEditedItem((prev) => ({ ...prev, [field]: value }));
100
175
  }
101
176
  }
177
+ else if (field === "totalPriceTaxExcl") {
178
+ // For total price, allow up to dynamic decimal places based on currency
179
+ const maxDecimals = getCurrencyPrecision(currency);
180
+ // Allow negative numbers with optional minus sign at start
181
+ const regex = new RegExp(`^-?\\d*\\.?\\d{0,${maxDecimals}}$`);
182
+ if (regex.test(value)) {
183
+ setEditedItem((prev) => ({ ...prev, [field]: Number(value) }));
184
+ }
185
+ }
186
+ else if (field === "totalPriceTaxIncl") {
187
+ // For total price, allow up to dynamic decimal places based on currency
188
+ const maxDecimals = getCurrencyPrecision(currency);
189
+ // Allow negative numbers with optional minus sign at start
190
+ const regex = new RegExp(`^-?\\d*\\.?\\d{0,${maxDecimals}}$`);
191
+ if (regex.test(value)) {
192
+ setEditedItem((prev) => ({ ...prev, [field]: value }));
193
+ }
194
+ }
102
195
  else {
103
196
  // For other decimal fields
104
197
  if (/^-?\d*\.?\d*$/.test(value)) {
@@ -108,40 +201,82 @@ const EditableLineItem = forwardRef(function EditableLineItem(props, ref) {
108
201
  };
109
202
  }
110
203
  function handleSave() {
111
- const quantity = typeof editedItem.quantity === "string"
112
- ? editedItem.quantity === ""
113
- ? 0
114
- : Number(editedItem.quantity)
115
- : (editedItem.quantity ?? 0);
116
- const unitPriceTaxExcl = typeof editedItem.unitPriceTaxExcl === "string"
117
- ? editedItem.unitPriceTaxExcl === ""
118
- ? 0
119
- : Number(editedItem.unitPriceTaxExcl)
120
- : (editedItem.unitPriceTaxExcl ?? 0);
121
- const taxPercent = typeof editedItem.taxPercent === "string"
122
- ? editedItem.taxPercent === ""
123
- ? 0
124
- : Number(editedItem.taxPercent)
125
- : (editedItem.taxPercent ?? 0);
126
- const completeItem = {
204
+ // Helper function for floating point comparison
205
+ const isClose = (a, b) => Math.abs(a - b) < 0.00001;
206
+ // Create an object with only the fields that have changed
207
+ const updateInput = {
127
208
  id: editedItem.id ?? generateId(),
128
- currency: editedItem.currency,
129
- description: editedItem.description ?? "",
130
- quantity: quantity,
131
- taxPercent: taxPercent,
132
- unitPriceTaxExcl: unitPriceTaxExcl,
133
- unitPriceTaxIncl: calculatedValues.unitPriceTaxIncl,
134
- totalPriceTaxExcl: calculatedValues.totalPriceTaxExcl,
135
- totalPriceTaxIncl: calculatedValues.totalPriceTaxIncl,
136
- lineItemTag: [],
209
+ currency,
137
210
  };
138
- onSave(completeItem);
211
+ // Check if any field was explicitly edited by the user
212
+ const hasEditedDescription = editedItem.description !== undefined &&
213
+ editedItem.description !== item.description;
214
+ const hasEditedQuantity = editedItem.quantity !== undefined &&
215
+ editedItem.quantity !== item.quantity;
216
+ const hasEditedTaxPercent = editedItem.taxPercent !== undefined &&
217
+ editedItem.taxPercent !== item.taxPercent;
218
+ const hasEditedUnitPriceTaxExcl = editedItem.unitPriceTaxExcl !== undefined &&
219
+ editedItem.unitPriceTaxExcl !== item.unitPriceTaxExcl;
220
+ const hasEditedTotalPriceTaxExcl = editedItem.totalPriceTaxExcl !== undefined &&
221
+ editedItem.totalPriceTaxExcl !== item.totalPriceTaxExcl;
222
+ const hasEditedTotalPriceTaxIncl = editedItem.totalPriceTaxIncl !== undefined &&
223
+ editedItem.totalPriceTaxIncl !== item.totalPriceTaxIncl;
224
+ // Always include fields that were explicitly edited
225
+ if (hasEditedDescription) {
226
+ updateInput.description = editedItem.description ?? "";
227
+ }
228
+ if (hasEditedQuantity) {
229
+ updateInput.quantity = calculatedValues.quantity;
230
+ }
231
+ if (hasEditedTaxPercent) {
232
+ updateInput.taxPercent = calculatedValues.taxPercent;
233
+ }
234
+ if (hasEditedUnitPriceTaxExcl) {
235
+ updateInput.unitPriceTaxExcl = calculatedValues.unitPriceTaxExcl;
236
+ }
237
+ if (hasEditedTotalPriceTaxExcl) {
238
+ updateInput.totalPriceTaxExcl = calculatedValues.totalPriceTaxExcl;
239
+ }
240
+ if (hasEditedTotalPriceTaxIncl) {
241
+ updateInput.totalPriceTaxIncl = calculatedValues.totalPriceTaxIncl;
242
+ }
243
+ // Also include fields that changed due to calculations (even if not explicitly edited)
244
+ if (!hasEditedQuantity &&
245
+ !isClose(calculatedValues.quantity, item.quantity ?? 0)) {
246
+ updateInput.quantity = calculatedValues.quantity;
247
+ }
248
+ if (!hasEditedTaxPercent &&
249
+ !isClose(calculatedValues.taxPercent, item.taxPercent ?? 0)) {
250
+ updateInput.taxPercent = calculatedValues.taxPercent;
251
+ }
252
+ if (!hasEditedUnitPriceTaxExcl &&
253
+ !isClose(calculatedValues.unitPriceTaxExcl, item.unitPriceTaxExcl ?? 0)) {
254
+ updateInput.unitPriceTaxExcl = calculatedValues.unitPriceTaxExcl;
255
+ }
256
+ if (!isClose(calculatedValues.unitPriceTaxIncl, item.unitPriceTaxIncl ?? 0)) {
257
+ updateInput.unitPriceTaxIncl = calculatedValues.unitPriceTaxIncl;
258
+ }
259
+ if (!hasEditedTotalPriceTaxExcl &&
260
+ !isClose(calculatedValues.totalPriceTaxExcl, item.totalPriceTaxExcl ?? 0)) {
261
+ updateInput.totalPriceTaxExcl = calculatedValues.totalPriceTaxExcl;
262
+ }
263
+ if (!hasEditedTotalPriceTaxIncl &&
264
+ !isClose(calculatedValues.totalPriceTaxIncl, item.totalPriceTaxIncl ?? 0)) {
265
+ updateInput.totalPriceTaxIncl = calculatedValues.totalPriceTaxIncl;
266
+ }
267
+ onSave(updateInput);
139
268
  }
140
269
  return (_jsxs("tr", { ref: ref, className: "hover:bg-gray-50 table-row", children: [_jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(InputField, { onBlur: () => { }, handleInputChange: (e) => {
141
270
  setEditedItem((prev) => ({ ...prev, description: e.target.value }));
142
- }, value: editedItem.description ?? "", placeholder: "Description", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number: editedItem.quantity ?? "", precision: 0, handleInputChange: handleInputChange("quantity"), placeholder: "Quantity", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number: editedItem.unitPriceTaxExcl ?? "", precision: getCurrencyPrecision(currency), handleInputChange: handleInputChange("unitPriceTaxExcl"), pattern: "^-?\\d*\\.?\\d*$", placeholder: "Unit Price (excl. tax)", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number: editedItem.taxPercent ?? "", precision: 0, pattern: "^(100|[1-9]?[0-9])$", handleInputChange: handleInputChange("taxPercent"), placeholder: "Tax %", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(calculatedValues.totalPriceTaxExcl) }), _jsx("td", { className: "border border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(calculatedValues.totalPriceTaxIncl) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsxs("div", { className: "flex space-x-2", children: [_jsx("button", { className: "rounded bg-blue-500 px-3 py-1 text-white hover:bg-blue-700", onClick: handleSave, children: "Save" }), _jsx("button", { className: "rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600", onClick: onCancel, children: "Cancel" })] }) })] }));
271
+ }, value: editedItem.description ?? "", placeholder: "Description", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number: calculatedValues.quantity, precision: 0, handleInputChange: handleInputChange("quantity"), placeholder: "Quantity", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number: calculatedValues.unitPriceTaxExcl % 1 === 0
272
+ ? calculatedValues.unitPriceTaxExcl.toString()
273
+ : calculatedValues.unitPriceTaxExcl.toFixed(2), precision: 2, handleInputChange: handleInputChange("unitPriceTaxExcl"), pattern: "^-?\\d*\\.?\\d*$", placeholder: "Unit Price (excl. tax)", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsx(NumberForm, { number: calculatedValues.taxPercent, precision: 0, pattern: "^(100|[1-9]?[0-9])$", handleInputChange: handleInputChange("taxPercent"), placeholder: "Tax %", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 text-right font-medium table-cell", children: _jsx(NumberForm, { number: calculatedValues.totalPriceTaxExcl % 1 === 0
274
+ ? calculatedValues.totalPriceTaxExcl.toString()
275
+ : calculatedValues.totalPriceTaxExcl.toFixed(2), precision: 2, handleInputChange: handleInputChange("totalPriceTaxExcl"), pattern: "^-?\\d*\\.?\\d*$", placeholder: "Total (excl. tax)", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 text-right font-medium table-cell", children: _jsx(NumberForm, { number: calculatedValues.totalPriceTaxIncl % 1 === 0
276
+ ? calculatedValues.totalPriceTaxIncl.toString()
277
+ : calculatedValues.totalPriceTaxIncl.toFixed(2), precision: 2, handleInputChange: handleInputChange("totalPriceTaxIncl"), pattern: "^-?\\d*\\.?\\d*$", placeholder: "Total (incl. tax)", className: "" }) }), _jsx("td", { className: "border border-gray-200 p-3 table-cell", children: _jsxs("div", { className: "flex space-x-2", children: [_jsx("button", { className: "rounded bg-blue-500 px-3 py-1 text-white hover:bg-blue-700", onClick: handleSave, children: "Save" }), _jsx("button", { className: "rounded bg-gray-500 px-3 py-1 text-white hover:bg-gray-600", onClick: onCancel, children: "Cancel" })] }) })] }));
143
278
  });
144
- export function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, onDeleteItem, onUpdateCurrency, dispatch, paymentAccounts, }) {
279
+ export function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, onDeleteItem, onUpdateCurrency, onEditingItemChange, dispatch, paymentAccounts, }) {
145
280
  const [editingId, setEditingId] = useState(null);
146
281
  const [isAddingNew, setIsAddingNew] = useState(false);
147
282
  const [showTagTable, setShowTagTable] = useState(false);
@@ -180,7 +315,7 @@ export function LineItemsTable({ lineItems, currency, onAddItem, onUpdateItem, o
180
315
  } })] }) })] }) }), _jsxs("tbody", { children: [lineItems.map((item) => editingId === item.id ? (_jsx(EditableLineItem, { currency: currency, item: item, onCancel: () => setEditingId(null), onSave: (updatedItem) => {
181
316
  onUpdateItem(updatedItem);
182
317
  setEditingId(null);
183
- } }, item.id)) : (_jsxs("tr", { className: "hover:bg-gray-50 table-row", children: [_jsx("td", { className: "border-b border-gray-200 p-3 table-cell", children: item.description }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: item.quantity }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: formatNumber(item.unitPriceTaxExcl) }), _jsxs("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: [typeof item.taxPercent === "number"
318
+ }, onEditingItemChange: onEditingItemChange }, item.id)) : (_jsxs("tr", { className: "hover:bg-gray-50 table-row", children: [_jsx("td", { className: "border-b border-gray-200 p-3 table-cell", children: item.description }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: item.quantity }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: formatNumber(item.unitPriceTaxExcl) }), _jsxs("td", { className: "border-b border-gray-200 p-3 text-right table-cell", children: [typeof item.taxPercent === "number"
184
319
  ? Math.round(item.taxPercent)
185
- : 0, "%"] }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(item.totalPriceTaxExcl) }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(item.totalPriceTaxIncl) }), _jsx("td", { className: "border-b border-gray-200 p-3 table-cell", children: _jsxs("div", { className: "flex justify-center space-x-2", children: [_jsx("button", { className: "rounded bg-blue-500 px-3 py-1 text-white hover:bg-blue-200", onClick: () => setEditingId(item.id), children: "Edit" }), _jsx("button", { className: "rounded bg-red-600 px-3 py-1 text-white hover:bg-red-700", onClick: () => onDeleteItem({ id: item.id }), children: "Delete" })] }) })] }, item.id))), isAddingNew ? (_jsx(EditableLineItem, { currency: currency, item: {}, onCancel: handleCancelNewItem, onSave: handleSaveNewItem })) : null] })] }) })] }) }));
320
+ : 0, "%"] }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(item.totalPriceTaxExcl) }), _jsx("td", { className: "border-b border-gray-200 p-3 text-right font-medium table-cell", children: formatNumber(item.totalPriceTaxIncl) }), _jsx("td", { className: "border-b border-gray-200 p-3 table-cell", children: _jsxs("div", { className: "flex justify-center space-x-2", children: [_jsx("button", { className: "rounded bg-blue-500 px-3 py-1 text-white hover:bg-blue-200", onClick: () => setEditingId(item.id), children: "Edit" }), _jsx("button", { className: "rounded bg-red-600 px-3 py-1 text-white hover:bg-red-700", onClick: () => onDeleteItem({ id: item.id }), children: "Delete" })] }) })] }, item.id))), isAddingNew ? (_jsx(EditableLineItem, { currency: currency, item: {}, onCancel: handleCancelNewItem, onSave: handleSaveNewItem, onEditingItemChange: onEditingItemChange })) : null] })] }) })] }) }));
186
321
  }
@@ -1 +1 @@
1
- {"version":3,"file":"requestFinance.d.ts","sourceRoot":"","sources":["../../../editors/invoice/requestFinance.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAYxC,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,GAAG,CAAC;IACd,QAAQ,EAAE,GAAG,CAAC;CACf;AAED,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAgNjD,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"requestFinance.d.ts","sourceRoot":"","sources":["../../../editors/invoice/requestFinance.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAUxC,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,GAAG,CAAC;IACd,QAAQ,EAAE,GAAG,CAAC;CACf;AAED,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAgQjD,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -1,17 +1,18 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
- import { actions, } from "../../document-models/invoice/index.js";
3
+ import { actions } from "../../document-models/invoice/index.js";
4
4
  import { generateId } from "document-model";
5
- let GRAPHQL_URL = 'http://localhost:4001/graphql/invoice';
6
- if (!window.document.baseURI.includes('localhost')) {
7
- GRAPHQL_URL = 'https://switchboard.powerhouse.xyz/graphql/invoice';
5
+ let GRAPHQL_URL = "http://localhost:4001/graphql/invoice";
6
+ if (!window.document.baseURI.includes("localhost")) {
7
+ GRAPHQL_URL = "https://switchboard-dev.powerhouse.xyz/graphql/invoice";
8
8
  }
9
- const RequestFinance = ({ docState, dispatch }) => {
9
+ const RequestFinance = ({ docState, dispatch, }) => {
10
10
  const [isLoading, setIsLoading] = useState(false);
11
11
  const [error, setError] = useState(null);
12
12
  const [responseData, setResponseData] = useState(null);
13
13
  const [invoiceLink, setInvoiceLink] = useState(null);
14
14
  const [directPaymentStatus, setDirectPaymentStatus] = useState(null);
15
+ const invoiceStatus = docState.status;
15
16
  // Function to call the createDirectPayment mutation
16
17
  const createDirectPayment = async (paymentData) => {
17
18
  try {
@@ -45,7 +46,8 @@ const RequestFinance = ({ docState, dispatch }) => {
45
46
  return result.data.Invoice_createRequestFinancePayment.data;
46
47
  }
47
48
  else {
48
- throw new Error(result.data?.Invoice_createRequestFinancePayment?.error || "Unknown error");
49
+ throw new Error(result.data?.Invoice_createRequestFinancePayment?.error ||
50
+ "Unknown error");
49
51
  }
50
52
  }
51
53
  catch (err) {
@@ -87,7 +89,7 @@ const RequestFinance = ({ docState, dispatch }) => {
87
89
  invoiceNumber: docState.invoiceNo,
88
90
  buyerInfo: {
89
91
  // email: docState.payer.contactInfo.email,
90
- email: '',
92
+ email: "",
91
93
  firstName: docState.payer.name,
92
94
  // lastName: docState.payer.name.split(" ")[1] || "Liberty",
93
95
  businessName: docState.payer.name,
@@ -102,7 +104,7 @@ const RequestFinance = ({ docState, dispatch }) => {
102
104
  sellerInfo: {
103
105
  email: docState.issuer.contactInfo.email,
104
106
  firstName: docState.issuer.name,
105
- lastName: '',
107
+ lastName: "",
106
108
  address: {
107
109
  country: docState.issuer.address.country,
108
110
  city: docState.issuer.address.city,
@@ -137,10 +139,19 @@ const RequestFinance = ({ docState, dispatch }) => {
137
139
  // Process the response
138
140
  if (directPaymentResult?.response?.invoiceLinks?.pay) {
139
141
  setInvoiceLink(directPaymentResult.response.invoiceLinks.pay);
140
- dispatch(actions.schedulePayment({
141
- id: generateId(),
142
- processorRef: directPaymentResult.response.invoiceLinks.pay,
143
- }));
142
+ if (invoiceStatus === "ACCEPTED") {
143
+ dispatch(actions.schedulePayment({
144
+ id: generateId(),
145
+ processorRef: directPaymentResult.response.invoiceLinks.pay,
146
+ }));
147
+ }
148
+ else {
149
+ dispatch(actions.addPayment({
150
+ id: generateId(),
151
+ processorRef: directPaymentResult.response.invoiceLinks.pay,
152
+ confirmed: false,
153
+ }));
154
+ }
144
155
  }
145
156
  setResponseData(directPaymentResult);
146
157
  setDirectPaymentStatus("Direct payment created successfully");
@@ -152,11 +163,25 @@ const RequestFinance = ({ docState, dispatch }) => {
152
163
  errorMessage = err.message;
153
164
  }
154
165
  setError(errorMessage);
166
+ dispatch(actions.addPayment({
167
+ id: generateId(),
168
+ processorRef: "",
169
+ confirmed: false,
170
+ issue: errorMessage,
171
+ }));
155
172
  }
156
173
  finally {
157
174
  setIsLoading(false);
158
175
  }
159
176
  };
160
- return (_jsxs("div", { children: [_jsx("button", { className: "bg-blue-500 text-black px-4 py-2 rounded-md", onClick: handleRequestFinance, disabled: isLoading, children: isLoading ? "Processing..." : "Send Payment to Request Finance >" }), error && (_jsx("div", { className: "error-message", style: { color: "red" }, children: error })), invoiceLink && (_jsxs("div", { children: [_jsx("div", { className: "direct-payment-status", children: _jsx("p", { children: directPaymentStatus }) }), _jsx("div", { className: "invoice-link", children: _jsx("a", { style: { color: "blue" }, href: invoiceLink, target: "_blank", rel: "noopener noreferrer", className: "view-invoice-button", children: "View Invoice" }) })] }))] }));
177
+ const liktText = "Request Finance [>]";
178
+ return (_jsxs("div", { children: [_jsx("button", { className: "bg-blue-500 text-black px-4 py-2 rounded-md", onClick: handleRequestFinance, disabled: isLoading, children: isLoading
179
+ ? "Processing..."
180
+ : invoiceStatus === "ACCEPTED"
181
+ ? "Schedule Payment in Request Finance"
182
+ : "Reschedule Payment in Request Finance" }), invoiceLink && (_jsxs("div", { children: [_jsx("div", { className: "direct-payment-status", children: _jsx("p", { children: directPaymentStatus }) }), _jsx("div", { className: "invoice-link text-blue-900 hover:text-blue-600", children: _jsx("a", { href: invoiceLink, target: "_blank", rel: "noopener noreferrer", className: "view-invoice-button", children: liktText }) })] })), !invoiceLink &&
183
+ invoiceStatus === "PAYMENTSCHEDULED" &&
184
+ docState.payments.length > 0 && (_jsx(_Fragment, { children: docState.payments[docState.payments.length - 1].issue !== "" ? (_jsx("div", { className: "mt-4", children: _jsxs("p", { className: "text-red-700 font-medium", children: ["Issue: ", docState.payments[docState.payments.length - 1].issue] }) })) : (_jsx("div", { className: "mt-4", children: _jsx("div", { className: "invoice-link text-blue-900 hover:text-blue-600", children: _jsx("a", { className: "view-invoice-button", href: docState.payments[docState.payments.length - 1]
185
+ .processorRef, target: "_blank", rel: "noopener noreferrer", children: liktText }) }) })) }))] }));
161
186
  };
162
187
  export default RequestFinance;
@@ -7,7 +7,7 @@
7
7
  */
8
8
  let GRAPHQL_URL = 'http://localhost:4001/graphql/invoice';
9
9
  if (!window.document.baseURI.includes('localhost')) {
10
- GRAPHQL_URL = 'https://switchboard.powerhouse.xyz/graphql/invoice';
10
+ GRAPHQL_URL = 'https://switchboard-dev.powerhouse.xyz/graphql/invoice';
11
11
  }
12
12
  export async function uploadPdfChunked(pdfData, endpoint = GRAPHQL_URL, chunkSize = 500 * 1024, // 500KB chunks
13
13
  onProgress) {
@@ -4,7 +4,7 @@ import { OperationType } from '@safe-global/types-kit';
4
4
  import { ethers, AbiCoder } from 'ethers';
5
5
  import dotenv from 'dotenv';
6
6
  dotenv.config();
7
- const safeAddress = process.env.PRODUCTION_SAFE_ADDRESS;
7
+ const safeAddress = process.env.DEV_STAGING_SAFE_ADDRESS;
8
8
  if (!safeAddress) {
9
9
  throw new Error('Missing SAFE_ADDRESS in .env');
10
10
  }
package/dist/style.css CHANGED
@@ -871,6 +871,9 @@
871
871
  .text-blue-600 {
872
872
  color: var(--color-blue-600);
873
873
  }
874
+ .text-blue-900 {
875
+ color: var(--color-blue-900);
876
+ }
874
877
  .text-gray-50 {
875
878
  color: var(--color-gray-50);
876
879
  }
@@ -904,6 +907,9 @@
904
907
  .text-red-600 {
905
908
  color: var(--color-red-600);
906
909
  }
910
+ .text-red-700 {
911
+ color: var(--color-red-700);
912
+ }
907
913
  .text-slate-800 {
908
914
  color: var(--color-slate-800);
909
915
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@powerhousedao/contributor-billing",
3
3
  "description": "Document models that help contributors of open organisations get paid anonymously for their work on a monthly basis.",
4
- "version": "0.0.65",
4
+ "version": "0.0.66",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
7
7
  "files": [