@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.
@@ -20,47 +20,45 @@ export function useWalletSync(wallets) {
20
20
  if (!wallet.billingStatements || wallet.billingStatements.length === 0) {
21
21
  return; // No billing statements to sync
22
22
  }
23
- // Get current line items count and total for this wallet
24
- const currentLineItemsCount = wallet.lineItems?.length || 0;
25
- let currentTotal = 0;
23
+ // Get current aggregated totals by category from wallet line items
24
+ const currentCategoryTotals = new Map();
26
25
  wallet.lineItems?.forEach((item) => {
27
- currentTotal += item?.actuals || 0;
26
+ if (item?.group) {
27
+ const currentTotal = currentCategoryTotals.get(item.group) || 0;
28
+ currentCategoryTotals.set(item.group, currentTotal + (item.actuals || 0));
29
+ }
28
30
  });
29
- // Calculate what the line items should be based on billing statements
30
- let expectedLineItemsCount = 0;
31
- let expectedTotal = 0;
32
- const expectedTags = new Set();
31
+ // Calculate expected aggregated totals by category from billing statements
32
+ const expectedCategoryTotals = new Map();
33
+ const expectedCategoryLabels = new Set();
33
34
  wallet.billingStatements.forEach((statementId) => {
34
35
  if (!statementId)
35
36
  return;
36
37
  const statement = billingStatements.get(statementId);
37
38
  if (statement?.state?.global?.lineItems) {
38
- expectedLineItemsCount += statement.state.global.lineItems.length;
39
39
  statement.state.global.lineItems.forEach((item) => {
40
- expectedTotal += item.totalPriceCash || 0;
41
- // Collect tags from billing statement
40
+ // Find expense-account tag
42
41
  const expenseAccountTag = item.lineItemTag?.find((tag) => tag.dimension === "expense-account");
43
42
  if (expenseAccountTag?.label) {
44
- expectedTags.add(expenseAccountTag.label);
43
+ expectedCategoryLabels.add(expenseAccountTag.label);
44
+ const currentTotal = expectedCategoryTotals.get(expenseAccountTag.label) || 0;
45
+ expectedCategoryTotals.set(expenseAccountTag.label, currentTotal + (item.totalPriceCash || 0));
45
46
  }
46
47
  });
47
48
  }
48
49
  });
49
- // Check current tags in wallet line items
50
- const currentTags = new Set();
51
- wallet.lineItems?.forEach((item) => {
52
- if (item?.group) {
53
- // We need to map the group ID back to label to compare
54
- // This is a simplified check - just checking if tags exist
55
- currentTags.add(item.group);
56
- }
57
- });
58
- // Check if wallet needs sync (count or total mismatch)
59
- const needsQuantitySync = currentLineItemsCount !== expectedLineItemsCount ||
60
- Math.abs(currentTotal - expectedTotal) > 0.01; // Account for floating point precision
61
- // Check if tags have changed (size mismatch suggests tag changes)
62
- const hasTagChanges = expectedTags.size > 0 && currentTags.size !== expectedTags.size;
63
- if (needsQuantitySync || hasTagChanges) {
50
+ // Check if categories have changed
51
+ const currentCategories = new Set(currentCategoryTotals.keys());
52
+ const hasTagChanges = currentCategories.size !== expectedCategoryLabels.size;
53
+ // Check if totals per category have changed
54
+ let hasTotalMismatch = false;
55
+ // We need to check if the aggregated totals match
56
+ // Since wallet stores group IDs but billing statements have labels,
57
+ // we need to sum up all line items regardless of category structure
58
+ const currentTotalActuals = Array.from(currentCategoryTotals.values()).reduce((sum, total) => sum + total, 0);
59
+ const expectedTotalActuals = Array.from(expectedCategoryTotals.values()).reduce((sum, total) => sum + total, 0);
60
+ hasTotalMismatch = Math.abs(currentTotalActuals - expectedTotalActuals) > 0.01;
61
+ if (hasTagChanges || hasTotalMismatch) {
64
62
  if (wallet.wallet) {
65
63
  outdatedWallets.push(wallet.wallet);
66
64
  if (hasTagChanges) {
@@ -1 +1 @@
1
- {"version":3,"file":"bankSection.d.ts","sourceRoot":"","sources":["../../../../editors/invoice/legalEntity/bankSection.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EAOtB,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAMtE,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,qBAAqB,CAAC,KAAK,CAAC,EAC5B,UAAU,CACX,GAAG;IACF,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAC;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACrD,QAAQ,CAAC,cAAc,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClD,QAAQ,CAAC,aAAa,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,uBAAuB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACtD,QAAQ,CAAC,uBAAuB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAmCF,eAAO,MAAM,sBAAsB,qIA6hBlC,CAAC"}
1
+ {"version":3,"file":"bankSection.d.ts","sourceRoot":"","sources":["../../../../editors/invoice/legalEntity/bankSection.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EAOtB,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAMtE,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,qBAAqB,CAAC,KAAK,CAAC,EAC5B,UAAU,CACX,GAAG;IACF,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAC;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACrD,QAAQ,CAAC,cAAc,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClD,QAAQ,CAAC,aAAa,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,uBAAuB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACtD,QAAQ,CAAC,uBAAuB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAmCF,eAAO,MAAM,sBAAsB,qIAoiBlC,CAAC"}
@@ -79,24 +79,24 @@ export const LegalEntityBankSection = forwardRef(function LegalEntityBankSection
79
79
  }
80
80
  const SEPA_SWIFT_CURRENCIES = ["EUR", "DKK", "GBP", "CHF", "JPY"];
81
81
  const usdIbanPayment = useMemo(() => isValidIBAN(localState.accountNum ?? "") && currency === "USD", [localState.accountNum, currency]);
82
- return (_jsxs("div", { ...divProps, className: twMerge("rounded-lg border border-gray-200 bg-white p-6", props.className), ref: ref, children: [_jsx("h3", { className: "mb-4 text-lg font-semibold text-gray-900", children: "Banking Information" }), _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "space-y-2", children: _jsx(InputField
83
- // input={localState.accountNum ?? ""}
84
- , {
85
- // input={localState.accountNum ?? ""}
86
- value: localState.accountNum ?? "", label: "Account Number", placeholder: "Account Number", onBlur: createBlurHandler("accountNum"), handleInputChange: createInputHandler("accountNum"), className: "h-10 w-full text-md mb-2", validation:
87
- // Prefer the first failing validation between IBAN and generic account number
88
- (() => {
89
- const firstInvalid = (ibanvalidation &&
90
- !ibanvalidation.isValid &&
91
- ibanvalidation) ||
92
- (accountNumbervalidation &&
93
- !accountNumbervalidation.isValid &&
94
- accountNumbervalidation);
95
- return (firstInvalid ||
96
- ibanvalidation ||
97
- accountNumbervalidation ||
98
- null);
99
- })() }) }), _jsx("div", { className: "space-y-2", children: _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx("div", { className: "space-y-2", children: _jsx(Select, { className: "h-10 w-full text-md mb-2", label: "Account Type", options: ACCOUNT_TYPES.map((type) => ({
82
+ return (_jsxs("div", { ...divProps, className: twMerge("rounded-lg border border-gray-200 bg-white p-6", props.className), ref: ref, children: [_jsx("h3", { className: "mb-4 text-lg font-semibold text-gray-900", children: "Banking Information" }), _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "space-y-2", children: _jsxs("div", { children: [_jsxs("label", { className: "block text-sm font-medium text-gray-700 mb-1", children: ["Account Number", isValidIBAN(localState.accountNum ?? "") && (_jsx("span", { className: "ml-2 text-green-600 font-medium", children: "IBAN" }))] }), _jsx(InputField
83
+ // input={localState.accountNum ?? ""}
84
+ , {
85
+ // input={localState.accountNum ?? ""}
86
+ value: localState.accountNum ?? "", placeholder: "Account Number", onBlur: createBlurHandler("accountNum"), handleInputChange: createInputHandler("accountNum"), className: "h-10 w-full text-md mb-2", validation:
87
+ // Prefer the first failing validation between IBAN and generic account number
88
+ (() => {
89
+ const firstInvalid = (ibanvalidation &&
90
+ !ibanvalidation.isValid &&
91
+ ibanvalidation) ||
92
+ (accountNumbervalidation &&
93
+ !accountNumbervalidation.isValid &&
94
+ accountNumbervalidation);
95
+ return (firstInvalid ||
96
+ ibanvalidation ||
97
+ accountNumbervalidation ||
98
+ null);
99
+ })() })] }) }), _jsx("div", { className: "space-y-2", children: _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx("div", { className: "space-y-2", children: _jsx(Select, { className: "h-10 w-full text-md mb-2", label: "Account Type", options: ACCOUNT_TYPES.map((type) => ({
100
100
  label: type,
101
101
  value: type,
102
102
  })), value: localState.accountType ?? "", onChange: (value) => {
@@ -49,7 +49,7 @@ export function LineItemTagsTable({ lineItems, onClose, dispatch, paymentAccount
49
49
  id: item.id,
50
50
  description: e.target.value,
51
51
  }));
52
- } }) }), _jsx("td", { className: "border-b border-gray-200 w-48", 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.setLineItemTag({
52
+ } }) }), _jsx("td", { className: "border-b border-gray-200 w-48", 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.setLineItemTag({
53
53
  lineItemId: item.id,
54
54
  dimension: "accounting-period",
55
55
  value: new Date(e.target.value)
package/dist/style.css CHANGED
@@ -310,6 +310,9 @@
310
310
  .inset-0 {
311
311
  inset: calc(var(--spacing) * 0);
312
312
  }
313
+ .top-0 {
314
+ top: calc(var(--spacing) * 0);
315
+ }
313
316
  .top-1\/2 {
314
317
  top: calc(1/2 * 100%);
315
318
  }
@@ -352,6 +355,9 @@
352
355
  max-width: 96rem;
353
356
  }
354
357
  }
358
+ .mx-4 {
359
+ margin-inline: calc(var(--spacing) * 4);
360
+ }
355
361
  .mx-auto {
356
362
  margin-inline: auto;
357
363
  }
@@ -421,6 +427,9 @@
421
427
  .inline {
422
428
  display: inline;
423
429
  }
430
+ .inline-block {
431
+ display: inline-block;
432
+ }
424
433
  .inline-flex {
425
434
  display: inline-flex;
426
435
  }
@@ -473,6 +482,9 @@
473
482
  .h-full {
474
483
  height: 100%;
475
484
  }
485
+ .max-h-20 {
486
+ max-height: calc(var(--spacing) * 20);
487
+ }
476
488
  .max-h-32 {
477
489
  max-height: calc(var(--spacing) * 32);
478
490
  }
@@ -533,6 +545,9 @@
533
545
  .w-72 {
534
546
  width: calc(var(--spacing) * 72);
535
547
  }
548
+ .w-96 {
549
+ width: calc(var(--spacing) * 96);
550
+ }
536
551
  .w-\[200px\] {
537
552
  width: 200px;
538
553
  }
@@ -569,6 +584,9 @@
569
584
  .min-w-0 {
570
585
  min-width: calc(var(--spacing) * 0);
571
586
  }
587
+ .min-w-\[4rem\] {
588
+ min-width: 4rem;
589
+ }
572
590
  .min-w-\[142px\] {
573
591
  min-width: 142px;
574
592
  }
@@ -810,6 +828,9 @@
810
828
  --tw-border-style: dashed;
811
829
  border-style: dashed;
812
830
  }
831
+ .border-amber-200 {
832
+ border-color: var(--color-amber-200);
833
+ }
813
834
  .border-blue-100 {
814
835
  border-color: var(--color-blue-100);
815
836
  }
@@ -831,6 +852,9 @@
831
852
  .border-gray-500 {
832
853
  border-color: var(--color-gray-500);
833
854
  }
855
+ .border-gray-900 {
856
+ border-color: var(--color-gray-900);
857
+ }
834
858
  .border-green-500 {
835
859
  border-color: var(--color-green-500);
836
860
  }
@@ -861,6 +885,12 @@
861
885
  background-color: color-mix(in oklab, var(--color-black) 30%, transparent);
862
886
  }
863
887
  }
888
+ .bg-black\/50 {
889
+ background-color: color-mix(in srgb, #000 50%, transparent);
890
+ @supports (color: color-mix(in lab, red, red)) {
891
+ background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
892
+ }
893
+ }
864
894
  .bg-blue-50 {
865
895
  background-color: var(--color-blue-50);
866
896
  }
@@ -885,9 +915,6 @@
885
915
  .bg-gray-300 {
886
916
  background-color: var(--color-gray-300);
887
917
  }
888
- .bg-gray-400 {
889
- background-color: var(--color-gray-400);
890
- }
891
918
  .bg-gray-500 {
892
919
  background-color: var(--color-gray-500);
893
920
  }
@@ -927,6 +954,9 @@
927
954
  .p-0 {
928
955
  padding: calc(var(--spacing) * 0);
929
956
  }
957
+ .p-0\.5 {
958
+ padding: calc(var(--spacing) * 0.5);
959
+ }
930
960
  .p-1 {
931
961
  padding: calc(var(--spacing) * 1);
932
962
  }
@@ -999,9 +1029,15 @@
999
1029
  .pt-6 {
1000
1030
  padding-top: calc(var(--spacing) * 6);
1001
1031
  }
1032
+ .pr-3 {
1033
+ padding-right: calc(var(--spacing) * 3);
1034
+ }
1002
1035
  .pb-2 {
1003
1036
  padding-bottom: calc(var(--spacing) * 2);
1004
1037
  }
1038
+ .pl-6 {
1039
+ padding-left: calc(var(--spacing) * 6);
1040
+ }
1005
1041
  .pl-11 {
1006
1042
  padding-left: calc(var(--spacing) * 11);
1007
1043
  }
@@ -1014,6 +1050,9 @@
1014
1050
  .text-right {
1015
1051
  text-align: right;
1016
1052
  }
1053
+ .align-top {
1054
+ vertical-align: top;
1055
+ }
1017
1056
  .font-mono {
1018
1057
  font-family: var(--font-mono);
1019
1058
  }
@@ -1061,18 +1100,21 @@
1061
1100
  --tw-tracking: var(--tracking-wider);
1062
1101
  letter-spacing: var(--tracking-wider);
1063
1102
  }
1103
+ .break-words {
1104
+ overflow-wrap: break-word;
1105
+ }
1064
1106
  .break-all {
1065
1107
  word-break: break-all;
1066
1108
  }
1067
1109
  .whitespace-nowrap {
1068
1110
  white-space: nowrap;
1069
1111
  }
1070
- .whitespace-pre-wrap {
1071
- white-space: pre-wrap;
1072
- }
1073
1112
  .text-amber-600 {
1074
1113
  color: var(--color-amber-600);
1075
1114
  }
1115
+ .text-amber-800 {
1116
+ color: var(--color-amber-800);
1117
+ }
1076
1118
  .text-black {
1077
1119
  color: var(--color-black);
1078
1120
  }
@@ -1139,9 +1181,6 @@
1139
1181
  .uppercase {
1140
1182
  text-transform: uppercase;
1141
1183
  }
1142
- .italic {
1143
- font-style: italic;
1144
- }
1145
1184
  .ordinal {
1146
1185
  --tw-ordinal: ordinal;
1147
1186
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
@@ -1154,9 +1193,6 @@
1154
1193
  color: var(--color-gray-500);
1155
1194
  }
1156
1195
  }
1157
- .opacity-0 {
1158
- opacity: 0%;
1159
- }
1160
1196
  .opacity-50 {
1161
1197
  opacity: 50%;
1162
1198
  }
@@ -1238,10 +1274,10 @@
1238
1274
  --tw-outline-style: none;
1239
1275
  outline-style: none;
1240
1276
  }
1241
- .group-hover\:opacity-100 {
1277
+ .group-hover\:bg-blue-50 {
1242
1278
  &:is(:where(.group):hover *) {
1243
1279
  @media (hover: hover) {
1244
- opacity: 100%;
1280
+ background-color: var(--color-blue-50);
1245
1281
  }
1246
1282
  }
1247
1283
  }
@@ -1325,13 +1361,6 @@
1325
1361
  }
1326
1362
  }
1327
1363
  }
1328
- .hover\:bg-gray-500 {
1329
- &:hover {
1330
- @media (hover: hover) {
1331
- background-color: var(--color-gray-500);
1332
- }
1333
- }
1334
- }
1335
1364
  .hover\:bg-gray-600 {
1336
1365
  &:hover {
1337
1366
  @media (hover: hover) {
@@ -1430,6 +1459,13 @@
1430
1459
  }
1431
1460
  }
1432
1461
  }
1462
+ .hover\:text-red-800 {
1463
+ &:hover {
1464
+ @media (hover: hover) {
1465
+ color: var(--color-red-800);
1466
+ }
1467
+ }
1468
+ }
1433
1469
  .hover\:opacity-80 {
1434
1470
  &:hover {
1435
1471
  @media (hover: hover) {
@@ -1601,6 +1637,16 @@
1601
1637
  }
1602
1638
  }
1603
1639
  }
1640
+ .dark\:border-amber-800 {
1641
+ &:where(.dark, .dark *) {
1642
+ border-color: var(--color-amber-800);
1643
+ }
1644
+ }
1645
+ .dark\:border-gray-100 {
1646
+ &:where(.dark, .dark *) {
1647
+ border-color: var(--color-gray-100);
1648
+ }
1649
+ }
1604
1650
  .dark\:border-gray-500 {
1605
1651
  &:where(.dark, .dark *) {
1606
1652
  border-color: var(--color-gray-500);
@@ -1668,6 +1714,11 @@
1668
1714
  }
1669
1715
  }
1670
1716
  }
1717
+ .dark\:text-amber-200 {
1718
+ &:where(.dark, .dark *) {
1719
+ color: var(--color-amber-200);
1720
+ }
1721
+ }
1671
1722
  .dark\:text-amber-400 {
1672
1723
  &:where(.dark, .dark *) {
1673
1724
  color: var(--color-amber-400);
@@ -1710,6 +1761,18 @@
1710
1761
  }
1711
1762
  }
1712
1763
  }
1764
+ .dark\:group-hover\:bg-blue-900\/20 {
1765
+ &:where(.dark, .dark *) {
1766
+ &:is(:where(.group):hover *) {
1767
+ @media (hover: hover) {
1768
+ background-color: color-mix(in srgb, oklch(37.9% 0.146 265.522) 20%, transparent);
1769
+ @supports (color: color-mix(in lab, red, red)) {
1770
+ background-color: color-mix(in oklab, var(--color-blue-900) 20%, transparent);
1771
+ }
1772
+ }
1773
+ }
1774
+ }
1775
+ }
1713
1776
  .dark\:hover\:border-gray-600 {
1714
1777
  &:where(.dark, .dark *) {
1715
1778
  &:hover {
@@ -1836,6 +1899,15 @@
1836
1899
  }
1837
1900
  }
1838
1901
  }
1902
+ .dark\:hover\:text-red-300 {
1903
+ &:where(.dark, .dark *) {
1904
+ &:hover {
1905
+ @media (hover: hover) {
1906
+ color: var(--color-red-300);
1907
+ }
1908
+ }
1909
+ }
1910
+ }
1839
1911
  .dark\:focus\:ring-blue-400 {
1840
1912
  &:where(.dark, .dark *) {
1841
1913
  &:focus {
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.1.4",
4
+ "version": "0.1.6",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
7
7
  "files": [
@@ -93,6 +93,7 @@
93
93
  "@powerhousedao/reactor-local": "^5.0.0-staging.30",
94
94
  "@powerhousedao/scalars": "^1.33.1-staging.5",
95
95
  "@powerhousedao/switchboard": "^5.0.0-staging.30",
96
+ "@powerhousedao/vetra": "5.0.0-staging.30",
96
97
  "@tailwindcss/cli": "^4.1.4",
97
98
  "@testing-library/react": "^16.3.0",
98
99
  "@types/node": "^22.14.1",