@spaceinvoices/react-ui 0.4.8 → 0.4.10

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 (258) hide show
  1. package/cli/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/common/autocomplete.tsx +69 -6
  4. package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +124 -285
  5. package/src/components/advance-invoices/list/list-table.tsx +10 -3
  6. package/src/components/advance-invoices/list/locales/de.ts +2 -0
  7. package/src/components/advance-invoices/list/locales/en.ts +1 -0
  8. package/src/components/advance-invoices/list/locales/es.ts +1 -0
  9. package/src/components/advance-invoices/list/locales/fr.ts +1 -0
  10. package/src/components/advance-invoices/list/locales/hr.ts +1 -0
  11. package/src/components/advance-invoices/list/locales/it.ts +1 -0
  12. package/src/components/advance-invoices/list/locales/nl.ts +1 -0
  13. package/src/components/advance-invoices/list/locales/pl.ts +1 -0
  14. package/src/components/advance-invoices/list/locales/pt.ts +1 -0
  15. package/src/components/advance-invoices/list/locales/sl.ts +1 -0
  16. package/src/components/advance-invoices/list/use-advance-invoice-download.ts +1 -12
  17. package/src/components/credit-notes/create/create-credit-note-form.tsx +116 -238
  18. package/src/components/credit-notes/list/list-table.tsx +6 -3
  19. package/src/components/credit-notes/list/use-credit-note-download.ts +1 -12
  20. package/src/components/customers/customer-autocomplete.tsx +64 -11
  21. package/src/components/customers/customer-list-table/customer-list-table.tsx +3 -2
  22. package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +9 -1
  23. package/src/components/dashboard/collection-rate-card/locales/bg.ts +3 -0
  24. package/src/components/dashboard/collection-rate-card/locales/cs.ts +3 -0
  25. package/src/components/dashboard/collection-rate-card/locales/et.ts +3 -0
  26. package/src/components/dashboard/collection-rate-card/locales/fi.ts +3 -0
  27. package/src/components/dashboard/collection-rate-card/locales/is.ts +3 -0
  28. package/src/components/dashboard/collection-rate-card/locales/nb.ts +3 -0
  29. package/src/components/dashboard/collection-rate-card/locales/sk.ts +3 -0
  30. package/src/components/dashboard/collection-rate-card/locales/sv.ts +3 -0
  31. package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +10 -2
  32. package/src/components/dashboard/invoice-status-chart/locales/bg.ts +10 -0
  33. package/src/components/dashboard/invoice-status-chart/locales/cs.ts +10 -0
  34. package/src/components/dashboard/invoice-status-chart/locales/de.ts +1 -0
  35. package/src/components/dashboard/invoice-status-chart/locales/es.ts +1 -0
  36. package/src/components/dashboard/invoice-status-chart/locales/et.ts +10 -0
  37. package/src/components/dashboard/invoice-status-chart/locales/fi.ts +10 -0
  38. package/src/components/dashboard/invoice-status-chart/locales/fr.ts +1 -0
  39. package/src/components/dashboard/invoice-status-chart/locales/hr.ts +1 -0
  40. package/src/components/dashboard/invoice-status-chart/locales/is.ts +10 -0
  41. package/src/components/dashboard/invoice-status-chart/locales/it.ts +1 -0
  42. package/src/components/dashboard/invoice-status-chart/locales/nb.ts +10 -0
  43. package/src/components/dashboard/invoice-status-chart/locales/nl.ts +1 -0
  44. package/src/components/dashboard/invoice-status-chart/locales/pl.ts +1 -0
  45. package/src/components/dashboard/invoice-status-chart/locales/pt.ts +1 -0
  46. package/src/components/dashboard/invoice-status-chart/locales/sk.ts +10 -0
  47. package/src/components/dashboard/invoice-status-chart/locales/sl.ts +1 -0
  48. package/src/components/dashboard/invoice-status-chart/locales/sv.ts +10 -0
  49. package/src/components/dashboard/payment-methods-chart/locales/bg.ts +12 -0
  50. package/src/components/dashboard/payment-methods-chart/locales/cs.ts +12 -0
  51. package/src/components/dashboard/payment-methods-chart/locales/et.ts +12 -0
  52. package/src/components/dashboard/payment-methods-chart/locales/fi.ts +12 -0
  53. package/src/components/dashboard/payment-methods-chart/locales/is.ts +12 -0
  54. package/src/components/dashboard/payment-methods-chart/locales/nb.ts +12 -0
  55. package/src/components/dashboard/payment-methods-chart/locales/sk.ts +12 -0
  56. package/src/components/dashboard/payment-methods-chart/locales/sv.ts +12 -0
  57. package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +9 -1
  58. package/src/components/dashboard/payment-trend-chart/locales/bg.ts +6 -0
  59. package/src/components/dashboard/payment-trend-chart/locales/cs.ts +6 -0
  60. package/src/components/dashboard/payment-trend-chart/locales/de.ts +1 -0
  61. package/src/components/dashboard/payment-trend-chart/locales/es.ts +1 -0
  62. package/src/components/dashboard/payment-trend-chart/locales/et.ts +6 -0
  63. package/src/components/dashboard/payment-trend-chart/locales/fi.ts +6 -0
  64. package/src/components/dashboard/payment-trend-chart/locales/fr.ts +1 -0
  65. package/src/components/dashboard/payment-trend-chart/locales/hr.ts +1 -0
  66. package/src/components/dashboard/payment-trend-chart/locales/is.ts +6 -0
  67. package/src/components/dashboard/payment-trend-chart/locales/it.ts +1 -0
  68. package/src/components/dashboard/payment-trend-chart/locales/nb.ts +6 -0
  69. package/src/components/dashboard/payment-trend-chart/locales/nl.ts +1 -0
  70. package/src/components/dashboard/payment-trend-chart/locales/pl.ts +1 -0
  71. package/src/components/dashboard/payment-trend-chart/locales/pt.ts +1 -0
  72. package/src/components/dashboard/payment-trend-chart/locales/sk.ts +6 -0
  73. package/src/components/dashboard/payment-trend-chart/locales/sl.ts +1 -0
  74. package/src/components/dashboard/payment-trend-chart/locales/sv.ts +6 -0
  75. package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +15 -8
  76. package/src/components/dashboard/revenue-trend-chart/locales/bg.ts +6 -0
  77. package/src/components/dashboard/revenue-trend-chart/locales/cs.ts +6 -0
  78. package/src/components/dashboard/revenue-trend-chart/locales/de.ts +1 -0
  79. package/src/components/dashboard/revenue-trend-chart/locales/es.ts +1 -0
  80. package/src/components/dashboard/revenue-trend-chart/locales/et.ts +6 -0
  81. package/src/components/dashboard/revenue-trend-chart/locales/fi.ts +6 -0
  82. package/src/components/dashboard/revenue-trend-chart/locales/fr.ts +1 -0
  83. package/src/components/dashboard/revenue-trend-chart/locales/hr.ts +1 -0
  84. package/src/components/dashboard/revenue-trend-chart/locales/is.ts +6 -0
  85. package/src/components/dashboard/revenue-trend-chart/locales/it.ts +1 -0
  86. package/src/components/dashboard/revenue-trend-chart/locales/nb.ts +6 -0
  87. package/src/components/dashboard/revenue-trend-chart/locales/nl.ts +1 -0
  88. package/src/components/dashboard/revenue-trend-chart/locales/pl.ts +1 -0
  89. package/src/components/dashboard/revenue-trend-chart/locales/pt.ts +1 -0
  90. package/src/components/dashboard/revenue-trend-chart/locales/sk.ts +6 -0
  91. package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +1 -0
  92. package/src/components/dashboard/revenue-trend-chart/locales/sv.ts +6 -0
  93. package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +15 -8
  94. package/src/components/dashboard/tax-collected-card/locales.ts +110 -0
  95. package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +8 -2
  96. package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +4 -4
  97. package/src/components/dashboard/top-customers-chart/locales/bg.ts +7 -0
  98. package/src/components/dashboard/top-customers-chart/locales/cs.ts +7 -0
  99. package/src/components/dashboard/top-customers-chart/locales/de.ts +2 -0
  100. package/src/components/dashboard/top-customers-chart/locales/es.ts +2 -0
  101. package/src/components/dashboard/top-customers-chart/locales/et.ts +7 -0
  102. package/src/components/dashboard/top-customers-chart/locales/fi.ts +7 -0
  103. package/src/components/dashboard/top-customers-chart/locales/fr.ts +2 -0
  104. package/src/components/dashboard/top-customers-chart/locales/hr.ts +2 -0
  105. package/src/components/dashboard/top-customers-chart/locales/is.ts +7 -0
  106. package/src/components/dashboard/top-customers-chart/locales/it.ts +2 -0
  107. package/src/components/dashboard/top-customers-chart/locales/nb.ts +7 -0
  108. package/src/components/dashboard/top-customers-chart/locales/nl.ts +2 -0
  109. package/src/components/dashboard/top-customers-chart/locales/pl.ts +2 -0
  110. package/src/components/dashboard/top-customers-chart/locales/pt.ts +2 -0
  111. package/src/components/dashboard/top-customers-chart/locales/sk.ts +7 -0
  112. package/src/components/dashboard/top-customers-chart/locales/sl.ts +2 -0
  113. package/src/components/dashboard/top-customers-chart/locales/sv.ts +7 -0
  114. package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +23 -12
  115. package/src/components/delivery-notes/create/create-delivery-note-form.tsx +33 -20
  116. package/src/components/delivery-notes/list/list-table.tsx +22 -13
  117. package/src/components/delivery-notes/list/locales/de.ts +2 -0
  118. package/src/components/delivery-notes/list/locales/en.ts +1 -0
  119. package/src/components/delivery-notes/list/locales/es.ts +1 -0
  120. package/src/components/delivery-notes/list/locales/fr.ts +1 -0
  121. package/src/components/delivery-notes/list/locales/hr.ts +1 -0
  122. package/src/components/delivery-notes/list/locales/it.ts +1 -0
  123. package/src/components/delivery-notes/list/locales/nl.ts +1 -0
  124. package/src/components/delivery-notes/list/locales/pl.ts +1 -0
  125. package/src/components/delivery-notes/list/locales/pt.ts +1 -0
  126. package/src/components/delivery-notes/list/locales/sl.ts +1 -0
  127. package/src/components/delivery-notes/list/use-delivery-note-download.ts +1 -12
  128. package/src/components/documents/create/document-add-item-form.tsx +28 -16
  129. package/src/components/documents/create/document-add-item-tax-rate-field.tsx +12 -2
  130. package/src/components/documents/create/document-items-section.tsx +70 -39
  131. package/src/components/documents/create/document-recipient-section.tsx +10 -1
  132. package/src/components/documents/create/live-preview.tsx +113 -15
  133. package/src/components/documents/create/prepare-document-submission.ts +35 -16
  134. package/src/components/documents/create/use-document-customer-form.ts +14 -3
  135. package/src/components/documents/documents.hooks.ts +7 -2
  136. package/src/components/documents/shared/document-preview-display.tsx +136 -67
  137. package/src/components/documents/shared/scaled-document-preview.tsx +45 -5
  138. package/src/components/documents/view/document-actions-bar.tsx +284 -182
  139. package/src/components/documents/view/document-activities-list.tsx +3 -0
  140. package/src/components/documents/view/document-payments-list.tsx +3 -0
  141. package/src/components/documents/view/locales/de.ts +8 -0
  142. package/src/components/documents/view/locales/es.ts +8 -0
  143. package/src/components/documents/view/locales/fr.ts +8 -0
  144. package/src/components/documents/view/locales/hr.ts +8 -0
  145. package/src/components/documents/view/locales/it.ts +8 -0
  146. package/src/components/documents/view/locales/nl.ts +8 -0
  147. package/src/components/documents/view/locales/pl.ts +8 -0
  148. package/src/components/documents/view/locales/pt.ts +8 -0
  149. package/src/components/documents/view/locales/sl.ts +8 -0
  150. package/src/components/documents/view/use-document-download.ts +14 -25
  151. package/src/components/entities/create-entity-form.tsx +101 -16
  152. package/src/components/entities/fina-settings-form/fina-operator-required-dialog.tsx +3 -3
  153. package/src/components/entities/fina-settings-form/fina-settings-form.tsx +78 -124
  154. package/src/components/entities/fina-settings-form/sections/certificate-settings-section.tsx +8 -1
  155. package/src/components/entities/fina-settings-form/sections/premises-management-section.tsx +14 -2
  156. package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +7 -2
  157. package/src/components/entities/furs-settings-form/furs-settings-form.tsx +56 -130
  158. package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +8 -1
  159. package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +1 -0
  160. package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +15 -2
  161. package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +20 -3
  162. package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +38 -12
  163. package/src/components/entities/settings/eslog-settings-form.tsx +13 -1
  164. package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +3 -22
  165. package/src/components/entities/shared/fiscalization-step-flow.ts +77 -0
  166. package/src/components/entities/shared/fiscalization-step-tabs.tsx +71 -0
  167. package/src/components/estimates/create/create-estimate-form.tsx +34 -21
  168. package/src/components/estimates/list/list-table.tsx +23 -14
  169. package/src/components/estimates/list/locales/de.ts +2 -0
  170. package/src/components/estimates/list/locales/en.ts +1 -0
  171. package/src/components/estimates/list/locales/es.ts +1 -0
  172. package/src/components/estimates/list/locales/fr.ts +1 -0
  173. package/src/components/estimates/list/locales/hr.ts +1 -0
  174. package/src/components/estimates/list/locales/it.ts +1 -0
  175. package/src/components/estimates/list/locales/nl.ts +1 -0
  176. package/src/components/estimates/list/locales/pl.ts +1 -0
  177. package/src/components/estimates/list/locales/pt.ts +1 -0
  178. package/src/components/estimates/list/locales/sl.ts +1 -0
  179. package/src/components/estimates/list/use-estimate-download.ts +1 -12
  180. package/src/components/export/document-export-form.tsx +33 -7
  181. package/src/components/export/sales-per-item-export-form.tsx +23 -7
  182. package/src/components/invoices/create/create-invoice-form.tsx +295 -329
  183. package/src/components/invoices/create/prepare-invoice-submission.ts +0 -8
  184. package/src/components/invoices/list/list-table.tsx +7 -4
  185. package/src/components/invoices/list/use-invoice-download.ts +1 -11
  186. package/src/components/invoices/send-email-dialog/locales/de.ts +2 -0
  187. package/src/components/invoices/send-email-dialog/locales/es.ts +2 -0
  188. package/src/components/invoices/send-email-dialog/locales/fr.ts +2 -0
  189. package/src/components/invoices/send-email-dialog/locales/hr.ts +2 -0
  190. package/src/components/invoices/send-email-dialog/locales/it.ts +2 -0
  191. package/src/components/invoices/send-email-dialog/locales/nl.ts +2 -0
  192. package/src/components/invoices/send-email-dialog/locales/pl.ts +2 -0
  193. package/src/components/invoices/send-email-dialog/locales/pt.ts +2 -0
  194. package/src/components/invoices/send-email-dialog/locales/sl.ts +2 -0
  195. package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +77 -8
  196. package/src/components/invoices/view/eslog-info-display.tsx +17 -1
  197. package/src/components/invoices/view/fiscalization-status-card.tsx +7 -3
  198. package/src/components/items/item-combobox.tsx +26 -6
  199. package/src/components/items/item-list-table/item-list-table.tsx +5 -2
  200. package/src/components/payments/list/list-table.tsx +14 -4
  201. package/src/components/recurring-invoices/list/list-table.tsx +7 -4
  202. package/src/components/request-logs/locales.ts +412 -0
  203. package/src/components/request-logs/request-log-detail.tsx +37 -21
  204. package/src/components/request-logs/request-log-list-table.tsx +57 -11
  205. package/src/components/table/data-table.tsx +5 -2
  206. package/src/components/table/date-cell.tsx +3 -1
  207. package/src/components/table/filter-bar.tsx +14 -2
  208. package/src/components/table/hooks/use-table-query.ts +1 -1
  209. package/src/components/table/locales.ts +1116 -0
  210. package/src/components/table/search-input.tsx +12 -3
  211. package/src/components/table/selection-toolbar.tsx +23 -6
  212. package/src/components/table/table-empty-state.tsx +43 -3
  213. package/src/components/table/table-no-results.tsx +3 -3
  214. package/src/components/table/table-pagination.tsx +4 -3
  215. package/src/components/table/types.ts +1 -0
  216. package/src/components/tax-reports/index.ts +1 -0
  217. package/src/components/tax-reports/kir-export-form.tsx +46 -8
  218. package/src/components/tax-reports/slovenia-tax-profile-step.tsx +191 -0
  219. package/src/components/tax-reports/slovenia-yearly-export-form.tsx +509 -0
  220. package/src/components/tax-reports/slovenia-yearly-review-step.tsx +253 -0
  221. package/src/components/tax-reports/slovenia-yearly-summary.tsx +19 -0
  222. package/src/components/taxes/tax-list-table/tax-list-table.tsx +3 -2
  223. package/src/components/ui/sticky-form-footer.tsx +7 -1
  224. package/src/components/webhook-logs/index.ts +6 -0
  225. package/src/components/webhook-logs/locales.ts +392 -0
  226. package/src/components/webhook-logs/webhook-delivery-detail.tsx +255 -0
  227. package/src/components/webhook-logs/webhook-delivery-list-table.tsx +278 -0
  228. package/src/components/wl-subscription/index.ts +1 -0
  229. package/src/components/wl-subscription/locked-feature.tsx +1 -0
  230. package/src/components/wl-subscription/paywall.tsx +193 -0
  231. package/src/components/wl-subscription/upgrade-modal.tsx +93 -29
  232. package/src/generate-schemas.ts +10 -5
  233. package/src/generated/schemas/customer.ts +2 -0
  234. package/src/generated/schemas/entity.ts +34 -0
  235. package/src/generated/schemas/me.ts +20 -1
  236. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +40 -34
  237. package/src/generated/schemas/rendercreditnotepreview_body.ts +42 -36
  238. package/src/generated/schemas/renderdeliverynotepreview_body.ts +23 -13
  239. package/src/generated/schemas/renderestimatepreview_body.ts +23 -13
  240. package/src/generated/schemas/renderinvoicepreview_body.ts +40 -34
  241. package/src/generated/schemas/sendemail_body.ts +44 -0
  242. package/src/generated/schemas/startpdfexport_body.ts +91 -1
  243. package/src/generated/schemas/webhook.ts +10 -0
  244. package/src/hooks/use-duplicate-document.ts +51 -13
  245. package/src/hooks/use-eslog-validation.ts +59 -0
  246. package/src/hooks/use-premise-selection.ts +186 -0
  247. package/src/lib/browser-cookies.ts +4 -4
  248. package/src/lib/date-fns-locale.ts +48 -0
  249. package/src/lib/fiscalization-options.ts +81 -0
  250. package/src/lib/locale.ts +38 -0
  251. package/src/lib/template-variables.tsx +1 -1
  252. package/src/lib/translation.ts +14 -3
  253. package/src/providers/entities-context.tsx +1 -0
  254. package/src/providers/entities-provider.tsx +102 -3
  255. package/src/providers/form-footer-context.tsx +37 -4
  256. package/src/providers/sdk-provider.tsx +7 -2
  257. package/src/providers/white-label-provider.tsx +4 -1
  258. package/src/providers/wl-subscription-provider.tsx +90 -3
@@ -122,6 +122,14 @@ export default {
122
122
  Dutch: "Neerlandês",
123
123
  Polish: "Polaco",
124
124
  Croatian: "Croata",
125
+ Swedish: "Sueco",
126
+ Finnish: "Finlandês",
127
+ Estonian: "Estónio",
128
+ Bulgarian: "Búlgaro",
129
+ Czech: "Checo",
130
+ Slovak: "Eslovaco",
131
+ Norwegian: "Norueguês",
132
+ Icelandic: "Islandês",
125
133
 
126
134
  // Linked documents
127
135
  "Linked documents": "Documentos vinculados",
@@ -123,6 +123,14 @@ export default {
123
123
  Dutch: "Nizozemščina",
124
124
  Polish: "Poljščina",
125
125
  Croatian: "Hrvaščina",
126
+ Swedish: "Švedščina",
127
+ Finnish: "Finščina",
128
+ Estonian: "Estonščina",
129
+ Bulgarian: "Bolgarščina",
130
+ Czech: "Češčina",
131
+ Slovak: "Slovaščina",
132
+ Norwegian: "Norveščina",
133
+ Icelandic: "Islandščina",
126
134
 
127
135
  // Linked documents
128
136
  "Linked documents": "Povezani dokumenti",
@@ -1,4 +1,11 @@
1
- import type { AdvanceInvoice, CreditNote, DeliveryNote, Estimate, Invoice } from "@spaceinvoices/js-sdk";
1
+ import {
2
+ type AdvanceInvoice,
3
+ type CreditNote,
4
+ type DeliveryNote,
5
+ downloadBlob,
6
+ type Estimate,
7
+ type Invoice,
8
+ } from "@spaceinvoices/js-sdk";
2
9
  import { useState } from "react";
3
10
  import { useEntities } from "@/ui/providers/entities-context";
4
11
  import { useSDK } from "@/ui/providers/sdk-provider";
@@ -36,11 +43,10 @@ export function useDocumentDownload({
36
43
  const [isDownloadingEslog, setIsDownloadingEslog] = useState(false);
37
44
 
38
45
  /**
39
- * Download PDF with optional language override for translations
40
- * Formatting (decimal separators, dates) always uses entity locale.
41
- * The language param only changes labels/translations on the PDF.
46
+ * Download PDF with optional locale override.
47
+ * When a locale is selected, it is sent as both the formatting locale and label language.
42
48
  */
43
- const downloadPdf = async (document: Document, documentType: DocumentType, language?: string) => {
49
+ const downloadPdf = async (document: Document, documentType: DocumentType, locale?: string) => {
44
50
  if (!sdk || !activeEntity?.id) {
45
51
  onDownloadError?.("Download failed");
46
52
  return;
@@ -53,21 +59,11 @@ export function useDocumentDownload({
53
59
  // SDK signature: renderPdf(id, params?, SDKMethodOptions?)
54
60
  // entity_id goes in SDKMethodOptions (last arg), not params
55
61
  // Note: renderPdf is on invoices module but works with any document ID via /documents/{id}/pdf
56
- // Don't send locale entity locale drives formatting. Send language for translation override.
57
- const params = language ? { language } : {};
58
- const blob = await sdk.invoices.renderPdf(document.id, params, { entity_id: activeEntity.id });
59
- const downloadUrl = window.URL.createObjectURL(blob);
60
- const link = window.document.createElement("a");
61
- link.href = downloadUrl;
62
-
62
+ const params = locale ? { locale, language: locale } : {};
63
63
  const typeLabel = TYPE_LABELS[documentType] || "Document";
64
64
  const fileName = `${typeLabel} ${document.number}.pdf`;
65
- link.download = fileName;
66
65
 
67
- window.document.body.appendChild(link);
68
- link.click();
69
- window.document.body.removeChild(link);
70
- window.URL.revokeObjectURL(downloadUrl);
66
+ await sdk.invoices.downloadPdf(document.id, fileName, params, { entity_id: activeEntity.id });
71
67
 
72
68
  onDownloadSuccess?.(fileName);
73
69
  } catch (error) {
@@ -106,14 +102,7 @@ export function useDocumentDownload({
106
102
  const xml = await eSlogModule.download(document.id, typeMap[documentType], { entity_id: activeEntity.id });
107
103
 
108
104
  const blob = new Blob([xml], { type: "application/xml" });
109
- const url = window.URL.createObjectURL(blob);
110
- const a = window.document.createElement("a");
111
- a.href = url;
112
- a.download = `${document.number}.xml`;
113
- window.document.body.appendChild(a);
114
- a.click();
115
- window.URL.revokeObjectURL(url);
116
- window.document.body.removeChild(a);
105
+ downloadBlob(blob, `${document.number}.xml`);
117
106
  } catch (error) {
118
107
  console.error("Error downloading e-SLOG:", error);
119
108
  onDownloadError?.("e-SLOG download failed");
@@ -10,6 +10,7 @@ import { Button } from "@/ui/components/ui/button";
10
10
  import { Checkbox } from "@/ui/components/ui/checkbox";
11
11
  import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/ui/components/ui/form";
12
12
  import { type CreateEntitySchema, createEntitySchema } from "@/ui/generated/schemas";
13
+ import { createTranslation } from "@/ui/lib/translation";
13
14
 
14
15
  import ButtonLoader from "../button-loader";
15
16
  import { useCreateEntity } from "./entities.hooks";
@@ -27,10 +28,85 @@ export type CreateEntityFormProps = {
27
28
  onError?: (error: unknown) => void;
28
29
  };
29
30
 
30
- const defaultTranslate = (text: string) => text;
31
+ const translations = {
32
+ en: {
33
+ name: "Name",
34
+ "search-hint": "Search companies by name",
35
+ "no-results": "No companies found",
36
+ country: "Country",
37
+ address: "Address",
38
+ "address-2": "Address 2",
39
+ "post-code": "Post Code",
40
+ city: "City",
41
+ state: "State",
42
+ "tax-number": "Tax Number",
43
+ "is-tax-subject": "Tax subject",
44
+ "company-number": "Company Number",
45
+ submit: "Create entity",
46
+ },
47
+ } as const;
48
+
49
+ const ISO_COUNTRY_CODES = [
50
+ "AD", "AE", "AF", "AG", "AL", "AM", "AO", "AR", "AT", "AU", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI",
51
+ "BJ", "BN", "BO", "BR", "BS", "BT", "BW", "BY", "BZ", "CA", "CD", "CF", "CG", "CH", "CI", "CL", "CM", "CN", "CO",
52
+ "CR", "CU", "CV", "CY", "CZ", "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE", "EG", "ER", "ES", "ET", "FI", "FJ",
53
+ "FM", "FR", "GA", "GB", "GD", "GE", "GH", "GM", "GN", "GQ", "GR", "GT", "GW", "GY", "HK", "HN", "HR", "HT", "HU",
54
+ "ID", "IE", "IL", "IN", "IQ", "IR", "IS", "IT", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR",
55
+ "KW", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MG", "MH",
56
+ "MK", "ML", "MM", "MN", "MR", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NE", "NG", "NI", "NL", "NO", "NP",
57
+ "NR", "NZ", "OM", "PA", "PE", "PG", "PH", "PK", "PL", "PT", "PW", "PY", "QA", "RO", "RS", "RU", "RW", "SA", "SB",
58
+ "SC", "SD", "SE", "SG", "SI", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SY", "SZ", "TD", "TG", "TH",
59
+ "TJ", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "US", "UY", "UZ", "VA", "VC", "VE", "VN",
60
+ "VU", "WS", "XK", "YE", "ZA", "ZM", "ZW",
61
+ ] as const;
62
+
63
+ const COUNTRY_CODE_ALIASES: Record<string, string> = {
64
+ uk: "GB",
65
+ "u.k.": "GB",
66
+ usa: "US",
67
+ "u.s.": "US",
68
+ "u.s.a.": "US",
69
+ };
70
+
71
+ function normalizeCountryName(value: string): string {
72
+ return value
73
+ .normalize("NFKD")
74
+ .replace(/\p{Diacritic}/gu, "")
75
+ .replace(/[.'’]/g, "")
76
+ .replace(/\s+/g, " ")
77
+ .trim()
78
+ .toLowerCase();
79
+ }
80
+
81
+ function resolveCountryCodeFromName(value: string | undefined, locale: string): string | undefined {
82
+ const trimmed = value?.trim();
83
+ if (!trimmed) return undefined;
84
+
85
+ const alias = COUNTRY_CODE_ALIASES[normalizeCountryName(trimmed)];
86
+ if (alias) return alias;
87
+
88
+ const upper = trimmed.toUpperCase();
89
+ if (upper.length === 2 && ISO_COUNTRY_CODES.includes(upper as (typeof ISO_COUNTRY_CODES)[number])) {
90
+ return upper;
91
+ }
92
+
93
+ const localesToTry = Array.from(new Set([locale, "en", "en-US"]));
94
+
95
+ for (const candidateLocale of localesToTry) {
96
+ const displayNames = new Intl.DisplayNames([candidateLocale], { type: "region" });
97
+ for (const code of ISO_COUNTRY_CODES) {
98
+ const label = displayNames.of(code);
99
+ if (label && normalizeCountryName(label) === normalizeCountryName(trimmed)) {
100
+ return code;
101
+ }
102
+ }
103
+ }
104
+
105
+ return undefined;
106
+ }
31
107
 
32
108
  export function CreateEntityForm({
33
- t = defaultTranslate,
109
+ t,
34
110
  namespace = "",
35
111
  accountId,
36
112
  environment,
@@ -41,7 +117,7 @@ export function CreateEntityForm({
41
117
  onSuccess,
42
118
  onError,
43
119
  }: CreateEntityFormProps) {
44
- const translate = (key: string) => t(namespace ? `${namespace}.${key}` : key);
120
+ const translate = createTranslation({ t, namespace, locale, translations });
45
121
 
46
122
  const countryName = countryCode ? new Intl.DisplayNames([locale], { type: "region" }).of(countryCode) : undefined;
47
123
 
@@ -50,12 +126,13 @@ export function CreateEntityForm({
50
126
  const autoFilledCountryRef = useRef(countryName);
51
127
 
52
128
  // Company registry autocomplete state
129
+ // showAutocomplete is based on the initial countryCode prop to avoid component switch mid-typing
53
130
  const [nameSearch, setNameSearch] = useState("");
54
- const { isSupported: isRegistrySupported } = useIsCountrySupported(activeCountryCode || "");
131
+ const { isSupported: isRegistrySupported } = useIsCountrySupported(countryCode || "");
55
132
  const { data: searchData, isLoading: isSearching } = useCompanyRegistrySearch(activeCountryCode || "", nameSearch);
56
133
  const companies = searchData?.data || [];
57
134
 
58
- const showAutocomplete = !!activeCountryCode && isRegistrySupported;
135
+ const showAutocomplete = !!countryCode && isRegistrySupported;
59
136
 
60
137
  const nameOptions = companies.map((company) => {
61
138
  const addressParts = [company.address, company.city].filter(Boolean);
@@ -97,13 +174,14 @@ export function CreateEntityForm({
97
174
  // Watch country field — clear activeCountryCode when user edits away from auto-filled value
98
175
  const countryValue = form.watch("country");
99
176
  useEffect(() => {
100
- if (countryValue !== autoFilledCountryRef.current) {
101
- setActiveCountryCode(undefined);
102
- form.setValue("country_code", "");
103
- } else {
104
- setActiveCountryCode(countryCode);
105
- }
106
- }, [countryValue, countryCode, form]);
177
+ const nextCountryCode =
178
+ countryValue === autoFilledCountryRef.current
179
+ ? countryCode
180
+ : resolveCountryCodeFromName(countryValue, locale) || undefined;
181
+
182
+ setActiveCountryCode(nextCountryCode);
183
+ form.setValue("country_code", nextCountryCode || "");
184
+ }, [countryValue, countryCode, form, locale]);
107
185
 
108
186
  const handleCompanySelect = (company: CompanyRegistryResult) => {
109
187
  form.setValue("name", company.name);
@@ -133,9 +211,10 @@ export function CreateEntityForm({
133
211
 
134
212
  const onSubmit = async (values: CreateEntitySchema) => {
135
213
  try {
136
- // Zod validation ensures required fields are present before this is called
137
- // The type cast is safe because React Hook Form's DeepPartial doesn't reflect runtime validation
138
- createEntity(values as CreateEntityRequest);
214
+ const resolvedCountryCode = values.country_code || resolveCountryCodeFromName(values.country, locale);
215
+ const { country_code: _countryCode, ...rest } = values;
216
+ const payload = resolvedCountryCode ? { ...rest, country_code: resolvedCountryCode } : rest;
217
+ createEntity(payload as CreateEntityRequest);
139
218
  } catch (e) {
140
219
  onError?.(e);
141
220
  form.setError("root", {
@@ -259,7 +338,13 @@ export function CreateEntityForm({
259
338
  placeholder={translate("company-number")}
260
339
  />
261
340
 
262
- <Button type="submit" className="w-full cursor-pointer" disabled={isPending} aria-busy={isPending}>
341
+ <Button
342
+ type="submit"
343
+ className="w-full cursor-pointer"
344
+ disabled={isPending}
345
+ aria-busy={isPending}
346
+ data-testid="entity-create-submit"
347
+ >
263
348
  {isPending ? <ButtonLoader /> : translate("submit")}
264
349
  </Button>
265
350
  </form>
@@ -40,18 +40,18 @@ export const FinaOperatorRequiredDialog: FC<FinaOperatorRequiredDialogProps> = (
40
40
  const handleSubmit = (e: React.FormEvent) => {
41
41
  e.preventDefault();
42
42
  e.stopPropagation();
43
- if (!operatorOib || !operatorLabel) return;
43
+ if (!operatorOib) return;
44
44
  updateUserSettings({
45
45
  entityId,
46
46
  data: {
47
47
  operator_oib: operatorOib,
48
- operator_label: operatorLabel,
48
+ operator_label: operatorLabel || undefined,
49
49
  },
50
50
  });
51
51
  };
52
52
 
53
53
  const oibError = operatorOib !== "" && !/^\d{11}$/.test(operatorOib);
54
- const isValid = /^\d{11}$/.test(operatorOib) && operatorLabel.trim() !== "";
54
+ const isValid = /^\d{11}$/.test(operatorOib);
55
55
 
56
56
  return (
57
57
  <Dialog open={open} onOpenChange={onOpenChange}>
@@ -1,5 +1,5 @@
1
1
  import { AlertCircle, AlertTriangle, Building2, CheckCircle2, ChevronRight, Info, User } from "lucide-react";
2
- import { type FC, type ReactNode, useCallback, useEffect, useState } from "react";
2
+ import { type FC, type ReactNode, useEffect, useState } from "react";
3
3
  import { useUpdateEntity } from "@/ui/components/entities/entities.hooks";
4
4
  import { Alert, AlertDescription, AlertTitle } from "@/ui/components/ui/alert";
5
5
  import { Button } from "@/ui/components/ui/button";
@@ -9,11 +9,11 @@ import { PageLoadingSpinner } from "@/ui/components/ui/loading-spinner";
9
9
  import { RadioGroup, RadioGroupItem } from "@/ui/components/ui/radio-group";
10
10
  import { Separator } from "@/ui/components/ui/separator";
11
11
  import { Switch } from "@/ui/components/ui/switch";
12
- import { Tabs, TabsList, TabsTrigger } from "@/ui/components/ui/tabs";
13
- import { Tooltip, TooltipContent, TooltipTrigger } from "@/ui/components/ui/tooltip";
14
12
  import type { ComponentTranslationProps } from "@/ui/lib/translation";
15
13
  import { createTranslation } from "@/ui/lib/translation";
16
14
  import { cn } from "@/ui/lib/utils";
15
+ import { type FiscalizationStepConfig, useFiscalizationStepFlow } from "../shared/fiscalization-step-flow";
16
+ import { FiscalizationStepTabs } from "../shared/fiscalization-step-tabs";
17
17
  import {
18
18
  useFinaPremises,
19
19
  useFinaSettings,
@@ -82,9 +82,6 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
82
82
  renderSection,
83
83
  hideUserOperatorSection,
84
84
  }) => {
85
- const [activeStep, setActiveStep] = useState<FinaStepType>(initialStep);
86
- const [hasInitializedStep, setHasInitializedStep] = useState(false);
87
-
88
85
  const translate = createTranslation({
89
86
  t: translateFn,
90
87
  namespace,
@@ -92,14 +89,6 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
92
89
  translations,
93
90
  });
94
91
 
95
- const handleStepChange = useCallback(
96
- (newStep: FinaStepType) => {
97
- setActiveStep(newStep);
98
- onStepChange?.(newStep);
99
- },
100
- [onStepChange],
101
- );
102
-
103
92
  // Entity info state
104
93
  const [entityTaxNumber, setEntityTaxNumber] = useState("");
105
94
  const [entityAddress, setEntityAddress] = useState("");
@@ -201,10 +190,9 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
201
190
  // Determine completion status
202
191
  const hasEntityTaxNumber = !!entity.tax_number;
203
192
  // Operator OIB is required by CIS protocol (minOccurs="1" in FiskalizacijaSchema.xsd)
204
- // Can come from user settings or entity-level FINA settings
205
- const hasOperatorSettings =
206
- (!!userFinaSettings?.operator_oib && !!userFinaSettings?.operator_label) ||
207
- (!!finaSettings?.operator_oib && !!finaSettings?.operator_label);
193
+ // Can come from user settings or entity-level FINA settings.
194
+ // operator_label is optional metadata and must not block setup.
195
+ const hasOperatorSettings = !!userFinaSettings?.operator_oib || !!finaSettings?.operator_oib;
208
196
  const hasCertificate = finaSettings?.has_certificate || false;
209
197
  const certificateValid = finaSettings?.certificate_status === "valid";
210
198
  const hasPremises = (premises?.length || 0) > 0;
@@ -217,7 +205,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
217
205
  const canAccessEnable =
218
206
  hasEntityTaxNumber && hasOperatorSettings && certificateValid && hasPremises && hasPremiseWithDevice;
219
207
 
220
- const steps = [
208
+ const steps: FiscalizationStepConfig<FinaStepType>[] = [
221
209
  {
222
210
  id: "settings" as const,
223
211
  title: translate("General Settings"),
@@ -233,14 +221,14 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
233
221
  {
234
222
  id: "premises" as const,
235
223
  title: translate("Business Premises"),
236
- complete: !!hasPremiseWithDevice,
237
- unlocked: canAccessPremises,
224
+ complete: Boolean(hasPremiseWithDevice),
225
+ unlocked: Boolean(canAccessPremises),
238
226
  },
239
227
  {
240
228
  id: "enable" as const,
241
229
  title: translate("Enable Fiscalization"),
242
- complete: finaEnabled,
243
- unlocked: canAccessEnable,
230
+ complete: Boolean(finaEnabled),
231
+ unlocked: Boolean(canAccessEnable),
244
232
  },
245
233
  ];
246
234
 
@@ -255,27 +243,13 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
255
243
  return "settings";
256
244
  };
257
245
 
258
- // biome-ignore lint/correctness/useExhaustiveDependencies: Intentionally run only when data loads
259
- useEffect(() => {
260
- if (!hasInitializedStep && !settingsLoading && !premisesLoading) {
261
- const smartStep = getDefaultStep();
262
- if (smartStep !== activeStep) {
263
- handleStepChange(smartStep);
264
- }
265
- setHasInitializedStep(true);
266
- }
267
- }, [settingsLoading, premisesLoading, hasInitializedStep]);
268
-
269
- // biome-ignore lint/correctness/useExhaustiveDependencies: steps is recreated on each render but values are stable
270
- useEffect(() => {
271
- const currentStepInfo = steps.find((s) => s.id === activeStep);
272
- if (currentStepInfo && !currentStepInfo.unlocked) {
273
- const firstUnlockedStep = steps.find((s) => s.unlocked);
274
- if (firstUnlockedStep) {
275
- handleStepChange(firstUnlockedStep.id);
276
- }
277
- }
278
- }, [activeStep, handleStepChange]);
246
+ const { activeStep, handleStepChange } = useFiscalizationStepFlow({
247
+ initialStep,
248
+ isReady: !settingsLoading && !premisesLoading,
249
+ steps,
250
+ getDefaultStep,
251
+ onStepChange,
252
+ });
279
253
 
280
254
  if (entity.country_code !== "HR") {
281
255
  return (
@@ -322,82 +296,39 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
322
296
  return content;
323
297
  };
324
298
 
325
- const tabsNavigation = (
326
- <div className="grid items-start gap-6 lg:grid-cols-[1fr_280px]">
327
- <Tabs value={activeStep} onValueChange={(value) => handleStepChange(value as FinaStepType)} className="w-full">
328
- <TabsList className="grid w-full grid-cols-4 rounded-none p-0">
329
- {steps.map((step, index) => {
330
- const isLocked = !step.unlocked;
331
- let tooltipText = "";
332
- if (isLocked) {
333
- if (step.id === "certificate") {
334
- if (!hasEntityTaxNumber) {
335
- tooltipText = translate("Set entity OIB in General Settings first");
336
- } else {
337
- tooltipText = translate("Set operator OIB and label in General Settings first");
338
- }
339
- } else if (step.id === "premises") {
340
- if (!hasEntityTaxNumber) {
341
- tooltipText = translate("Set entity OIB in General Settings first");
342
- } else if (!hasOperatorSettings) {
343
- tooltipText = translate("Set operator OIB and label in General Settings first");
344
- } else {
345
- tooltipText = translate("Upload and validate digital certificate first");
346
- }
347
- } else if (step.id === "enable") {
348
- if (!hasEntityTaxNumber || !hasOperatorSettings) {
349
- tooltipText = translate("Complete General Settings first");
350
- } else if (!certificateValid) {
351
- tooltipText = translate("Upload and validate digital certificate first");
352
- } else if (!hasPremises) {
353
- tooltipText = translate("Register at least one business premise first");
354
- } else {
355
- tooltipText = translate("Register at least one electronic device first");
356
- }
357
- }
358
- }
359
-
360
- const trigger = (
361
- <TabsTrigger
362
- value={step.id}
363
- disabled={isLocked}
364
- className={cn("cursor-pointer justify-center", !step.unlocked && "opacity-50")}
365
- >
366
- <span className="flex items-center gap-2">
367
- {step.complete ? (
368
- <CheckCircle2 className="h-4 w-4 text-green-500" />
369
- ) : (
370
- <span className="text-xs">{index + 1}</span>
371
- )}
372
- {step.title}
373
- </span>
374
- </TabsTrigger>
375
- );
376
-
377
- if (isLocked) {
378
- return (
379
- <Tooltip key={step.id} delayDuration={0}>
380
- <TooltipTrigger asChild>
381
- <span className="flex cursor-not-allowed justify-center">{trigger}</span>
382
- </TooltipTrigger>
383
- <TooltipContent>
384
- <p>{tooltipText}</p>
385
- </TooltipContent>
386
- </Tooltip>
387
- );
388
- }
389
-
390
- return (
391
- <span key={step.id} className="flex justify-center">
392
- {trigger}
393
- </span>
394
- );
395
- })}
396
- </TabsList>
397
- </Tabs>
398
- <div className="hidden lg:block" />
399
- </div>
400
- );
299
+ const getStepTooltipText = (step: (typeof steps)[number]) => {
300
+ if (step.id === "certificate") {
301
+ if (!hasEntityTaxNumber) {
302
+ return translate("Set entity OIB in General Settings first");
303
+ }
304
+ return translate("Set operator OIB in General Settings first");
305
+ }
306
+
307
+ if (step.id === "premises") {
308
+ if (!hasEntityTaxNumber) {
309
+ return translate("Set entity OIB in General Settings first");
310
+ }
311
+ if (!hasOperatorSettings) {
312
+ return translate("Set operator OIB in General Settings first");
313
+ }
314
+ return translate("Upload and validate digital certificate first");
315
+ }
316
+
317
+ if (step.id === "enable") {
318
+ if (!hasEntityTaxNumber || !hasOperatorSettings) {
319
+ return translate("Complete General Settings first");
320
+ }
321
+ if (!certificateValid) {
322
+ return translate("Upload and validate digital certificate first");
323
+ }
324
+ if (!hasPremises) {
325
+ return translate("Register at least one business premise first");
326
+ }
327
+ return translate("Register at least one electronic device first");
328
+ }
329
+
330
+ return "";
331
+ };
401
332
 
402
333
  return (
403
334
  <div className="space-y-6">
@@ -416,7 +347,13 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
416
347
  </div>
417
348
  )}
418
349
 
419
- {tabsNavigation}
350
+ <FiscalizationStepTabs
351
+ activeStep={activeStep}
352
+ steps={steps}
353
+ onStepChange={handleStepChange}
354
+ getTooltipText={getStepTooltipText}
355
+ testIdPrefix="fina-tab"
356
+ />
420
357
 
421
358
  {/* Settings step */}
422
359
  {activeStep === "settings" && (
@@ -503,6 +440,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
503
440
  onClick={handleSaveEntityInfo}
504
441
  disabled={isEntityUpdatePending}
505
442
  className="cursor-pointer"
443
+ data-testid="fina-entity-info-save"
506
444
  >
507
445
  {isEntityUpdatePending ? translate("Saving...") : translate("Save Entity Info")}
508
446
  </Button>
@@ -568,7 +506,11 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
568
506
  )}
569
507
 
570
508
  <div className="grid items-start gap-6 lg:grid-cols-[1fr_280px]">
571
- <Button onClick={handleSaveSettings} disabled={isPending || !!operatorOibError}>
509
+ <Button
510
+ onClick={handleSaveSettings}
511
+ disabled={isPending || !!operatorOibError}
512
+ data-testid="fina-settings-save"
513
+ >
572
514
  {isPending ? translate("Saving...") : translate("Save Settings")}
573
515
  </Button>
574
516
  <div className="hidden lg:block" />
@@ -594,7 +536,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
594
536
  </div>
595
537
  </div>
596
538
 
597
- {(!userFinaSettings?.operator_oib || !userFinaSettings?.operator_label) && (
539
+ {!userFinaSettings?.operator_oib && (
598
540
  <Alert variant="destructive">
599
541
  <AlertTriangle className="h-4 w-4" />
600
542
  <AlertDescription>
@@ -617,6 +559,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
617
559
  className={cn("mt-1", userOperatorOibError && "border-destructive")}
618
560
  maxLength={11}
619
561
  disabled={userSettingsLoading}
562
+ data-testid="fina-user-operator-oib-input"
620
563
  />
621
564
  {userOperatorOibError && <p className="mt-1 text-destructive text-xs">{userOperatorOibError}</p>}
622
565
  </div>
@@ -629,6 +572,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
629
572
  placeholder={translate("e.g. Cashier 1")}
630
573
  className="mt-1"
631
574
  disabled={userSettingsLoading}
575
+ data-testid="fina-user-operator-label-input"
632
576
  />
633
577
  </div>
634
578
 
@@ -637,6 +581,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
637
581
  onClick={handleSaveUserSettings}
638
582
  disabled={isUserSettingsPending || userSettingsLoading || !!userOperatorOibError}
639
583
  className="cursor-pointer"
584
+ data-testid="fina-operator-settings-save"
640
585
  >
641
586
  {isUserSettingsPending ? translate("Saving...") : translate("Save Operator Settings")}
642
587
  </Button>
@@ -656,6 +601,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
656
601
  type="button"
657
602
  onClick={() => setIsAdvancedOpen(!isAdvancedOpen)}
658
603
  className="flex w-full items-center gap-2 py-2 text-muted-foreground hover:text-foreground"
604
+ data-testid="fina-advanced-toggle"
659
605
  >
660
606
  <ChevronRight className={cn("h-4 w-4 transition-transform", isAdvancedOpen && "rotate-90")} />
661
607
  <span className="font-medium text-sm">{translate("Advanced Settings")}</span>
@@ -683,6 +629,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
683
629
  placeholder={translate("OIB of the operator (11 digits)")}
684
630
  className={cn("mt-1", operatorOibError && "border-destructive")}
685
631
  maxLength={11}
632
+ data-testid="fina-api-operator-oib-input"
686
633
  />
687
634
  {operatorOibError && <p className="mt-1 text-destructive text-xs">{operatorOibError}</p>}
688
635
  <p className="mt-1 text-muted-foreground text-xs">
@@ -697,6 +644,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
697
644
  onChange={(e) => setFormData((prev) => ({ ...prev, operator_label: e.target.value }))}
698
645
  placeholder="API Default"
699
646
  className="mt-1"
647
+ data-testid="fina-api-operator-label-input"
700
648
  />
701
649
  <p className="mt-1 text-muted-foreground text-xs">
702
650
  {translate("Operator label for API key usage (optional)")}
@@ -704,7 +652,12 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
704
652
  </div>
705
653
  </div>
706
654
 
707
- <Button onClick={handleSaveSettings} disabled={isPending || !!operatorOibError} size="sm">
655
+ <Button
656
+ onClick={handleSaveSettings}
657
+ disabled={isPending || !!operatorOibError}
658
+ size="sm"
659
+ data-testid="fina-advanced-settings-save"
660
+ >
708
661
  {isPending ? translate("Saving...") : translate("Save Settings")}
709
662
  </Button>
710
663
  </div>
@@ -791,6 +744,7 @@ export const FinaSettingsForm: FC<FinaSettingsFormProps> = ({
791
744
  : { enabled: false },
792
745
  });
793
746
  }}
747
+ data-testid="fina-enable-switch"
794
748
  />
795
749
  <Label>{translate("Enable FINA Fiscalization")}</Label>
796
750
  </div>