@spaceinvoices/react-ui 0.4.8 → 0.4.11

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 (276) hide show
  1. package/README.md +24 -8
  2. package/cli/dist/index.js +89 -26
  3. package/package.json +4 -1
  4. package/spaceinvoices.schema.json +6 -1
  5. package/src/common/autocomplete.tsx +69 -6
  6. package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +124 -285
  7. package/src/components/advance-invoices/list/list-table.tsx +10 -3
  8. package/src/components/advance-invoices/list/locales/de.ts +2 -0
  9. package/src/components/advance-invoices/list/locales/en.ts +1 -0
  10. package/src/components/advance-invoices/list/locales/es.ts +1 -0
  11. package/src/components/advance-invoices/list/locales/fr.ts +1 -0
  12. package/src/components/advance-invoices/list/locales/hr.ts +1 -0
  13. package/src/components/advance-invoices/list/locales/it.ts +1 -0
  14. package/src/components/advance-invoices/list/locales/nl.ts +1 -0
  15. package/src/components/advance-invoices/list/locales/pl.ts +1 -0
  16. package/src/components/advance-invoices/list/locales/pt.ts +1 -0
  17. package/src/components/advance-invoices/list/locales/sl.ts +1 -0
  18. package/src/components/advance-invoices/list/use-advance-invoice-download.ts +1 -12
  19. package/src/components/credit-notes/create/create-credit-note-form.tsx +116 -238
  20. package/src/components/credit-notes/list/list-table.tsx +6 -3
  21. package/src/components/credit-notes/list/use-credit-note-download.ts +1 -12
  22. package/src/components/customers/customer-autocomplete.tsx +64 -11
  23. package/src/components/customers/customer-list-table/customer-list-table.tsx +3 -2
  24. package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +9 -1
  25. package/src/components/dashboard/collection-rate-card/locales/bg.ts +3 -0
  26. package/src/components/dashboard/collection-rate-card/locales/cs.ts +3 -0
  27. package/src/components/dashboard/collection-rate-card/locales/et.ts +3 -0
  28. package/src/components/dashboard/collection-rate-card/locales/fi.ts +3 -0
  29. package/src/components/dashboard/collection-rate-card/locales/is.ts +3 -0
  30. package/src/components/dashboard/collection-rate-card/locales/nb.ts +3 -0
  31. package/src/components/dashboard/collection-rate-card/locales/sk.ts +3 -0
  32. package/src/components/dashboard/collection-rate-card/locales/sv.ts +3 -0
  33. package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +10 -2
  34. package/src/components/dashboard/invoice-status-chart/locales/bg.ts +10 -0
  35. package/src/components/dashboard/invoice-status-chart/locales/cs.ts +10 -0
  36. package/src/components/dashboard/invoice-status-chart/locales/de.ts +1 -0
  37. package/src/components/dashboard/invoice-status-chart/locales/es.ts +1 -0
  38. package/src/components/dashboard/invoice-status-chart/locales/et.ts +10 -0
  39. package/src/components/dashboard/invoice-status-chart/locales/fi.ts +10 -0
  40. package/src/components/dashboard/invoice-status-chart/locales/fr.ts +1 -0
  41. package/src/components/dashboard/invoice-status-chart/locales/hr.ts +1 -0
  42. package/src/components/dashboard/invoice-status-chart/locales/is.ts +10 -0
  43. package/src/components/dashboard/invoice-status-chart/locales/it.ts +1 -0
  44. package/src/components/dashboard/invoice-status-chart/locales/nb.ts +10 -0
  45. package/src/components/dashboard/invoice-status-chart/locales/nl.ts +1 -0
  46. package/src/components/dashboard/invoice-status-chart/locales/pl.ts +1 -0
  47. package/src/components/dashboard/invoice-status-chart/locales/pt.ts +1 -0
  48. package/src/components/dashboard/invoice-status-chart/locales/sk.ts +10 -0
  49. package/src/components/dashboard/invoice-status-chart/locales/sl.ts +1 -0
  50. package/src/components/dashboard/invoice-status-chart/locales/sv.ts +10 -0
  51. package/src/components/dashboard/payment-methods-chart/locales/bg.ts +12 -0
  52. package/src/components/dashboard/payment-methods-chart/locales/cs.ts +12 -0
  53. package/src/components/dashboard/payment-methods-chart/locales/et.ts +12 -0
  54. package/src/components/dashboard/payment-methods-chart/locales/fi.ts +12 -0
  55. package/src/components/dashboard/payment-methods-chart/locales/is.ts +12 -0
  56. package/src/components/dashboard/payment-methods-chart/locales/nb.ts +12 -0
  57. package/src/components/dashboard/payment-methods-chart/locales/sk.ts +12 -0
  58. package/src/components/dashboard/payment-methods-chart/locales/sv.ts +12 -0
  59. package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +9 -1
  60. package/src/components/dashboard/payment-trend-chart/locales/bg.ts +6 -0
  61. package/src/components/dashboard/payment-trend-chart/locales/cs.ts +6 -0
  62. package/src/components/dashboard/payment-trend-chart/locales/de.ts +1 -0
  63. package/src/components/dashboard/payment-trend-chart/locales/es.ts +1 -0
  64. package/src/components/dashboard/payment-trend-chart/locales/et.ts +6 -0
  65. package/src/components/dashboard/payment-trend-chart/locales/fi.ts +6 -0
  66. package/src/components/dashboard/payment-trend-chart/locales/fr.ts +1 -0
  67. package/src/components/dashboard/payment-trend-chart/locales/hr.ts +1 -0
  68. package/src/components/dashboard/payment-trend-chart/locales/is.ts +6 -0
  69. package/src/components/dashboard/payment-trend-chart/locales/it.ts +1 -0
  70. package/src/components/dashboard/payment-trend-chart/locales/nb.ts +6 -0
  71. package/src/components/dashboard/payment-trend-chart/locales/nl.ts +1 -0
  72. package/src/components/dashboard/payment-trend-chart/locales/pl.ts +1 -0
  73. package/src/components/dashboard/payment-trend-chart/locales/pt.ts +1 -0
  74. package/src/components/dashboard/payment-trend-chart/locales/sk.ts +6 -0
  75. package/src/components/dashboard/payment-trend-chart/locales/sl.ts +1 -0
  76. package/src/components/dashboard/payment-trend-chart/locales/sv.ts +6 -0
  77. package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +15 -8
  78. package/src/components/dashboard/revenue-trend-chart/locales/bg.ts +6 -0
  79. package/src/components/dashboard/revenue-trend-chart/locales/cs.ts +6 -0
  80. package/src/components/dashboard/revenue-trend-chart/locales/de.ts +1 -0
  81. package/src/components/dashboard/revenue-trend-chart/locales/es.ts +1 -0
  82. package/src/components/dashboard/revenue-trend-chart/locales/et.ts +6 -0
  83. package/src/components/dashboard/revenue-trend-chart/locales/fi.ts +6 -0
  84. package/src/components/dashboard/revenue-trend-chart/locales/fr.ts +1 -0
  85. package/src/components/dashboard/revenue-trend-chart/locales/hr.ts +1 -0
  86. package/src/components/dashboard/revenue-trend-chart/locales/is.ts +6 -0
  87. package/src/components/dashboard/revenue-trend-chart/locales/it.ts +1 -0
  88. package/src/components/dashboard/revenue-trend-chart/locales/nb.ts +6 -0
  89. package/src/components/dashboard/revenue-trend-chart/locales/nl.ts +1 -0
  90. package/src/components/dashboard/revenue-trend-chart/locales/pl.ts +1 -0
  91. package/src/components/dashboard/revenue-trend-chart/locales/pt.ts +1 -0
  92. package/src/components/dashboard/revenue-trend-chart/locales/sk.ts +6 -0
  93. package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +1 -0
  94. package/src/components/dashboard/revenue-trend-chart/locales/sv.ts +6 -0
  95. package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +15 -8
  96. package/src/components/dashboard/tax-collected-card/locales.ts +110 -0
  97. package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +8 -2
  98. package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +4 -4
  99. package/src/components/dashboard/top-customers-chart/locales/bg.ts +7 -0
  100. package/src/components/dashboard/top-customers-chart/locales/cs.ts +7 -0
  101. package/src/components/dashboard/top-customers-chart/locales/de.ts +2 -0
  102. package/src/components/dashboard/top-customers-chart/locales/es.ts +2 -0
  103. package/src/components/dashboard/top-customers-chart/locales/et.ts +7 -0
  104. package/src/components/dashboard/top-customers-chart/locales/fi.ts +7 -0
  105. package/src/components/dashboard/top-customers-chart/locales/fr.ts +2 -0
  106. package/src/components/dashboard/top-customers-chart/locales/hr.ts +2 -0
  107. package/src/components/dashboard/top-customers-chart/locales/is.ts +7 -0
  108. package/src/components/dashboard/top-customers-chart/locales/it.ts +2 -0
  109. package/src/components/dashboard/top-customers-chart/locales/nb.ts +7 -0
  110. package/src/components/dashboard/top-customers-chart/locales/nl.ts +2 -0
  111. package/src/components/dashboard/top-customers-chart/locales/pl.ts +2 -0
  112. package/src/components/dashboard/top-customers-chart/locales/pt.ts +2 -0
  113. package/src/components/dashboard/top-customers-chart/locales/sk.ts +7 -0
  114. package/src/components/dashboard/top-customers-chart/locales/sl.ts +2 -0
  115. package/src/components/dashboard/top-customers-chart/locales/sv.ts +7 -0
  116. package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +23 -12
  117. package/src/components/delivery-notes/create/create-delivery-note-form.tsx +33 -20
  118. package/src/components/delivery-notes/list/list-table.tsx +22 -13
  119. package/src/components/delivery-notes/list/locales/de.ts +2 -0
  120. package/src/components/delivery-notes/list/locales/en.ts +1 -0
  121. package/src/components/delivery-notes/list/locales/es.ts +1 -0
  122. package/src/components/delivery-notes/list/locales/fr.ts +1 -0
  123. package/src/components/delivery-notes/list/locales/hr.ts +1 -0
  124. package/src/components/delivery-notes/list/locales/it.ts +1 -0
  125. package/src/components/delivery-notes/list/locales/nl.ts +1 -0
  126. package/src/components/delivery-notes/list/locales/pl.ts +1 -0
  127. package/src/components/delivery-notes/list/locales/pt.ts +1 -0
  128. package/src/components/delivery-notes/list/locales/sl.ts +1 -0
  129. package/src/components/delivery-notes/list/use-delivery-note-download.ts +1 -12
  130. package/src/components/documents/create/document-add-item-form.tsx +28 -16
  131. package/src/components/documents/create/document-add-item-tax-rate-field.tsx +12 -2
  132. package/src/components/documents/create/document-items-section.tsx +70 -39
  133. package/src/components/documents/create/document-recipient-section.tsx +10 -1
  134. package/src/components/documents/create/live-preview.tsx +113 -15
  135. package/src/components/documents/create/prepare-document-submission.ts +35 -16
  136. package/src/components/documents/create/use-document-customer-form.ts +14 -3
  137. package/src/components/documents/documents.hooks.ts +7 -2
  138. package/src/components/documents/shared/document-preview-display.tsx +136 -67
  139. package/src/components/documents/shared/scaled-document-preview.tsx +45 -5
  140. package/src/components/documents/view/document-actions-bar.tsx +284 -182
  141. package/src/components/documents/view/document-activities-list.tsx +3 -0
  142. package/src/components/documents/view/document-payments-list.tsx +3 -0
  143. package/src/components/documents/view/locales/de.ts +8 -0
  144. package/src/components/documents/view/locales/es.ts +8 -0
  145. package/src/components/documents/view/locales/fr.ts +8 -0
  146. package/src/components/documents/view/locales/hr.ts +8 -0
  147. package/src/components/documents/view/locales/it.ts +8 -0
  148. package/src/components/documents/view/locales/nl.ts +8 -0
  149. package/src/components/documents/view/locales/pl.ts +8 -0
  150. package/src/components/documents/view/locales/pt.ts +8 -0
  151. package/src/components/documents/view/locales/sl.ts +8 -0
  152. package/src/components/documents/view/use-document-download.ts +14 -25
  153. package/src/components/entities/create-entity-form.tsx +101 -16
  154. package/src/components/entities/entity-settings-form/entity-settings-form.tsx +10 -10
  155. package/src/components/entities/entity-settings-form/locales/de.ts +10 -0
  156. package/src/components/entities/entity-settings-form/locales/es.ts +10 -0
  157. package/src/components/entities/entity-settings-form/locales/fr.ts +10 -0
  158. package/src/components/entities/entity-settings-form/locales/hr.ts +10 -0
  159. package/src/components/entities/entity-settings-form/locales/it.ts +10 -0
  160. package/src/components/entities/entity-settings-form/locales/nl.ts +10 -0
  161. package/src/components/entities/entity-settings-form/locales/pl.ts +10 -0
  162. package/src/components/entities/entity-settings-form/locales/pt.ts +10 -0
  163. package/src/components/entities/entity-settings-form/locales/sl.ts +10 -0
  164. package/src/components/entities/fina-settings-form/fina-operator-required-dialog.tsx +3 -3
  165. package/src/components/entities/fina-settings-form/fina-settings-form.tsx +78 -124
  166. package/src/components/entities/fina-settings-form/sections/certificate-settings-section.tsx +8 -1
  167. package/src/components/entities/fina-settings-form/sections/premises-management-section.tsx +14 -2
  168. package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +7 -2
  169. package/src/components/entities/furs-settings-form/furs-settings-form.tsx +56 -130
  170. package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +8 -1
  171. package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +1 -0
  172. package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +15 -2
  173. package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +20 -3
  174. package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +38 -12
  175. package/src/components/entities/settings/defaults-settings-form.tsx +6 -6
  176. package/src/components/entities/settings/eslog-settings-form.tsx +13 -1
  177. package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +3 -22
  178. package/src/components/entities/shared/fiscalization-step-flow.ts +77 -0
  179. package/src/components/entities/shared/fiscalization-step-tabs.tsx +71 -0
  180. package/src/components/estimates/create/create-estimate-form.tsx +34 -21
  181. package/src/components/estimates/list/list-table.tsx +23 -14
  182. package/src/components/estimates/list/locales/de.ts +2 -0
  183. package/src/components/estimates/list/locales/en.ts +1 -0
  184. package/src/components/estimates/list/locales/es.ts +1 -0
  185. package/src/components/estimates/list/locales/fr.ts +1 -0
  186. package/src/components/estimates/list/locales/hr.ts +1 -0
  187. package/src/components/estimates/list/locales/it.ts +1 -0
  188. package/src/components/estimates/list/locales/nl.ts +1 -0
  189. package/src/components/estimates/list/locales/pl.ts +1 -0
  190. package/src/components/estimates/list/locales/pt.ts +1 -0
  191. package/src/components/estimates/list/locales/sl.ts +1 -0
  192. package/src/components/estimates/list/use-estimate-download.ts +1 -12
  193. package/src/components/export/document-export-form.tsx +33 -7
  194. package/src/components/export/sales-per-item-export-form.tsx +23 -7
  195. package/src/components/invoices/create/create-invoice-form.tsx +295 -329
  196. package/src/components/invoices/create/prepare-invoice-submission.ts +0 -8
  197. package/src/components/invoices/list/list-table.tsx +7 -4
  198. package/src/components/invoices/list/use-invoice-download.ts +1 -11
  199. package/src/components/invoices/send-email-dialog/locales/de.ts +20 -0
  200. package/src/components/invoices/send-email-dialog/locales/es.ts +20 -0
  201. package/src/components/invoices/send-email-dialog/locales/fr.ts +20 -0
  202. package/src/components/invoices/send-email-dialog/locales/hr.ts +20 -0
  203. package/src/components/invoices/send-email-dialog/locales/it.ts +20 -0
  204. package/src/components/invoices/send-email-dialog/locales/nl.ts +20 -0
  205. package/src/components/invoices/send-email-dialog/locales/pl.ts +20 -0
  206. package/src/components/invoices/send-email-dialog/locales/pt.ts +20 -0
  207. package/src/components/invoices/send-email-dialog/locales/sl.ts +20 -0
  208. package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +77 -8
  209. package/src/components/invoices/view/eslog-info-display.tsx +17 -1
  210. package/src/components/invoices/view/fiscalization-status-card.tsx +7 -3
  211. package/src/components/items/item-combobox.tsx +26 -6
  212. package/src/components/items/item-list-table/item-list-table.tsx +5 -2
  213. package/src/components/payments/create-payment-form/index.ts +1 -0
  214. package/src/components/payments/list/list-table.tsx +14 -4
  215. package/src/components/recurring-invoices/list/list-table.tsx +7 -4
  216. package/src/components/request-logs/locales.ts +412 -0
  217. package/src/components/request-logs/request-log-detail.tsx +37 -21
  218. package/src/components/request-logs/request-log-list-table.tsx +57 -11
  219. package/src/components/table/data-table.tsx +5 -2
  220. package/src/components/table/date-cell.tsx +3 -1
  221. package/src/components/table/filter-bar.tsx +14 -2
  222. package/src/components/table/hooks/use-table-query.ts +1 -1
  223. package/src/components/table/locales.ts +1116 -0
  224. package/src/components/table/search-input.tsx +12 -3
  225. package/src/components/table/selection-toolbar.tsx +23 -6
  226. package/src/components/table/table-empty-state.tsx +43 -3
  227. package/src/components/table/table-no-results.tsx +3 -3
  228. package/src/components/table/table-pagination.tsx +4 -3
  229. package/src/components/table/types.ts +1 -0
  230. package/src/components/tax-reports/index.ts +1 -0
  231. package/src/components/tax-reports/kir-export-form.tsx +46 -8
  232. package/src/components/tax-reports/slovenia-tax-profile-step.tsx +191 -0
  233. package/src/components/tax-reports/slovenia-yearly-export-form.tsx +509 -0
  234. package/src/components/tax-reports/slovenia-yearly-review-step.tsx +253 -0
  235. package/src/components/tax-reports/slovenia-yearly-summary.tsx +19 -0
  236. package/src/components/taxes/tax-list-table/tax-list-table.tsx +3 -2
  237. package/src/components/ui/sidebar.tsx +3 -2
  238. package/src/components/ui/sticky-form-footer.tsx +7 -1
  239. package/src/components/webhook-logs/index.ts +6 -0
  240. package/src/components/webhook-logs/locales.ts +392 -0
  241. package/src/components/webhook-logs/webhook-delivery-detail.tsx +255 -0
  242. package/src/components/webhook-logs/webhook-delivery-list-table.tsx +278 -0
  243. package/src/components/wl-subscription/index.ts +1 -0
  244. package/src/components/wl-subscription/locked-feature.tsx +1 -0
  245. package/src/components/wl-subscription/paywall.tsx +193 -0
  246. package/src/components/wl-subscription/upgrade-modal.tsx +93 -29
  247. package/src/generate-schemas.ts +12 -7
  248. package/src/generated/schemas/customer.ts +2 -0
  249. package/src/generated/schemas/entity.ts +134 -0
  250. package/src/generated/schemas/exportsloveniayearlynormiranireport_body.ts +27 -0
  251. package/src/generated/schemas/index.ts +2 -0
  252. package/src/generated/schemas/me.ts +20 -1
  253. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +40 -34
  254. package/src/generated/schemas/rendercreditnotepreview_body.ts +42 -36
  255. package/src/generated/schemas/renderdeliverynotepreview_body.ts +23 -13
  256. package/src/generated/schemas/renderestimatepreview_body.ts +23 -13
  257. package/src/generated/schemas/renderinvoicepreview_body.ts +40 -34
  258. package/src/generated/schemas/sendemail_body.ts +44 -0
  259. package/src/generated/schemas/sloveniataxprofile.ts +42 -0
  260. package/src/generated/schemas/startpdfexport_body.ts +91 -1
  261. package/src/generated/schemas/webhook.ts +10 -0
  262. package/src/hooks/use-duplicate-document.ts +51 -13
  263. package/src/hooks/use-eslog-validation.ts +59 -0
  264. package/src/hooks/use-premise-selection.ts +186 -0
  265. package/src/lib/browser-cookies.ts +4 -4
  266. package/src/lib/date-fns-locale.ts +48 -0
  267. package/src/lib/fiscalization-options.ts +81 -0
  268. package/src/lib/locale.ts +38 -0
  269. package/src/lib/template-variables.tsx +1 -1
  270. package/src/lib/translation.ts +14 -3
  271. package/src/providers/entities-context.tsx +1 -0
  272. package/src/providers/entities-provider.tsx +102 -3
  273. package/src/providers/form-footer-context.tsx +37 -4
  274. package/src/providers/sdk-provider.tsx +7 -2
  275. package/src/providers/white-label-provider.tsx +4 -1
  276. package/src/providers/wl-subscription-provider.tsx +90 -3
@@ -15,6 +15,13 @@ import { useQuery } from "@tanstack/react-query";
15
15
  import { useEntities } from "@/ui/providers/entities-context";
16
16
  import { useSDK } from "@/ui/providers/sdk-provider";
17
17
 
18
+ const DUPLICATE_TIMING_EVENT = "si:duplicate-timing";
19
+
20
+ function emitDuplicateDebug(detail: Record<string, unknown>) {
21
+ if (!import.meta.env.DEV || typeof window === "undefined") return;
22
+ window.dispatchEvent(new CustomEvent(DUPLICATE_TIMING_EVENT, { detail }));
23
+ }
24
+
18
25
  export type DocumentType = "invoice" | "estimate" | "credit_note" | "advance_invoice" | "delivery_note";
19
26
  type Document = Invoice | Estimate | CreditNote | AdvanceInvoice | DeliveryNote;
20
27
  type CreateRequest =
@@ -127,9 +134,21 @@ function transformDocumentForDuplication(source: Document, targetType: DocumentT
127
134
  // Number - leave empty for auto-generation
128
135
  // Do NOT copy: number, totals, taxes, payments, furs, eslog, vies, shareable_id
129
136
  // Link back to source document when converting (e.g., delivery note → invoice)
130
- ...(isConversion ? { linked_documents: [source.id] } : {}),
137
+ // Skip linking if source is a draft (drafts have no number/fiscalization)
138
+ ...(isConversion && !(source as any).is_draft ? { linked_documents: [source.id] } : {}),
131
139
  };
132
140
 
141
+ // Copy service dates when source is an invoice (available on invoices and credit notes)
142
+ if (sourceType === "invoice" || sourceType === "credit_note") {
143
+ const sourceDoc = source as any;
144
+ if (sourceDoc.date_service) {
145
+ (baseData as any).date_service = sourceDoc.date_service;
146
+ }
147
+ if (sourceDoc.date_service_to) {
148
+ (baseData as any).date_service_to = sourceDoc.date_service_to;
149
+ }
150
+ }
151
+
133
152
  return baseData;
134
153
  }
135
154
 
@@ -192,6 +211,13 @@ export function useDuplicateDocument({
192
211
  throw new Error("Source document ID and entity ID are required");
193
212
  }
194
213
 
214
+ const startedAt = performance.now();
215
+ emitDuplicateDebug({
216
+ stage: "request_started",
217
+ sourceId,
218
+ sourceType,
219
+ targetType,
220
+ });
195
221
  // Fetch source document based on its type
196
222
  let source: Document;
197
223
  if (sourceType === "invoice") {
@@ -215,22 +241,34 @@ export function useDuplicateDocument({
215
241
 
216
242
  // Build source document summaries for conversions (different source → target type)
217
243
  const isConversion = sourceType !== targetType;
218
- const sourceDocuments: LinkedDocumentSummary[] = isConversion
219
- ? [
220
- {
221
- id: source.id,
222
- type: sourceType,
223
- number: (source as any).number || "",
224
- date: (source as any).date || "",
225
- total_with_tax: (source as any).total_with_tax ?? 0,
226
- currency_code: (source as any).currency_code || "",
227
- },
228
- ]
229
- : [];
244
+ const sourceDocuments: LinkedDocumentSummary[] =
245
+ isConversion && !(source as any).is_draft
246
+ ? [
247
+ {
248
+ id: source.id,
249
+ type: sourceType,
250
+ number: (source as any).number || "",
251
+ date: (source as any).date || "",
252
+ total_with_tax: (source as any).total_with_tax ?? 0,
253
+ currency_code: (source as any).currency_code || "",
254
+ },
255
+ ]
256
+ : [];
257
+
258
+ emitDuplicateDebug({
259
+ stage: "request_succeeded",
260
+ sourceId,
261
+ sourceType,
262
+ targetType,
263
+ elapsedMs: Number((performance.now() - startedAt).toFixed(1)),
264
+ });
230
265
 
231
266
  return { initialValues, sourceDocuments };
232
267
  },
233
268
  enabled: enabled && !!sourceId && !!activeEntity?.id && !!sourceType,
269
+ retry: false,
270
+ refetchOnWindowFocus: false,
271
+ refetchOnReconnect: false,
234
272
  });
235
273
 
236
274
  return {
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Shared hook for eSLOG validation state.
3
+ *
4
+ * Encapsulates the eSLOG availability check, enabled toggle, and
5
+ * entity error tracking duplicated across invoice and advance invoice forms.
6
+ */
7
+
8
+ import { useEffect, useState } from "react";
9
+
10
+ type EslogError = { field: string; message: string };
11
+
12
+ type Entity = {
13
+ country_code?: string;
14
+ settings?: Record<string, any>;
15
+ } | null;
16
+
17
+ export type EslogValidationResult = {
18
+ /** Whether eSLOG is available (SI entity + setting enabled) */
19
+ isAvailable: boolean;
20
+ /** Current enabled state (undefined until initialized) */
21
+ isEnabled: boolean | undefined;
22
+ /** Toggle eSLOG validation on/off */
23
+ setEnabled: (v: boolean) => void;
24
+ /** Entity-level errors that require settings updates */
25
+ entityErrors: EslogError[];
26
+ /** Update entity errors (e.g. from validation results) */
27
+ setEntityErrors: (errors: EslogError[]) => void;
28
+ };
29
+
30
+ export function useEslogValidation(activeEntity: Entity): EslogValidationResult {
31
+ const isSlovenianEntity = activeEntity?.country_code === "SI";
32
+ const entityEslogEnabled = !!(activeEntity?.settings as any)?.eslog_validation_enabled;
33
+ const isAvailable = isSlovenianEntity && entityEslogEnabled;
34
+
35
+ const [isEnabled, setEnabled] = useState<boolean | undefined>(undefined);
36
+ const [entityErrors, setEntityErrors] = useState<EslogError[]>([]);
37
+
38
+ // Auto-enable when available and not yet initialized
39
+ useEffect(() => {
40
+ if (isAvailable && isEnabled === undefined) {
41
+ setEnabled(true);
42
+ }
43
+ }, [isAvailable, isEnabled]);
44
+
45
+ // Clear entity errors when disabled
46
+ useEffect(() => {
47
+ if (!isEnabled) {
48
+ setEntityErrors([]);
49
+ }
50
+ }, [isEnabled]);
51
+
52
+ return {
53
+ isAvailable,
54
+ isEnabled,
55
+ setEnabled,
56
+ entityErrors,
57
+ setEntityErrors,
58
+ };
59
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Shared hook for FURS/FINA premise and device selection.
3
+ *
4
+ * Encapsulates the premise/device loading, localStorage persistence,
5
+ * auto-selection, and readiness logic duplicated across invoice,
6
+ * credit note, and advance invoice create forms.
7
+ */
8
+
9
+ import { useEffect, useMemo, useState } from "react";
10
+
11
+ import { useFinaPremises, useFinaSettings } from "../components/entities/fina-settings-form/fina-settings.hooks";
12
+ import { useFursPremises, useFursSettings } from "../components/entities/furs-settings-form/furs-settings.hooks";
13
+ import {
14
+ type FinaCombo,
15
+ type FursCombo,
16
+ getLastUsedFinaCombo,
17
+ getLastUsedFursCombo,
18
+ setLastUsedFinaCombo,
19
+ setLastUsedFursCombo,
20
+ } from "../components/invoices/invoices.hooks";
21
+
22
+ /** Minimal premise shape needed by the hook */
23
+ type Premise = {
24
+ id: string;
25
+ business_premise_name: string;
26
+ is_active?: boolean;
27
+ Devices?: Array<{
28
+ electronic_device_name: string;
29
+ is_active?: boolean;
30
+ }>;
31
+ };
32
+
33
+ export type PremiseSelectionResult = {
34
+ /** Whether the fiscalization system is enabled in settings */
35
+ isEnabled: boolean;
36
+ /** Raw settings object (for form-specific logic like unified_numbering) */
37
+ settings: any;
38
+ /** Whether settings/premises are still loading */
39
+ isLoading: boolean;
40
+ /** Active (non-archived) premises */
41
+ activePremises: Premise[];
42
+ /** Whether there are any active premises */
43
+ hasPremises: boolean;
44
+ /** Active devices for the currently selected premise */
45
+ activeDevices: Array<{ electronic_device_name: string; is_active?: boolean }>;
46
+ /** Currently selected premise name */
47
+ selectedPremiseName: string | undefined;
48
+ /** Currently selected device name */
49
+ selectedDeviceName: string | undefined;
50
+ /** Update selected premise */
51
+ setSelectedPremiseName: (name: string | undefined) => void;
52
+ /** Update selected device */
53
+ setSelectedDeviceName: (name: string | undefined) => void;
54
+ /** True when disabled OR both premise+device are selected */
55
+ isSelectionReady: boolean;
56
+ /** True when enabled, has premises, and both are selected */
57
+ isActive: boolean;
58
+ /** Save current combo to localStorage (call on successful submission) */
59
+ saveCombo: () => void;
60
+ };
61
+
62
+ export function usePremiseSelection(opts: {
63
+ entityId: string;
64
+ type: "furs" | "fina";
65
+ enabled?: boolean;
66
+ }): PremiseSelectionResult {
67
+ const { entityId, type } = opts;
68
+ const externalEnabled = opts.enabled !== false;
69
+
70
+ // --- FURS hooks (only called when type is furs) ---
71
+ const { data: fursSettings, isLoading: isFursSettingsLoading } = useFursSettings(entityId, {
72
+ enabled: type === "furs" && externalEnabled,
73
+ });
74
+ const { data: fursPremises, isLoading: isFursPremisesLoading } = useFursPremises(entityId, {
75
+ enabled: type === "furs" && externalEnabled && fursSettings?.enabled === true,
76
+ });
77
+
78
+ // --- FINA hooks (only called when type is fina) ---
79
+ const { data: finaSettings, isLoading: isFinaSettingsLoading } = useFinaSettings(entityId, {
80
+ enabled: type === "fina" && externalEnabled,
81
+ });
82
+ const { data: finaPremises, isLoading: isFinaPremisesLoading } = useFinaPremises(entityId, {
83
+ enabled: type === "fina" && externalEnabled && finaSettings?.enabled === true,
84
+ });
85
+
86
+ // Unified values
87
+ const settings = type === "furs" ? fursSettings : finaSettings;
88
+ const premises = type === "furs" ? fursPremises : finaPremises;
89
+ const isSettingsLoading = type === "furs" ? isFursSettingsLoading : isFinaSettingsLoading;
90
+ const isPremisesLoading = type === "furs" ? isFursPremisesLoading : isFinaPremisesLoading;
91
+
92
+ const isEnabled = settings?.enabled === true;
93
+ const isLoading = isSettingsLoading || (isEnabled && isPremisesLoading);
94
+
95
+ const activePremises = useMemo(() => (premises?.filter((p: any) => p.is_active) as Premise[]) || [], [premises]);
96
+ const hasPremises = activePremises.length > 0;
97
+
98
+ // Selection state
99
+ const [selectedPremiseName, setSelectedPremiseName] = useState<string | undefined>();
100
+ const [selectedDeviceName, setSelectedDeviceName] = useState<string | undefined>();
101
+
102
+ // Active devices for selected premise
103
+ const activeDevices = useMemo(() => {
104
+ if (!selectedPremiseName) return [];
105
+ const premise = activePremises.find((p) => p.business_premise_name === selectedPremiseName);
106
+ const devices = premise?.Devices?.filter((d) => d.is_active) || [];
107
+ // For FURS, exclude the legacy "OLD" device
108
+ return type === "furs" ? devices.filter((d) => d.electronic_device_name !== "OLD") : devices;
109
+ }, [activePremises, selectedPremiseName, type]);
110
+
111
+ // Initialize selection from localStorage or first active combo
112
+ useEffect(() => {
113
+ if (!isEnabled || !hasPremises || selectedPremiseName) return;
114
+
115
+ const lastUsed = type === "furs" ? getLastUsedFursCombo(entityId) : getLastUsedFinaCombo(entityId);
116
+
117
+ if (lastUsed) {
118
+ const premise = activePremises.find((p) => p.business_premise_name === lastUsed.business_premise_name);
119
+ const device = premise?.Devices?.find(
120
+ (d) => d.electronic_device_name === lastUsed.electronic_device_name && d.is_active,
121
+ );
122
+ if (premise && device) {
123
+ setSelectedPremiseName(lastUsed.business_premise_name);
124
+ setSelectedDeviceName(lastUsed.electronic_device_name);
125
+ return;
126
+ }
127
+ }
128
+
129
+ // Fall back to first active premise/device
130
+ const firstPremise = activePremises[0];
131
+ const firstDevice = firstPremise?.Devices?.find((d) => d.is_active);
132
+ if (firstPremise && firstDevice) {
133
+ setSelectedPremiseName(firstPremise.business_premise_name);
134
+ setSelectedDeviceName(firstDevice.electronic_device_name);
135
+ }
136
+ }, [isEnabled, hasPremises, activePremises, entityId, selectedPremiseName, type]);
137
+
138
+ // When premise changes, auto-select first active device if current is invalid
139
+ useEffect(() => {
140
+ if (!selectedPremiseName) return;
141
+ const premise = activePremises.find((p) => p.business_premise_name === selectedPremiseName);
142
+ const devicesForPremise =
143
+ type === "furs"
144
+ ? premise?.Devices?.filter((d) => d.is_active && d.electronic_device_name !== "OLD")
145
+ : premise?.Devices?.filter((d) => d.is_active);
146
+ const firstDevice = devicesForPremise?.[0];
147
+ if (firstDevice && selectedDeviceName !== firstDevice.electronic_device_name) {
148
+ const currentDeviceInPremise = devicesForPremise?.find((d) => d.electronic_device_name === selectedDeviceName);
149
+ if (!currentDeviceInPremise) {
150
+ setSelectedDeviceName(firstDevice.electronic_device_name);
151
+ }
152
+ }
153
+ }, [selectedPremiseName, activePremises, selectedDeviceName, type]);
154
+
155
+ const isSelectionReady = !isEnabled || !hasPremises || (!!selectedPremiseName && !!selectedDeviceName);
156
+ const isActive = !!(isEnabled && hasPremises && selectedPremiseName && selectedDeviceName);
157
+
158
+ const saveCombo = () => {
159
+ if (!isActive || !selectedPremiseName || !selectedDeviceName) return;
160
+ const combo: FursCombo | FinaCombo = {
161
+ business_premise_name: selectedPremiseName,
162
+ electronic_device_name: selectedDeviceName,
163
+ };
164
+ if (type === "furs") {
165
+ setLastUsedFursCombo(entityId, combo);
166
+ } else {
167
+ setLastUsedFinaCombo(entityId, combo);
168
+ }
169
+ };
170
+
171
+ return {
172
+ isEnabled,
173
+ settings,
174
+ isLoading: !!isLoading,
175
+ activePremises,
176
+ hasPremises,
177
+ activeDevices,
178
+ selectedPremiseName,
179
+ selectedDeviceName,
180
+ setSelectedPremiseName,
181
+ setSelectedDeviceName,
182
+ isSelectionReady,
183
+ isActive,
184
+ saveCombo,
185
+ };
186
+ }
@@ -16,7 +16,7 @@ type CookieOptions = {
16
16
  * Sets a cookie with the given name, value, and options
17
17
  */
18
18
  export function setCookie(name: string, value: string, options: CookieOptions = {}) {
19
- if (typeof window === "undefined") {
19
+ if (typeof document === "undefined") {
20
20
  console.warn("setCookie called on server");
21
21
  return;
22
22
  }
@@ -41,7 +41,7 @@ export function setCookie(name: string, value: string, options: CookieOptions =
41
41
  * Gets a cookie value by name
42
42
  */
43
43
  export function getCookie(name: string): string | undefined {
44
- if (typeof window === "undefined") {
44
+ if (typeof document === "undefined") {
45
45
  return undefined;
46
46
  }
47
47
 
@@ -52,7 +52,7 @@ export function getCookie(name: string): string | undefined {
52
52
  * Removes a cookie by name, clearing both host-only and domain-scoped versions
53
53
  */
54
54
  export function deleteCookie(name: string, path = "/") {
55
- if (typeof window === "undefined") {
55
+ if (typeof document === "undefined") {
56
56
  console.warn("deleteCookie called on server");
57
57
  return;
58
58
  }
@@ -64,7 +64,7 @@ export function deleteCookie(name: string, path = "/") {
64
64
  });
65
65
 
66
66
  // Also delete domain cookie (e.g. .spaceinvoices.com) if on spaceinvoices.com
67
- const hostname = window.location.hostname;
67
+ const hostname = typeof window !== "undefined" ? window.location.hostname : globalThis.location?.hostname ?? "";
68
68
  if (hostname === "spaceinvoices.com" || hostname.endsWith(".spaceinvoices.com")) {
69
69
  setCookie(name, "", {
70
70
  path,
@@ -0,0 +1,48 @@
1
+ import type { Locale } from "date-fns";
2
+ import {
3
+ bg,
4
+ cs,
5
+ de,
6
+ enUS,
7
+ es,
8
+ et,
9
+ fi,
10
+ fr,
11
+ hr,
12
+ is,
13
+ it,
14
+ nb,
15
+ nl,
16
+ pl,
17
+ pt,
18
+ sk,
19
+ sl,
20
+ sv,
21
+ } from "date-fns/locale";
22
+ import { getLocaleLanguage } from "./locale";
23
+
24
+ const DATE_FNS_LOCALES: Record<string, Locale> = {
25
+ bg,
26
+ cs,
27
+ de,
28
+ en: enUS,
29
+ es,
30
+ et,
31
+ fi,
32
+ fr,
33
+ hr,
34
+ is,
35
+ it,
36
+ nb,
37
+ nl,
38
+ pl,
39
+ pt,
40
+ sk,
41
+ sl,
42
+ sv,
43
+ };
44
+
45
+ export function getDateFnsLocale(locale?: string): Locale {
46
+ const language = getLocaleLanguage(locale);
47
+ return DATE_FNS_LOCALES[language ?? ""] ?? enUS;
48
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Pure functions for building FURS/FINA/eSLOG submission options.
3
+ *
4
+ * Extracted from the submit callbacks of invoice, credit note,
5
+ * and advance invoice forms where this logic was duplicated.
6
+ */
7
+
8
+ export type FursSubmitOptions = { skip: true } | { business_premise_name: string; electronic_device_name: string };
9
+
10
+ export type FinaSubmitOptions = {
11
+ business_premise_name: string;
12
+ electronic_device_name: string;
13
+ payment_type: string;
14
+ };
15
+
16
+ export type EslogSubmitOptions = {
17
+ validation_enabled: boolean;
18
+ };
19
+
20
+ /**
21
+ * Build FURS fiscalization options for document submission.
22
+ *
23
+ * Returns undefined when FURS should not be included (drafts, disabled, edit mode).
24
+ */
25
+ export function buildFursOptions(opts: {
26
+ isDraft: boolean;
27
+ isEnabled: boolean;
28
+ isEditMode?: boolean;
29
+ skipFiscalization?: boolean;
30
+ premiseName?: string;
31
+ deviceName?: string;
32
+ }): FursSubmitOptions | undefined {
33
+ if (opts.isDraft || opts.isEditMode || !opts.isEnabled) return undefined;
34
+ if (opts.skipFiscalization) return { skip: true };
35
+ if (opts.premiseName && opts.deviceName) {
36
+ return {
37
+ business_premise_name: opts.premiseName,
38
+ electronic_device_name: opts.deviceName,
39
+ };
40
+ }
41
+ return undefined;
42
+ }
43
+
44
+ /**
45
+ * Build FINA fiscalization options for document submission.
46
+ *
47
+ * Returns undefined when FINA should not be included (drafts, numbering disabled).
48
+ */
49
+ export function buildFinaOptions(opts: {
50
+ isDraft: boolean;
51
+ useFinaNumbering: boolean;
52
+ isEditMode?: boolean;
53
+ premiseName?: string;
54
+ deviceName?: string;
55
+ paymentType?: string;
56
+ }): FinaSubmitOptions | undefined {
57
+ if (opts.isDraft || opts.isEditMode || !opts.useFinaNumbering) return undefined;
58
+ if (opts.premiseName && opts.deviceName) {
59
+ return {
60
+ business_premise_name: opts.premiseName,
61
+ electronic_device_name: opts.deviceName,
62
+ payment_type: opts.paymentType || "bank_transfer",
63
+ };
64
+ }
65
+ return undefined;
66
+ }
67
+
68
+ /**
69
+ * Build eSLOG validation options for document submission.
70
+ *
71
+ * Returns undefined when eSLOG should not be included (drafts, edit mode, unavailable).
72
+ */
73
+ export function buildEslogOptions(opts: {
74
+ isDraft: boolean;
75
+ isEditMode?: boolean;
76
+ isAvailable: boolean;
77
+ isEnabled: boolean | undefined;
78
+ }): EslogSubmitOptions | undefined {
79
+ if (opts.isDraft || opts.isEditMode || !opts.isAvailable) return undefined;
80
+ return { validation_enabled: opts.isEnabled === true };
81
+ }
@@ -0,0 +1,38 @@
1
+ export function normalizeLocale(locale?: string, fallback = "en"): string {
2
+ return locale?.trim().toLowerCase().replace(/_/g, "-") || fallback;
3
+ }
4
+
5
+ export function getLocaleLanguage(locale?: string, fallback = "en"): string {
6
+ return normalizeLocale(locale, fallback).split("-")[0] || fallback;
7
+ }
8
+
9
+ const LANGUAGE_TO_LOCALE: Record<string, string> = {
10
+ en: "en-US",
11
+ de: "de-DE",
12
+ sl: "sl-SI",
13
+ it: "it-IT",
14
+ fr: "fr-FR",
15
+ es: "es-ES",
16
+ pt: "pt-PT",
17
+ nl: "nl-NL",
18
+ pl: "pl-PL",
19
+ hr: "hr-HR",
20
+ sv: "sv-SE",
21
+ fi: "fi-FI",
22
+ et: "et-EE",
23
+ bg: "bg-BG",
24
+ cs: "cs-CZ",
25
+ sk: "sk-SK",
26
+ nb: "nb-NO",
27
+ is: "is-IS",
28
+ };
29
+
30
+ export function getFullLocale(locale?: string, fallback = "en-US"): string {
31
+ const normalized = normalizeLocale(locale, fallback);
32
+ if (Object.values(LANGUAGE_TO_LOCALE).includes(normalized)) {
33
+ return normalized;
34
+ }
35
+
36
+ const language = getLocaleLanguage(normalized, fallback.split("-")[0]);
37
+ return LANGUAGE_TO_LOCALE[language] || fallback;
38
+ }
@@ -26,7 +26,7 @@ export function getVariableValue(
26
26
 
27
27
  // Entity-related variables
28
28
  if (varName === "entity_name") return entity.name || null;
29
- if (varName === "entity_email") return (entity.settings as any)?.email || null;
29
+ if (varName === "entity_email") return entity.email || null;
30
30
  if (varName === "entity_address") return entity.address || null;
31
31
  if (varName === "entity_post_code") return entity.post_code || null;
32
32
  if (varName === "entity_city") return entity.city || null;
@@ -1,4 +1,5 @@
1
1
  import { useCallback } from "react";
2
+ import { getLocaleLanguage, normalizeLocale } from "./locale";
2
3
 
3
4
  type TranslationFunction = (key: string) => string;
4
5
 
@@ -24,9 +25,19 @@ export function createTranslation({ t, namespace, locale = "en", translations =
24
25
  }
25
26
  }
26
27
 
27
- // 2. Look up in local translations for current locale
28
- if (translations[locale]) {
29
- const translation = translations[locale][key];
28
+ const normalizedLocale = normalizeLocale(locale);
29
+ const baseLocale = getLocaleLanguage(normalizedLocale);
30
+
31
+ // 2. Look up in local translations for current locale, then base language
32
+ const localeTranslations = translations[normalizedLocale] ?? translations[baseLocale];
33
+ if (localeTranslations) {
34
+ const translation = localeTranslations[key];
35
+ if (translation) return translation;
36
+ }
37
+
38
+ const englishTranslations = translations.en;
39
+ if (englishTranslations) {
40
+ const translation = englishTranslations[key];
30
41
  if (translation) return translation;
31
42
  }
32
43
 
@@ -12,6 +12,7 @@ export type EntitiesContextType = {
12
12
  activeEntity: Entity | null;
13
13
  setActiveEntity: (entity: Entity | null) => void;
14
14
  environment: EntityEnvironment;
15
+ /** @deprecated Use URL-based sandbox routing instead of calling setEnvironment directly */
15
16
  setEnvironment: (environment: EntityEnvironment) => void;
16
17
  isLoading: boolean;
17
18
  refetchEntities: () => Promise<void>;