@dainamite/billing 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/CHANGELOG.md +658 -0
  2. package/LICENSE +21 -0
  3. package/README.md +77 -0
  4. package/dist/modules/billing/acl.js +20 -0
  5. package/dist/modules/billing/acl.js.map +7 -0
  6. package/dist/modules/billing/api/accounts/route.js +182 -0
  7. package/dist/modules/billing/api/accounts/route.js.map +7 -0
  8. package/dist/modules/billing/api/export/account/[id]/route.js +170 -0
  9. package/dist/modules/billing/api/export/account/[id]/route.js.map +7 -0
  10. package/dist/modules/billing/api/invoices/[id]/route.js +85 -0
  11. package/dist/modules/billing/api/invoices/[id]/route.js.map +7 -0
  12. package/dist/modules/billing/api/invoices/add-line/route.js +69 -0
  13. package/dist/modules/billing/api/invoices/add-line/route.js.map +7 -0
  14. package/dist/modules/billing/api/invoices/edit-line/route.js +78 -0
  15. package/dist/modules/billing/api/invoices/edit-line/route.js.map +7 -0
  16. package/dist/modules/billing/api/invoices/post/route.js +74 -0
  17. package/dist/modules/billing/api/invoices/post/route.js.map +7 -0
  18. package/dist/modules/billing/api/invoices/remove-line/route.js +69 -0
  19. package/dist/modules/billing/api/invoices/remove-line/route.js.map +7 -0
  20. package/dist/modules/billing/api/invoices/route.js +109 -0
  21. package/dist/modules/billing/api/invoices/route.js.map +7 -0
  22. package/dist/modules/billing/api/items/bulk/route.js +74 -0
  23. package/dist/modules/billing/api/items/bulk/route.js.map +7 -0
  24. package/dist/modules/billing/api/items/route.js +224 -0
  25. package/dist/modules/billing/api/items/route.js.map +7 -0
  26. package/dist/modules/billing/api/openapi.js +25 -0
  27. package/dist/modules/billing/api/openapi.js.map +7 -0
  28. package/dist/modules/billing/api/run-outcomes/route.js +122 -0
  29. package/dist/modules/billing/api/run-outcomes/route.js.map +7 -0
  30. package/dist/modules/billing/api/runs/retry-failed/route.js +70 -0
  31. package/dist/modules/billing/api/runs/retry-failed/route.js.map +7 -0
  32. package/dist/modules/billing/api/runs/route.js +143 -0
  33. package/dist/modules/billing/api/runs/route.js.map +7 -0
  34. package/dist/modules/billing/api/test-invoices/wipe/route.js +65 -0
  35. package/dist/modules/billing/api/test-invoices/wipe/route.js.map +7 -0
  36. package/dist/modules/billing/api/usage/route.js +138 -0
  37. package/dist/modules/billing/api/usage/route.js.map +7 -0
  38. package/dist/modules/billing/backend/billing/accounts/[id]/page.js +402 -0
  39. package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +7 -0
  40. package/dist/modules/billing/backend/billing/accounts/[id]/page.meta.js +25 -0
  41. package/dist/modules/billing/backend/billing/accounts/[id]/page.meta.js.map +7 -0
  42. package/dist/modules/billing/backend/billing/accounts/create/page.js +82 -0
  43. package/dist/modules/billing/backend/billing/accounts/create/page.js.map +7 -0
  44. package/dist/modules/billing/backend/billing/accounts/create/page.meta.js +25 -0
  45. package/dist/modules/billing/backend/billing/accounts/create/page.meta.js.map +7 -0
  46. package/dist/modules/billing/backend/billing/accounts/page.js +173 -0
  47. package/dist/modules/billing/backend/billing/accounts/page.js.map +7 -0
  48. package/dist/modules/billing/backend/billing/accounts/page.meta.js +18 -0
  49. package/dist/modules/billing/backend/billing/accounts/page.meta.js.map +7 -0
  50. package/dist/modules/billing/backend/billing/invoices/[id]/page.js +500 -0
  51. package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +7 -0
  52. package/dist/modules/billing/backend/billing/invoices/[id]/page.meta.js +25 -0
  53. package/dist/modules/billing/backend/billing/invoices/[id]/page.meta.js.map +7 -0
  54. package/dist/modules/billing/backend/billing/invoices/page.js +187 -0
  55. package/dist/modules/billing/backend/billing/invoices/page.js.map +7 -0
  56. package/dist/modules/billing/backend/billing/invoices/page.meta.js +18 -0
  57. package/dist/modules/billing/backend/billing/invoices/page.meta.js.map +7 -0
  58. package/dist/modules/billing/backend/billing/items/[id]/page.js +265 -0
  59. package/dist/modules/billing/backend/billing/items/[id]/page.js.map +7 -0
  60. package/dist/modules/billing/backend/billing/items/[id]/page.meta.js +25 -0
  61. package/dist/modules/billing/backend/billing/items/[id]/page.meta.js.map +7 -0
  62. package/dist/modules/billing/backend/billing/items/create/page.js +90 -0
  63. package/dist/modules/billing/backend/billing/items/create/page.js.map +7 -0
  64. package/dist/modules/billing/backend/billing/items/create/page.meta.js +25 -0
  65. package/dist/modules/billing/backend/billing/items/create/page.meta.js.map +7 -0
  66. package/dist/modules/billing/backend/billing/items/page.js +208 -0
  67. package/dist/modules/billing/backend/billing/items/page.js.map +7 -0
  68. package/dist/modules/billing/backend/billing/items/page.meta.js +18 -0
  69. package/dist/modules/billing/backend/billing/items/page.meta.js.map +7 -0
  70. package/dist/modules/billing/backend/billing/runs/[id]/page.js +293 -0
  71. package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +7 -0
  72. package/dist/modules/billing/backend/billing/runs/[id]/page.meta.js +21 -0
  73. package/dist/modules/billing/backend/billing/runs/[id]/page.meta.js.map +7 -0
  74. package/dist/modules/billing/backend/billing/runs/page.js +341 -0
  75. package/dist/modules/billing/backend/billing/runs/page.js.map +7 -0
  76. package/dist/modules/billing/backend/billing/runs/page.meta.js +18 -0
  77. package/dist/modules/billing/backend/billing/runs/page.meta.js.map +7 -0
  78. package/dist/modules/billing/backend/billing/usage/create/page.js +100 -0
  79. package/dist/modules/billing/backend/billing/usage/create/page.js.map +7 -0
  80. package/dist/modules/billing/backend/billing/usage/create/page.meta.js +25 -0
  81. package/dist/modules/billing/backend/billing/usage/create/page.meta.js.map +7 -0
  82. package/dist/modules/billing/ce.js +56 -0
  83. package/dist/modules/billing/ce.js.map +7 -0
  84. package/dist/modules/billing/cli.js +67 -0
  85. package/dist/modules/billing/cli.js.map +7 -0
  86. package/dist/modules/billing/commands/accounts.js +157 -0
  87. package/dist/modules/billing/commands/accounts.js.map +7 -0
  88. package/dist/modules/billing/commands/index.js +6 -0
  89. package/dist/modules/billing/commands/index.js.map +7 -0
  90. package/dist/modules/billing/commands/invoices.js +401 -0
  91. package/dist/modules/billing/commands/invoices.js.map +7 -0
  92. package/dist/modules/billing/commands/items.js +317 -0
  93. package/dist/modules/billing/commands/items.js.map +7 -0
  94. package/dist/modules/billing/commands/runs.js +123 -0
  95. package/dist/modules/billing/commands/runs.js.map +7 -0
  96. package/dist/modules/billing/commands/usage.js +88 -0
  97. package/dist/modules/billing/commands/usage.js.map +7 -0
  98. package/dist/modules/billing/components/AccountForm.js +420 -0
  99. package/dist/modules/billing/components/AccountForm.js.map +7 -0
  100. package/dist/modules/billing/components/DetailFields.js +37 -0
  101. package/dist/modules/billing/components/DetailFields.js.map +7 -0
  102. package/dist/modules/billing/components/ItemForm.js +440 -0
  103. package/dist/modules/billing/components/ItemForm.js.map +7 -0
  104. package/dist/modules/billing/components/LineFormDialog.js +195 -0
  105. package/dist/modules/billing/components/LineFormDialog.js.map +7 -0
  106. package/dist/modules/billing/components/UsageForm.js +262 -0
  107. package/dist/modules/billing/components/UsageForm.js.map +7 -0
  108. package/dist/modules/billing/data/entities.js +433 -0
  109. package/dist/modules/billing/data/entities.js.map +7 -0
  110. package/dist/modules/billing/data/entityIds.js +12 -0
  111. package/dist/modules/billing/data/entityIds.js.map +7 -0
  112. package/dist/modules/billing/data/validators.js +396 -0
  113. package/dist/modules/billing/data/validators.js.map +7 -0
  114. package/dist/modules/billing/di.js +6 -0
  115. package/dist/modules/billing/di.js.map +7 -0
  116. package/dist/modules/billing/events.js +35 -0
  117. package/dist/modules/billing/events.js.map +7 -0
  118. package/dist/modules/billing/i18n/en.json +336 -0
  119. package/dist/modules/billing/i18n/pl.json +336 -0
  120. package/dist/modules/billing/index.js +17 -0
  121. package/dist/modules/billing/index.js.map +7 -0
  122. package/dist/modules/billing/lib/billPeriod.js +101 -0
  123. package/dist/modules/billing/lib/billPeriod.js.map +7 -0
  124. package/dist/modules/billing/lib/billRunEngine.js +256 -0
  125. package/dist/modules/billing/lib/billRunEngine.js.map +7 -0
  126. package/dist/modules/billing/lib/billRunLabel.js +8 -0
  127. package/dist/modules/billing/lib/billRunLabel.js.map +7 -0
  128. package/dist/modules/billing/lib/idempotency.js +13 -0
  129. package/dist/modules/billing/lib/idempotency.js.map +7 -0
  130. package/dist/modules/billing/lib/invoiceStatus.js +38 -0
  131. package/dist/modules/billing/lib/invoiceStatus.js.map +7 -0
  132. package/dist/modules/billing/lib/invoiceWriter.js +153 -0
  133. package/dist/modules/billing/lib/invoiceWriter.js.map +7 -0
  134. package/dist/modules/billing/lib/itemSelector.js +77 -0
  135. package/dist/modules/billing/lib/itemSelector.js.map +7 -0
  136. package/dist/modules/billing/lib/seeds/demo.js +130 -0
  137. package/dist/modules/billing/lib/seeds/demo.js.map +7 -0
  138. package/dist/modules/billing/lib/tenantLock.js +32 -0
  139. package/dist/modules/billing/lib/tenantLock.js.map +7 -0
  140. package/dist/modules/billing/lib/usageRater.js +150 -0
  141. package/dist/modules/billing/lib/usageRater.js.map +7 -0
  142. package/dist/modules/billing/lib/usageRunner.js +82 -0
  143. package/dist/modules/billing/lib/usageRunner.js.map +7 -0
  144. package/dist/modules/billing/migrations/Migration20260519000000_billing.js +144 -0
  145. package/dist/modules/billing/migrations/Migration20260519000000_billing.js.map +7 -0
  146. package/dist/modules/billing/setup.js +190 -0
  147. package/dist/modules/billing/setup.js.map +7 -0
  148. package/dist/modules/billing/subscribers/payment-captured-to-paid.js +47 -0
  149. package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +7 -0
  150. package/dist/modules/billing/translations.js +5 -0
  151. package/dist/modules/billing/translations.js.map +7 -0
  152. package/dist/modules/billing/workers/reap-stale-bill-runs.js +31 -0
  153. package/dist/modules/billing/workers/reap-stale-bill-runs.js.map +7 -0
  154. package/package.json +148 -0
  155. package/src/modules/billing/__integration__/TC-BILL-001.spec.ts +116 -0
  156. package/src/modules/billing/__integration__/TC-BILL-002.spec.ts +169 -0
  157. package/src/modules/billing/__integration__/TC-BILL-003.spec.ts +28 -0
  158. package/src/modules/billing/__integration__/meta.ts +4 -0
  159. package/src/modules/billing/__tests__/commands/invoices.test.ts +464 -0
  160. package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +284 -0
  161. package/src/modules/billing/__tests__/lib/billPeriod.test.ts +131 -0
  162. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +561 -0
  163. package/src/modules/billing/__tests__/lib/idempotency.test.ts +81 -0
  164. package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +97 -0
  165. package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +119 -0
  166. package/src/modules/billing/__tests__/lib/itemSelector.test.ts +192 -0
  167. package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +86 -0
  168. package/src/modules/billing/__tests__/lib/tenantLock.test.ts +126 -0
  169. package/src/modules/billing/__tests__/lib/usageRater.test.ts +190 -0
  170. package/src/modules/billing/__tests__/package-isolation.test.ts +223 -0
  171. package/src/modules/billing/__tests__/setup.test.ts +107 -0
  172. package/src/modules/billing/__tests__/validators.test.ts +594 -0
  173. package/src/modules/billing/acl.ts +16 -0
  174. package/src/modules/billing/api/accounts/route.ts +198 -0
  175. package/src/modules/billing/api/export/account/[id]/route.ts +233 -0
  176. package/src/modules/billing/api/invoices/[id]/route.ts +143 -0
  177. package/src/modules/billing/api/invoices/add-line/route.ts +74 -0
  178. package/src/modules/billing/api/invoices/edit-line/route.ts +97 -0
  179. package/src/modules/billing/api/invoices/post/route.ts +97 -0
  180. package/src/modules/billing/api/invoices/remove-line/route.ts +75 -0
  181. package/src/modules/billing/api/invoices/route.ts +167 -0
  182. package/src/modules/billing/api/items/bulk/route.ts +92 -0
  183. package/src/modules/billing/api/items/route.ts +255 -0
  184. package/src/modules/billing/api/openapi.ts +33 -0
  185. package/src/modules/billing/api/run-outcomes/route.ts +149 -0
  186. package/src/modules/billing/api/runs/retry-failed/route.ts +88 -0
  187. package/src/modules/billing/api/runs/route.ts +170 -0
  188. package/src/modules/billing/api/test-invoices/wipe/route.ts +87 -0
  189. package/src/modules/billing/api/usage/route.ts +148 -0
  190. package/src/modules/billing/backend/billing/accounts/[id]/page.meta.ts +23 -0
  191. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +574 -0
  192. package/src/modules/billing/backend/billing/accounts/create/page.meta.ts +23 -0
  193. package/src/modules/billing/backend/billing/accounts/create/page.tsx +101 -0
  194. package/src/modules/billing/backend/billing/accounts/page.meta.ts +16 -0
  195. package/src/modules/billing/backend/billing/accounts/page.tsx +222 -0
  196. package/src/modules/billing/backend/billing/invoices/[id]/page.meta.ts +23 -0
  197. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +613 -0
  198. package/src/modules/billing/backend/billing/invoices/page.meta.ts +16 -0
  199. package/src/modules/billing/backend/billing/invoices/page.tsx +245 -0
  200. package/src/modules/billing/backend/billing/items/[id]/page.meta.ts +23 -0
  201. package/src/modules/billing/backend/billing/items/[id]/page.tsx +366 -0
  202. package/src/modules/billing/backend/billing/items/create/page.meta.ts +23 -0
  203. package/src/modules/billing/backend/billing/items/create/page.tsx +110 -0
  204. package/src/modules/billing/backend/billing/items/page.meta.ts +16 -0
  205. package/src/modules/billing/backend/billing/items/page.tsx +272 -0
  206. package/src/modules/billing/backend/billing/runs/[id]/page.meta.ts +19 -0
  207. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +404 -0
  208. package/src/modules/billing/backend/billing/runs/page.meta.ts +16 -0
  209. package/src/modules/billing/backend/billing/runs/page.tsx +426 -0
  210. package/src/modules/billing/backend/billing/usage/create/page.meta.ts +23 -0
  211. package/src/modules/billing/backend/billing/usage/create/page.tsx +120 -0
  212. package/src/modules/billing/ce.ts +74 -0
  213. package/src/modules/billing/cli.ts +89 -0
  214. package/src/modules/billing/commands/accounts.ts +205 -0
  215. package/src/modules/billing/commands/index.ts +13 -0
  216. package/src/modules/billing/commands/invoices.ts +542 -0
  217. package/src/modules/billing/commands/items.ts +438 -0
  218. package/src/modules/billing/commands/runs.ts +166 -0
  219. package/src/modules/billing/commands/usage.ts +118 -0
  220. package/src/modules/billing/components/AccountForm.tsx +517 -0
  221. package/src/modules/billing/components/DetailFields.tsx +60 -0
  222. package/src/modules/billing/components/ItemForm.tsx +558 -0
  223. package/src/modules/billing/components/LineFormDialog.tsx +253 -0
  224. package/src/modules/billing/components/UsageForm.tsx +326 -0
  225. package/src/modules/billing/data/entities.ts +533 -0
  226. package/src/modules/billing/data/entityIds.ts +25 -0
  227. package/src/modules/billing/data/validators.ts +562 -0
  228. package/src/modules/billing/di.ts +14 -0
  229. package/src/modules/billing/events.ts +47 -0
  230. package/src/modules/billing/i18n/en.json +336 -0
  231. package/src/modules/billing/i18n/pl.json +336 -0
  232. package/src/modules/billing/index.ts +22 -0
  233. package/src/modules/billing/lib/billPeriod.ts +181 -0
  234. package/src/modules/billing/lib/billRunEngine.ts +416 -0
  235. package/src/modules/billing/lib/billRunLabel.ts +14 -0
  236. package/src/modules/billing/lib/idempotency.ts +46 -0
  237. package/src/modules/billing/lib/invoiceStatus.ts +61 -0
  238. package/src/modules/billing/lib/invoiceWriter.ts +260 -0
  239. package/src/modules/billing/lib/itemSelector.ts +154 -0
  240. package/src/modules/billing/lib/seeds/demo.ts +199 -0
  241. package/src/modules/billing/lib/tenantLock.ts +87 -0
  242. package/src/modules/billing/lib/usageRater.ts +250 -0
  243. package/src/modules/billing/lib/usageRunner.ts +189 -0
  244. package/src/modules/billing/migrations/Migration20260519000000_billing.ts +175 -0
  245. package/src/modules/billing/setup.ts +252 -0
  246. package/src/modules/billing/subscribers/payment-captured-to-paid.ts +89 -0
  247. package/src/modules/billing/translations.ts +9 -0
  248. package/src/modules/billing/workers/reap-stale-bill-runs.ts +57 -0
@@ -0,0 +1,420 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Button } from "@open-mercato/ui/primitives/button";
5
+ import { Input } from "@open-mercato/ui/primitives/input";
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue
12
+ } from "@open-mercato/ui/primitives/select";
13
+ import { Switch } from "@open-mercato/ui/primitives/switch";
14
+ import { Kbd, KbdShortcut } from "@open-mercato/ui/primitives/kbd";
15
+ import { ComboboxInput } from "@open-mercato/ui/backend/inputs";
16
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
17
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
18
+ const EMPTY_VALUES = {
19
+ customerId: "",
20
+ name: "",
21
+ currencyCode: "EUR",
22
+ billCycle: "monthly",
23
+ billCycleAnchor: "1",
24
+ invoiceEmail: "",
25
+ invoiceLanguage: "en",
26
+ taxId: "",
27
+ invoiceAddress: {
28
+ line1: "",
29
+ line2: "",
30
+ city: "",
31
+ postal_code: "",
32
+ country: "PL"
33
+ },
34
+ nextBillDate: "",
35
+ isActive: true
36
+ };
37
+ function todayIso() {
38
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
39
+ }
40
+ function mergeInitial(initial) {
41
+ if (!initial) return { ...EMPTY_VALUES, nextBillDate: todayIso() };
42
+ return {
43
+ ...EMPTY_VALUES,
44
+ nextBillDate: EMPTY_VALUES.nextBillDate || todayIso(),
45
+ ...initial,
46
+ invoiceAddress: {
47
+ ...EMPTY_VALUES.invoiceAddress,
48
+ ...initial.invoiceAddress ?? {}
49
+ }
50
+ };
51
+ }
52
+ function AccountForm({
53
+ mode,
54
+ initial,
55
+ submitting = false,
56
+ onSubmit,
57
+ onCancel
58
+ }) {
59
+ const t = useT();
60
+ const [values, setValues] = React.useState(() => mergeInitial(initial));
61
+ const [error, setError] = React.useState(null);
62
+ const isEdit = mode === "edit";
63
+ const customerLabelCacheRef = React.useRef(/* @__PURE__ */ new Map());
64
+ const loadCustomerOptions = React.useCallback(
65
+ async (query) => {
66
+ const params = new URLSearchParams({ page: "1", pageSize: "20" });
67
+ const q = (query ?? "").trim();
68
+ if (q) params.set("search", q);
69
+ const call = await apiCall(`/api/customers/companies?${params.toString()}`);
70
+ if (!call.ok || call.result == null) return [];
71
+ const raw = call.result;
72
+ const items = Array.isArray(raw) ? raw : Array.isArray(raw.items) ? raw.items : [];
73
+ const options = [];
74
+ for (const c of items) {
75
+ const id = typeof c.id === "string" ? c.id : c.id != null ? String(c.id) : "";
76
+ if (!id) continue;
77
+ const label = String(c.display_name ?? c.displayName ?? c.name ?? id);
78
+ const emailRaw = c.primary_email ?? c.email;
79
+ const description = typeof emailRaw === "string" && emailRaw ? emailRaw : null;
80
+ const opt = { value: id, label, description };
81
+ customerLabelCacheRef.current.set(id, opt);
82
+ options.push(opt);
83
+ }
84
+ return options;
85
+ },
86
+ []
87
+ );
88
+ const resolveCustomerLabel = React.useCallback(
89
+ (id) => customerLabelCacheRef.current.get(id)?.label ?? id,
90
+ []
91
+ );
92
+ const resolveCustomerDescription = React.useCallback(
93
+ (id) => customerLabelCacheRef.current.get(id)?.description ?? null,
94
+ []
95
+ );
96
+ const initialKey = React.useMemo(
97
+ () => JSON.stringify(initial ?? null),
98
+ [initial]
99
+ );
100
+ React.useEffect(() => {
101
+ setValues(mergeInitial(initial));
102
+ }, [initialKey, initial]);
103
+ const set = React.useCallback(
104
+ (key, value) => {
105
+ setValues((prev) => ({ ...prev, [key]: value }));
106
+ },
107
+ []
108
+ );
109
+ const setAddress = React.useCallback(
110
+ (key, value) => {
111
+ setValues((prev) => ({
112
+ ...prev,
113
+ invoiceAddress: { ...prev.invoiceAddress, [key]: value }
114
+ }));
115
+ },
116
+ []
117
+ );
118
+ const handleSubmit = React.useCallback(async () => {
119
+ if (!isEdit && !values.customerId.trim()) {
120
+ setError(t("billing.accounts.form.error.customerId_required", "Please select a customer"));
121
+ return;
122
+ }
123
+ if (!values.name.trim()) {
124
+ setError(t("billing.accounts.form.error.name_required", "Name is required"));
125
+ return;
126
+ }
127
+ if (!isEdit && values.currencyCode.trim().length !== 3) {
128
+ setError(t("billing.accounts.form.error.currency_invalid", "Currency must be a 3-letter ISO code"));
129
+ return;
130
+ }
131
+ if (!values.invoiceEmail.includes("@")) {
132
+ setError(t("billing.accounts.form.error.email_invalid", "Invoice email looks invalid"));
133
+ return;
134
+ }
135
+ if (!values.invoiceAddress.line1.trim() || !values.invoiceAddress.city.trim() || !values.invoiceAddress.postal_code.trim()) {
136
+ setError(
137
+ t(
138
+ "billing.accounts.form.error.address_required",
139
+ "Invoice address (line 1 + city + postal code) is required"
140
+ )
141
+ );
142
+ return;
143
+ }
144
+ const country = values.invoiceAddress.country.trim();
145
+ if (country.length < 2 || country.length > 3) {
146
+ setError(
147
+ t(
148
+ "billing.accounts.form.error.country_invalid",
149
+ "Country must be a 2- or 3-letter ISO 3166 code"
150
+ )
151
+ );
152
+ return;
153
+ }
154
+ if (!values.nextBillDate) {
155
+ setError(t("billing.accounts.form.error.next_bill_date_required", "Next bill date is required"));
156
+ return;
157
+ }
158
+ setError(null);
159
+ await onSubmit(values);
160
+ }, [isEdit, onSubmit, t, values]);
161
+ const handleKeyDown = React.useCallback(
162
+ (event) => {
163
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
164
+ event.preventDefault();
165
+ void handleSubmit();
166
+ }
167
+ },
168
+ [handleSubmit]
169
+ );
170
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-card p-6", onKeyDown: handleKeyDown, children: [
171
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
172
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
173
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
174
+ isEdit ? t("billing.accounts.form.customer_id", "Customer ID") : t("billing.accounts.form.customer", "Customer"),
175
+ !isEdit ? /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" }) : null
176
+ ] }),
177
+ isEdit ? /* @__PURE__ */ jsx(Input, { value: values.customerId, disabled: true }) : /* @__PURE__ */ jsx(
178
+ ComboboxInput,
179
+ {
180
+ value: values.customerId,
181
+ onChange: (next) => {
182
+ setValues((prev) => {
183
+ const customerLabel = customerLabelCacheRef.current.get(next)?.label ?? "";
184
+ const shouldFillName = customerLabel && !prev.name.trim();
185
+ return {
186
+ ...prev,
187
+ customerId: next,
188
+ name: shouldFillName ? customerLabel : prev.name
189
+ };
190
+ });
191
+ },
192
+ loadSuggestions: loadCustomerOptions,
193
+ resolveLabel: resolveCustomerLabel,
194
+ resolveDescription: resolveCustomerDescription,
195
+ placeholder: t(
196
+ "billing.accounts.form.customer.placeholder",
197
+ "Search customers by name or email\u2026"
198
+ ),
199
+ allowCustomValues: false
200
+ }
201
+ ),
202
+ isEdit ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("billing.accounts.form.customer_id.locked", "Immutable \u2014 create a new account to re-link the customer.") }) : null
203
+ ] }),
204
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
205
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
206
+ t("billing.accounts.form.name", "Name"),
207
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
208
+ ] }),
209
+ /* @__PURE__ */ jsx(
210
+ Input,
211
+ {
212
+ value: values.name,
213
+ placeholder: !isEdit ? t(
214
+ "billing.accounts.form.name.placeholder",
215
+ 'Auto-filled from customer \u2014 override e.g. "Acme \u2014 production"'
216
+ ) : void 0,
217
+ onChange: (event) => set("name", event.currentTarget.value)
218
+ }
219
+ )
220
+ ] }),
221
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
222
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
223
+ t("billing.accounts.form.currency", "Currency (ISO 4217)"),
224
+ !isEdit ? /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" }) : null
225
+ ] }),
226
+ /* @__PURE__ */ jsx(
227
+ Input,
228
+ {
229
+ value: values.currencyCode,
230
+ disabled: isEdit,
231
+ maxLength: 3,
232
+ onChange: (event) => set("currencyCode", event.currentTarget.value.toUpperCase())
233
+ }
234
+ ),
235
+ isEdit ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("billing.accounts.form.currency.locked", "Immutable \u2014 frozen at account create time.") }) : null
236
+ ] }),
237
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
238
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("billing.accounts.form.bill_cycle", "Bill cycle") }),
239
+ /* @__PURE__ */ jsxs(
240
+ Select,
241
+ {
242
+ value: values.billCycle,
243
+ onValueChange: (next) => set("billCycle", next),
244
+ children: [
245
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
246
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
247
+ /* @__PURE__ */ jsx(SelectItem, { value: "weekly", children: t("billing.cycle.weekly", "Weekly") }),
248
+ /* @__PURE__ */ jsx(SelectItem, { value: "monthly", children: t("billing.cycle.monthly", "Monthly") }),
249
+ /* @__PURE__ */ jsx(SelectItem, { value: "quarterly", children: t("billing.cycle.quarterly", "Quarterly") }),
250
+ /* @__PURE__ */ jsx(SelectItem, { value: "annually", children: t("billing.cycle.annually", "Annually") })
251
+ ] })
252
+ ]
253
+ }
254
+ )
255
+ ] }),
256
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
257
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("billing.accounts.form.cycle_anchor", "Cycle anchor") }),
258
+ /* @__PURE__ */ jsx(
259
+ Input,
260
+ {
261
+ value: values.billCycleAnchor,
262
+ placeholder: values.billCycle === "weekly" ? t("billing.accounts.form.cycle_anchor.weekly_hint", "mon | tue | \u2026 | sun") : t("billing.accounts.form.cycle_anchor.month_hint", "1 \u2014 28"),
263
+ onChange: (event) => set("billCycleAnchor", event.currentTarget.value)
264
+ }
265
+ )
266
+ ] }),
267
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
268
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
269
+ t("billing.accounts.form.next_bill_date", "Next bill date"),
270
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
271
+ ] }),
272
+ /* @__PURE__ */ jsx(
273
+ Input,
274
+ {
275
+ type: "date",
276
+ value: values.nextBillDate,
277
+ onChange: (event) => set("nextBillDate", event.currentTarget.value)
278
+ }
279
+ )
280
+ ] }),
281
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
282
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
283
+ t("billing.accounts.form.invoice_email", "Invoice email"),
284
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
285
+ ] }),
286
+ /* @__PURE__ */ jsx(
287
+ Input,
288
+ {
289
+ type: "email",
290
+ value: values.invoiceEmail,
291
+ onChange: (event) => set("invoiceEmail", event.currentTarget.value)
292
+ }
293
+ )
294
+ ] }),
295
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
296
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("billing.accounts.form.invoice_language", "Invoice language (ISO 639-1)") }),
297
+ /* @__PURE__ */ jsx(
298
+ Input,
299
+ {
300
+ value: values.invoiceLanguage,
301
+ maxLength: 5,
302
+ onChange: (event) => set("invoiceLanguage", event.currentTarget.value.toLowerCase())
303
+ }
304
+ )
305
+ ] }),
306
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm col-span-2", children: [
307
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("billing.accounts.form.tax_id", "Tax ID (NIP / VATIN, optional)") }),
308
+ /* @__PURE__ */ jsx(
309
+ Input,
310
+ {
311
+ value: values.taxId,
312
+ onChange: (event) => set("taxId", event.currentTarget.value)
313
+ }
314
+ )
315
+ ] })
316
+ ] }),
317
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mt-6 mb-2", children: t("billing.accounts.form.address.title", "Invoice address") }),
318
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
319
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm col-span-2", children: [
320
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
321
+ t("billing.accounts.form.address.line1", "Line 1"),
322
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
323
+ ] }),
324
+ /* @__PURE__ */ jsx(
325
+ Input,
326
+ {
327
+ value: values.invoiceAddress.line1,
328
+ onChange: (event) => setAddress("line1", event.currentTarget.value)
329
+ }
330
+ )
331
+ ] }),
332
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm col-span-2", children: [
333
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("billing.accounts.form.address.line2", "Line 2") }),
334
+ /* @__PURE__ */ jsx(
335
+ Input,
336
+ {
337
+ value: values.invoiceAddress.line2,
338
+ onChange: (event) => setAddress("line2", event.currentTarget.value)
339
+ }
340
+ )
341
+ ] }),
342
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
343
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
344
+ t("billing.accounts.form.address.city", "City"),
345
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
346
+ ] }),
347
+ /* @__PURE__ */ jsx(
348
+ Input,
349
+ {
350
+ value: values.invoiceAddress.city,
351
+ onChange: (event) => setAddress("city", event.currentTarget.value)
352
+ }
353
+ )
354
+ ] }),
355
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
356
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
357
+ t("billing.accounts.form.address.postal_code", "Postal code"),
358
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
359
+ ] }),
360
+ /* @__PURE__ */ jsx(
361
+ Input,
362
+ {
363
+ value: values.invoiceAddress.postal_code,
364
+ onChange: (event) => setAddress("postal_code", event.currentTarget.value)
365
+ }
366
+ )
367
+ ] }),
368
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
369
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
370
+ t("billing.accounts.form.address.country", "Country (ISO 3166)"),
371
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
372
+ ] }),
373
+ /* @__PURE__ */ jsx(
374
+ Input,
375
+ {
376
+ value: values.invoiceAddress.country,
377
+ maxLength: 3,
378
+ onChange: (event) => setAddress("country", event.currentTarget.value.toUpperCase())
379
+ }
380
+ )
381
+ ] })
382
+ ] }),
383
+ isEdit ? /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-between gap-3", children: [
384
+ /* @__PURE__ */ jsxs("div", { children: [
385
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: t("billing.accounts.form.active", "Active") }),
386
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: t(
387
+ "billing.accounts.form.active.help",
388
+ "Inactive accounts stay billable until soft-deleted \u2014 toggle for visibility filtering only."
389
+ ) })
390
+ ] }),
391
+ /* @__PURE__ */ jsx(
392
+ Switch,
393
+ {
394
+ checked: values.isActive,
395
+ onCheckedChange: (next) => set("isActive", next)
396
+ }
397
+ )
398
+ ] }) : null,
399
+ error ? /* @__PURE__ */ jsx("div", { className: "mt-4 text-sm text-destructive", children: error }) : null,
400
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-between gap-3", children: [
401
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
402
+ /* @__PURE__ */ jsx(KbdShortcut, { keys: ["Ctrl/\u2318", "Enter"] }),
403
+ " ",
404
+ t("billing.accounts.form.submit_hint", "to save,"),
405
+ " ",
406
+ /* @__PURE__ */ jsx(Kbd, { children: "Esc" }),
407
+ " ",
408
+ t("billing.accounts.form.cancel_hint", "to discard")
409
+ ] }),
410
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
411
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onCancel, children: t("billing.common.cancel", "Cancel") }),
412
+ /* @__PURE__ */ jsx(Button, { type: "button", onClick: () => void handleSubmit(), disabled: submitting, children: submitting ? t("billing.accounts.form.saving", "Saving\u2026") : isEdit ? t("billing.accounts.form.save", "Save changes") : t("billing.accounts.form.create", "Create account") })
413
+ ] })
414
+ ] })
415
+ ] });
416
+ }
417
+ export {
418
+ AccountForm
419
+ };
420
+ //# sourceMappingURL=AccountForm.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/billing/components/AccountForm.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Kbd, KbdShortcut } from '@open-mercato/ui/primitives/kbd'\nimport { ComboboxInput, type ComboboxOption } from '@open-mercato/ui/backend/inputs'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\n/**\n * Shared form for Billing Account create + edit.\n *\n * Hand-rolled (not CrudForm) on purpose \u2014 the form's `invoiceAddress`\n * jsonb sub-object would need a custom-field renderer in the\n * CrudForm DSL, which is heavier than just rendering five inputs\n * inline. The trade-off is documented in `xd-249-billing-spec.md`\n * Phase 4d notes.\n *\n * UX per the OM UI AGENTS.md:\n * - `Cmd/Ctrl + Enter` submits.\n * - Buttons use the `Button` / `Switch` primitives, never raw HTML.\n * - Footer hint row uses `Kbd` / `KbdShortcut`.\n *\n * `mode='create'` shows the always-required fields. `mode='edit'`\n * disables `customerId` and `currencyCode` per the spec\n * (\"immutable per spec \u2014 create a new account if the customer FK\n * changes\" \u2014 see `billingAccountUpdateSchema`).\n */\n\nexport type AccountFormValues = {\n customerId: string\n name: string\n currencyCode: string\n billCycle: 'weekly' | 'monthly' | 'quarterly' | 'annually'\n billCycleAnchor: string\n invoiceEmail: string\n invoiceLanguage: string\n taxId: string\n invoiceAddress: {\n line1: string\n line2: string\n city: string\n postal_code: string\n country: string\n }\n nextBillDate: string\n isActive: boolean\n}\n\nexport type AccountFormProps = {\n mode: 'create' | 'edit'\n initial?: Partial<AccountFormValues>\n submitting?: boolean\n onSubmit: (values: AccountFormValues) => Promise<void> | void\n onCancel: () => void\n}\n\nconst EMPTY_VALUES: AccountFormValues = {\n customerId: '',\n name: '',\n currencyCode: 'EUR',\n billCycle: 'monthly',\n billCycleAnchor: '1',\n invoiceEmail: '',\n invoiceLanguage: 'en',\n taxId: '',\n invoiceAddress: {\n line1: '',\n line2: '',\n city: '',\n postal_code: '',\n country: 'PL',\n },\n nextBillDate: '',\n isActive: true,\n}\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nfunction mergeInitial(initial: Partial<AccountFormValues> | undefined): AccountFormValues {\n if (!initial) return { ...EMPTY_VALUES, nextBillDate: todayIso() }\n return {\n ...EMPTY_VALUES,\n nextBillDate: EMPTY_VALUES.nextBillDate || todayIso(),\n ...initial,\n invoiceAddress: {\n ...EMPTY_VALUES.invoiceAddress,\n ...(initial.invoiceAddress ?? {}),\n },\n }\n}\n\nexport function AccountForm({\n mode,\n initial,\n submitting = false,\n onSubmit,\n onCancel,\n}: AccountFormProps) {\n const t = useT()\n const [values, setValues] = React.useState<AccountFormValues>(() => mergeInitial(initial))\n const [error, setError] = React.useState<string | null>(null)\n const isEdit = mode === 'edit'\n\n // Label cache for the customer combobox \u2014 populated as the user searches,\n // so the picker can render \"Acme Telecom\" after selection instead of the\n // raw UUID we send to the API.\n const customerLabelCacheRef = React.useRef<Map<string, ComboboxOption>>(new Map())\n\n const loadCustomerOptions = React.useCallback(\n async (query?: string): Promise<ComboboxOption[]> => {\n const params = new URLSearchParams({ page: '1', pageSize: '20' })\n const q = (query ?? '').trim()\n if (q) params.set('search', q)\n const call = await apiCall<unknown>(`/api/customers/companies?${params.toString()}`)\n if (!call.ok || call.result == null) return []\n const raw = call.result as { items?: unknown } | unknown[]\n const items: Array<Record<string, unknown>> = Array.isArray(raw)\n ? (raw as Array<Record<string, unknown>>)\n : Array.isArray((raw as { items?: unknown }).items)\n ? ((raw as { items: unknown[] }).items as Array<Record<string, unknown>>)\n : []\n const options: ComboboxOption[] = []\n for (const c of items) {\n const id = typeof c.id === 'string' ? c.id : c.id != null ? String(c.id) : ''\n if (!id) continue\n const label = String(c.display_name ?? c.displayName ?? c.name ?? id)\n const emailRaw = c.primary_email ?? c.email\n const description = typeof emailRaw === 'string' && emailRaw ? emailRaw : null\n const opt: ComboboxOption = { value: id, label, description }\n customerLabelCacheRef.current.set(id, opt)\n options.push(opt)\n }\n return options\n },\n [],\n )\n\n const resolveCustomerLabel = React.useCallback(\n (id: string) => customerLabelCacheRef.current.get(id)?.label ?? id,\n [],\n )\n const resolveCustomerDescription = React.useCallback(\n (id: string) => customerLabelCacheRef.current.get(id)?.description ?? null,\n [],\n )\n\n // Reset when the parent swaps `initial` (e.g. after loading the edit row).\n const initialKey = React.useMemo(\n () => JSON.stringify(initial ?? null),\n [initial],\n )\n React.useEffect(() => {\n setValues(mergeInitial(initial))\n }, [initialKey, initial])\n\n const set = React.useCallback(\n <K extends keyof AccountFormValues>(key: K, value: AccountFormValues[K]) => {\n setValues((prev) => ({ ...prev, [key]: value }))\n },\n [],\n )\n\n const setAddress = React.useCallback(\n <K extends keyof AccountFormValues['invoiceAddress']>(\n key: K,\n value: AccountFormValues['invoiceAddress'][K],\n ) => {\n setValues((prev) => ({\n ...prev,\n invoiceAddress: { ...prev.invoiceAddress, [key]: value },\n }))\n },\n [],\n )\n\n const handleSubmit = React.useCallback(async () => {\n if (!isEdit && !values.customerId.trim()) {\n setError(t('billing.accounts.form.error.customerId_required', 'Please select a customer'))\n return\n }\n if (!values.name.trim()) {\n setError(t('billing.accounts.form.error.name_required', 'Name is required'))\n return\n }\n if (!isEdit && values.currencyCode.trim().length !== 3) {\n setError(t('billing.accounts.form.error.currency_invalid', 'Currency must be a 3-letter ISO code'))\n return\n }\n if (!values.invoiceEmail.includes('@')) {\n setError(t('billing.accounts.form.error.email_invalid', 'Invoice email looks invalid'))\n return\n }\n if (\n !values.invoiceAddress.line1.trim() ||\n !values.invoiceAddress.city.trim() ||\n !values.invoiceAddress.postal_code.trim()\n ) {\n setError(\n t(\n 'billing.accounts.form.error.address_required',\n 'Invoice address (line 1 + city + postal code) is required',\n ),\n )\n return\n }\n const country = values.invoiceAddress.country.trim()\n if (country.length < 2 || country.length > 3) {\n setError(\n t(\n 'billing.accounts.form.error.country_invalid',\n 'Country must be a 2- or 3-letter ISO 3166 code',\n ),\n )\n return\n }\n if (!values.nextBillDate) {\n setError(t('billing.accounts.form.error.next_bill_date_required', 'Next bill date is required'))\n return\n }\n setError(null)\n await onSubmit(values)\n }, [isEdit, onSubmit, t, values])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"rounded-lg border border-border bg-card p-6\" onKeyDown={handleKeyDown}>\n <div className=\"grid grid-cols-2 gap-4\">\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {isEdit\n ? t('billing.accounts.form.customer_id', 'Customer ID')\n : t('billing.accounts.form.customer', 'Customer')}\n {!isEdit ? <span className=\"text-destructive\"> *</span> : null}\n </span>\n {isEdit ? (\n <Input value={values.customerId} disabled />\n ) : (\n <ComboboxInput\n value={values.customerId}\n onChange={(next) => {\n setValues((prev) => {\n const customerLabel = customerLabelCacheRef.current.get(next)?.label ?? ''\n // Auto-fill Name with the customer's display name on the first\n // pick. We leave it alone if the operator has already typed\n // something \u2014 one customer can have multiple billing accounts\n // (e.g. \"Acme \u2014 production\" / \"Acme \u2014 staging\").\n const shouldFillName = customerLabel && !prev.name.trim()\n return {\n ...prev,\n customerId: next,\n name: shouldFillName ? customerLabel : prev.name,\n }\n })\n }}\n loadSuggestions={loadCustomerOptions}\n resolveLabel={resolveCustomerLabel}\n resolveDescription={resolveCustomerDescription}\n placeholder={t(\n 'billing.accounts.form.customer.placeholder',\n 'Search customers by name or email\u2026',\n )}\n allowCustomValues={false}\n />\n )}\n {isEdit ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('billing.accounts.form.customer_id.locked', 'Immutable \u2014 create a new account to re-link the customer.')}\n </span>\n ) : null}\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.name', 'Name')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n value={values.name}\n placeholder={\n !isEdit\n ? t(\n 'billing.accounts.form.name.placeholder',\n 'Auto-filled from customer \u2014 override e.g. \"Acme \u2014 production\"',\n )\n : undefined\n }\n onChange={(event) => set('name', event.currentTarget.value)}\n />\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.currency', 'Currency (ISO 4217)')}\n {!isEdit ? <span className=\"text-destructive\"> *</span> : null}\n </span>\n <Input\n value={values.currencyCode}\n disabled={isEdit}\n maxLength={3}\n onChange={(event) =>\n set('currencyCode', event.currentTarget.value.toUpperCase())\n }\n />\n {isEdit ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('billing.accounts.form.currency.locked', 'Immutable \u2014 frozen at account create time.')}\n </span>\n ) : null}\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.bill_cycle', 'Bill cycle')}\n </span>\n <Select\n value={values.billCycle}\n onValueChange={(next) =>\n set('billCycle', next as AccountFormValues['billCycle'])\n }\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"weekly\">{t('billing.cycle.weekly', 'Weekly')}</SelectItem>\n <SelectItem value=\"monthly\">{t('billing.cycle.monthly', 'Monthly')}</SelectItem>\n <SelectItem value=\"quarterly\">{t('billing.cycle.quarterly', 'Quarterly')}</SelectItem>\n <SelectItem value=\"annually\">{t('billing.cycle.annually', 'Annually')}</SelectItem>\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.cycle_anchor', 'Cycle anchor')}\n </span>\n <Input\n value={values.billCycleAnchor}\n placeholder={\n values.billCycle === 'weekly'\n ? t('billing.accounts.form.cycle_anchor.weekly_hint', 'mon | tue | \u2026 | sun')\n : t('billing.accounts.form.cycle_anchor.month_hint', '1 \u2014 28')\n }\n onChange={(event) => set('billCycleAnchor', event.currentTarget.value)}\n />\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.next_bill_date', 'Next bill date')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n type=\"date\"\n value={values.nextBillDate}\n onChange={(event) => set('nextBillDate', event.currentTarget.value)}\n />\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.invoice_email', 'Invoice email')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n type=\"email\"\n value={values.invoiceEmail}\n onChange={(event) => set('invoiceEmail', event.currentTarget.value)}\n />\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.invoice_language', 'Invoice language (ISO 639-1)')}\n </span>\n <Input\n value={values.invoiceLanguage}\n maxLength={5}\n onChange={(event) =>\n set('invoiceLanguage', event.currentTarget.value.toLowerCase())\n }\n />\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm col-span-2\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.tax_id', 'Tax ID (NIP / VATIN, optional)')}\n </span>\n <Input\n value={values.taxId}\n onChange={(event) => set('taxId', event.currentTarget.value)}\n />\n </label>\n </div>\n\n <h3 className=\"text-sm font-semibold mt-6 mb-2\">\n {t('billing.accounts.form.address.title', 'Invoice address')}\n </h3>\n <div className=\"grid grid-cols-2 gap-4\">\n <label className=\"flex flex-col gap-1 text-sm col-span-2\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.address.line1', 'Line 1')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n value={values.invoiceAddress.line1}\n onChange={(event) => setAddress('line1', event.currentTarget.value)}\n />\n </label>\n <label className=\"flex flex-col gap-1 text-sm col-span-2\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.address.line2', 'Line 2')}\n </span>\n <Input\n value={values.invoiceAddress.line2}\n onChange={(event) => setAddress('line2', event.currentTarget.value)}\n />\n </label>\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.address.city', 'City')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n value={values.invoiceAddress.city}\n onChange={(event) => setAddress('city', event.currentTarget.value)}\n />\n </label>\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.address.postal_code', 'Postal code')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n value={values.invoiceAddress.postal_code}\n onChange={(event) => setAddress('postal_code', event.currentTarget.value)}\n />\n </label>\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.accounts.form.address.country', 'Country (ISO 3166)')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n value={values.invoiceAddress.country}\n maxLength={3}\n onChange={(event) =>\n setAddress('country', event.currentTarget.value.toUpperCase())\n }\n />\n </label>\n </div>\n\n {isEdit ? (\n <div className=\"mt-6 flex items-center justify-between gap-3\">\n <div>\n <div className=\"text-sm font-medium\">\n {t('billing.accounts.form.active', 'Active')}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.accounts.form.active.help',\n 'Inactive accounts stay billable until soft-deleted \u2014 toggle for visibility filtering only.',\n )}\n </div>\n </div>\n <Switch\n checked={values.isActive}\n onCheckedChange={(next) => set('isActive', next)}\n />\n </div>\n ) : null}\n\n {error ? <div className=\"mt-4 text-sm text-destructive\">{error}</div> : null}\n\n <div className=\"mt-6 flex items-center justify-between gap-3\">\n <span className=\"text-xs text-muted-foreground\">\n <KbdShortcut keys={['Ctrl/\u2318', 'Enter']} />{' '}\n {t('billing.accounts.form.submit_hint', 'to save,')} <Kbd>Esc</Kbd>{' '}\n {t('billing.accounts.form.cancel_hint', 'to discard')}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onCancel}>\n {t('billing.common.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={() => void handleSubmit()} disabled={submitting}>\n {submitting\n ? t('billing.accounts.form.saving', 'Saving\u2026')\n : isEdit\n ? t('billing.accounts.form.save', 'Save changes')\n : t('billing.accounts.form.create', 'Create account')}\n </Button>\n </div>\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ function DetailCard({ children }) {
4
+ return /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6", children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children }) });
5
+ }
6
+ function DetailField({
7
+ label,
8
+ children,
9
+ fullWidth = false
10
+ }) {
11
+ return /* @__PURE__ */ jsxs("div", { className: fullWidth ? "sm:col-span-2" : void 0, children: [
12
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: label }),
13
+ /* @__PURE__ */ jsx("div", { className: "text-sm py-1", children })
14
+ ] });
15
+ }
16
+ function StatCard({
17
+ title,
18
+ children
19
+ }) {
20
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
21
+ title ? /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-3", children: title }) : null,
22
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-x-10 gap-y-4", children })
23
+ ] });
24
+ }
25
+ function Stat({ label, value }) {
26
+ return /* @__PURE__ */ jsxs("div", { children: [
27
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: label }),
28
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold", children: value })
29
+ ] });
30
+ }
31
+ export {
32
+ DetailCard,
33
+ DetailField,
34
+ Stat,
35
+ StatCard
36
+ };
37
+ //# sourceMappingURL=DetailFields.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/billing/components/DetailFields.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\n\n/**\n * OM/CPQ-standard read-only detail rendering.\n *\n * One bordered card holding a 2-col grid of label-above-value blocks \u2014\n * mirrors the CPQ offering detail \"General\" section so billing detail\n * pages match the rest of the admin shell.\n */\n\nexport function DetailCard({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"rounded-lg border bg-card p-6\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">{children}</div>\n </div>\n )\n}\n\nexport function DetailField({\n label,\n children,\n fullWidth = false,\n}: {\n label: string\n children: React.ReactNode\n fullWidth?: boolean\n}) {\n return (\n <div className={fullWidth ? 'sm:col-span-2' : undefined}>\n <label className=\"block text-sm font-medium mb-1\">{label}</label>\n <div className=\"text-sm py-1\">{children}</div>\n </div>\n )\n}\n\n/** Compact stat block \u2014 card with an optional heading + metric tiles. */\nexport function StatCard({\n title,\n children,\n}: {\n title?: string\n children: React.ReactNode\n}) {\n return (\n <div className=\"rounded-lg border bg-card p-4\">\n {title ? <h3 className=\"text-sm font-medium mb-3\">{title}</h3> : null}\n <div className=\"flex flex-wrap gap-x-10 gap-y-4\">{children}</div>\n </div>\n )\n}\n\nexport function Stat({ label, value }: { label: string; value: React.ReactNode }) {\n return (\n <div>\n <p className=\"text-xs text-muted-foreground\">{label}</p>\n <p className=\"text-lg font-semibold\">{value}</p>\n </div>\n )\n}\n"],
5
+ "mappings": ";AAcM,cAeF,YAfE;AAHC,SAAS,WAAW,EAAE,SAAS,GAAkC;AACtE,SACE,oBAAC,SAAI,WAAU,iCACb,8BAAC,SAAI,WAAU,yCAAyC,UAAS,GACnE;AAEJ;AAEO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA,YAAY;AACd,GAIG;AACD,SACE,qBAAC,SAAI,WAAW,YAAY,kBAAkB,QAC5C;AAAA,wBAAC,WAAM,WAAU,kCAAkC,iBAAM;AAAA,IACzD,oBAAC,SAAI,WAAU,gBAAgB,UAAS;AAAA,KAC1C;AAEJ;AAGO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AACF,GAGG;AACD,SACE,qBAAC,SAAI,WAAU,iCACZ;AAAA,YAAQ,oBAAC,QAAG,WAAU,4BAA4B,iBAAM,IAAQ;AAAA,IACjE,oBAAC,SAAI,WAAU,mCAAmC,UAAS;AAAA,KAC7D;AAEJ;AAEO,SAAS,KAAK,EAAE,OAAO,MAAM,GAA8C;AAChF,SACE,qBAAC,SACC;AAAA,wBAAC,OAAE,WAAU,iCAAiC,iBAAM;AAAA,IACpD,oBAAC,OAAE,WAAU,yBAAyB,iBAAM;AAAA,KAC9C;AAEJ;",
6
+ "names": []
7
+ }