@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
@@ -145,7 +145,11 @@ export const CertificateSettingsSection: FC<CertificateSettingsSectionProps> = (
145
145
  <div className="space-y-6">
146
146
  {hasCertificate && (
147
147
  <div className="space-y-4">
148
- <Alert variant={currentStatus.variant}>
148
+ <Alert
149
+ variant={currentStatus.variant}
150
+ data-testid="fina-certificate-status"
151
+ data-status={finaSettings?.certificate_status || "unknown"}
152
+ >
149
153
  <StatusIcon className={cn("h-4 w-4", currentStatus.iconColor)} />
150
154
  <AlertTitle>Certificate Status: {currentStatus.label}</AlertTitle>
151
155
  <AlertDescription className="space-y-2">
@@ -193,6 +197,7 @@ export const CertificateSettingsSection: FC<CertificateSettingsSectionProps> = (
193
197
  onChange={handleFileChange}
194
198
  disabled={isPending}
195
199
  className="flex-1"
200
+ data-testid="fina-certificate-file-input"
196
201
  />
197
202
  {certificateFile && <CheckCircle2 className="h-5 w-5 text-green-500" />}
198
203
  </div>
@@ -212,6 +217,7 @@ export const CertificateSettingsSection: FC<CertificateSettingsSectionProps> = (
212
217
  onChange={(e) => setPassphrase(e.target.value)}
213
218
  disabled={isPending}
214
219
  placeholder={t("Enter certificate passphrase")}
220
+ data-testid="fina-certificate-passphrase-input"
215
221
  />
216
222
  </div>
217
223
 
@@ -236,6 +242,7 @@ export const CertificateSettingsSection: FC<CertificateSettingsSectionProps> = (
236
242
  onClick={handleUpload}
237
243
  disabled={!certificateFile || !passphrase || isPending}
238
244
  className={cn(hasCertificate && showUploadForm ? "flex-1" : "w-full", "cursor-pointer")}
245
+ data-testid="fina-certificate-upload-submit"
239
246
  >
240
247
  <Upload className="mr-2 h-4 w-4" />
241
248
  {isPending ? t("Loading...") : hasCertificate ? t("Upload New Certificate") : t("Upload Certificate")}
@@ -151,7 +151,12 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
151
151
  </Alert>
152
152
 
153
153
  {/* Add Premise Button */}
154
- <Button onClick={() => setRegisterDialogOpen(true)} variant="default" className="cursor-pointer">
154
+ <Button
155
+ onClick={() => setRegisterDialogOpen(true)}
156
+ variant="default"
157
+ className="cursor-pointer"
158
+ data-testid="fina-add-premise"
159
+ >
155
160
  <Building2 className="mr-2 h-4 w-4" />
156
161
  {t("Add Premise")}
157
162
  </Button>
@@ -189,7 +194,11 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
189
194
  </Button>
190
195
  </DropdownMenuTrigger>
191
196
  <DropdownMenuContent align="end">
192
- <DropdownMenuItem onClick={() => handleAddDevice(premise.id)} className="cursor-pointer">
197
+ <DropdownMenuItem
198
+ onClick={() => handleAddDevice(premise.id)}
199
+ className="cursor-pointer"
200
+ data-testid={`fina-add-device-${premise.business_premise_name}`}
201
+ >
193
202
  <Cpu className="mr-2 h-4 w-4" />
194
203
  {t("Add Electronic Device")}
195
204
  </DropdownMenuItem>
@@ -245,6 +254,7 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
245
254
  variant="outline"
246
255
  onClick={() => handleAddDevice(premise.id)}
247
256
  className="cursor-pointer"
257
+ data-testid={`fina-add-device-${premise.business_premise_name}`}
248
258
  >
249
259
  <Cpu className="mr-2 h-4 w-4" />
250
260
  {t("Add Electronic Device")}
@@ -307,6 +317,7 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
307
317
  handleRegisterDevice();
308
318
  }
309
319
  }}
320
+ data-testid="fina-device-id-input"
310
321
  />
311
322
  </div>
312
323
  </div>
@@ -324,6 +335,7 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
324
335
  onClick={handleRegisterDevice}
325
336
  disabled={!deviceId.trim() || isRegisteringDevice}
326
337
  className="cursor-pointer"
338
+ data-testid="fina-register-device-submit"
327
339
  >
328
340
  {isRegisteringDevice ? t("Registering...") : t("Register Device")}
329
341
  </Button>
@@ -93,7 +93,7 @@ export const RegisterFinaPremiseDialog: FC<RegisterFinaPremiseDialogProps> = ({
93
93
  <FormItem>
94
94
  <FormLabel>{t("Premise ID")}</FormLabel>
95
95
  <FormControl>
96
- <Input placeholder="PP1" {...field} />
96
+ <Input placeholder="PP1" {...field} data-testid="fina-premise-name-input" />
97
97
  </FormControl>
98
98
  <FormDescription>{t("Unique identifier for this premise (e.g., PP1, OFFICE1)")}</FormDescription>
99
99
  <FormMessage />
@@ -111,7 +111,12 @@ export const RegisterFinaPremiseDialog: FC<RegisterFinaPremiseDialogProps> = ({
111
111
  >
112
112
  {t("Cancel")}
113
113
  </Button>
114
- <Button type="submit" disabled={isPending} className="cursor-pointer">
114
+ <Button
115
+ type="submit"
116
+ disabled={isPending}
117
+ className="cursor-pointer"
118
+ data-testid="fina-register-premise-submit"
119
+ >
115
120
  {isPending ? t("Adding...") : t("Add Premise")}
116
121
  </Button>
117
122
  </DialogFooter>
@@ -1,19 +1,18 @@
1
1
  import { zodResolver } from "@hookform/resolvers/zod";
2
2
  import type { Entity } from "@spaceinvoices/js-sdk";
3
- import { AlertCircle, CheckCircle2, Info } from "lucide-react";
4
- import { type FC, type ReactNode, useCallback, useEffect, useState } from "react";
3
+ import { AlertCircle, Info } from "lucide-react";
4
+ import type { FC, ReactNode } from "react";
5
5
  import { useForm } from "react-hook-form";
6
6
  import type { z } from "zod";
7
7
  import { Alert, AlertDescription, AlertTitle } from "@/ui/components/ui/alert";
8
8
  import { Form } from "@/ui/components/ui/form";
9
9
  import { PageLoadingSpinner } from "@/ui/components/ui/loading-spinner";
10
- import { Tabs, TabsList, TabsTrigger } from "@/ui/components/ui/tabs";
11
- import { Tooltip, TooltipContent, TooltipTrigger } from "@/ui/components/ui/tooltip";
12
10
  import { updateFursSettingsSchema } from "@/ui/generated/schemas";
13
11
  import type { ComponentTranslationProps } from "@/ui/lib/translation";
14
12
  import { createTranslation } from "@/ui/lib/translation";
15
- import { cn } from "@/ui/lib/utils";
16
13
  import { useFormFooterRegistration } from "@/ui/providers/form-footer-context";
14
+ import { type FiscalizationStepConfig, useFiscalizationStepFlow } from "../shared/fiscalization-step-flow";
15
+ import { FiscalizationStepTabs } from "../shared/fiscalization-step-tabs";
17
16
  import { useFursPremises, useFursSettings, useUpdateFursSettings, useUserFursSettings } from "./furs-settings.hooks";
18
17
  // Import locale files
19
18
  import de from "./locales/de";
@@ -99,10 +98,6 @@ export const FursSettingsForm: FC<FursSettingsFormProps> = ({
99
98
  renderSection,
100
99
  hideUserOperatorSection,
101
100
  }) => {
102
- // Step navigation state (can be controlled via props for URL sync)
103
- const [activeStep, setActiveStep] = useState<StepType>(initialStep);
104
- const [hasInitializedStep, setHasInitializedStep] = useState(false);
105
-
106
101
  // Create a guaranteed translation function using the createTranslation utility
107
102
  const translate = createTranslation({
108
103
  t: translateFn,
@@ -111,15 +106,6 @@ export const FursSettingsForm: FC<FursSettingsFormProps> = ({
111
106
  translations,
112
107
  });
113
108
 
114
- // Handle step changes
115
- const handleStepChange = useCallback(
116
- (newStep: StepType) => {
117
- setActiveStep(newStep);
118
- onStepChange?.(newStep); // Notify parent (e.g., for URL updates)
119
- },
120
- [onStepChange],
121
- );
122
-
123
109
  // Fetch FURS settings and premises
124
110
  const { data: fursSettings, isLoading: settingsLoading } = useFursSettings(entity.id);
125
111
  const { data: premises, isLoading: premisesLoading } = useFursPremises(entity.id);
@@ -178,7 +164,7 @@ export const FursSettingsForm: FC<FursSettingsFormProps> = ({
178
164
  const canAccessEnable =
179
165
  hasEntityTaxNumber && hasOperatorSettings && certificateValid && hasPremises && hasPremiseWithDevice;
180
166
 
181
- const steps = [
167
+ const steps: FiscalizationStepConfig<StepType>[] = [
182
168
  {
183
169
  id: "settings" as const,
184
170
  title: translate("General Settings"),
@@ -194,14 +180,14 @@ export const FursSettingsForm: FC<FursSettingsFormProps> = ({
194
180
  {
195
181
  id: "premises" as const,
196
182
  title: translate("Business Premises"),
197
- complete: hasPremiseWithDevice,
198
- unlocked: canAccessPremises,
183
+ complete: Boolean(hasPremiseWithDevice),
184
+ unlocked: Boolean(canAccessPremises),
199
185
  },
200
186
  {
201
187
  id: "enable" as const,
202
188
  title: translate("Enable Fiscalization"),
203
- complete: fursEnabled,
204
- unlocked: canAccessEnable,
189
+ complete: Boolean(fursEnabled),
190
+ unlocked: Boolean(canAccessEnable),
205
191
  },
206
192
  ];
207
193
 
@@ -222,30 +208,13 @@ export const FursSettingsForm: FC<FursSettingsFormProps> = ({
222
208
  return "settings";
223
209
  };
224
210
 
225
- // Smart initial step selection on first load (when data is ready)
226
- // biome-ignore lint/correctness/useExhaustiveDependencies: Intentionally run only when data loads, not on every dep change
227
- useEffect(() => {
228
- if (!hasInitializedStep && !settingsLoading && !premisesLoading) {
229
- const smartStep = getDefaultStep();
230
- if (smartStep !== activeStep) {
231
- handleStepChange(smartStep);
232
- }
233
- setHasInitializedStep(true);
234
- }
235
- }, [settingsLoading, premisesLoading, hasInitializedStep]);
236
-
237
- // Validate step and redirect if current step becomes locked
238
- // biome-ignore lint/correctness/useExhaustiveDependencies: steps is recreated on each render but values are stable
239
- useEffect(() => {
240
- const currentStepInfo = steps.find((s) => s.id === activeStep);
241
- if (currentStepInfo && !currentStepInfo.unlocked) {
242
- // If current step is locked, redirect to first unlocked step
243
- const firstUnlockedStep = steps.find((s) => s.unlocked);
244
- if (firstUnlockedStep) {
245
- handleStepChange(firstUnlockedStep.id);
246
- }
247
- }
248
- }, [activeStep, handleStepChange]);
211
+ const { activeStep, handleStepChange } = useFiscalizationStepFlow({
212
+ initialStep,
213
+ isReady: !settingsLoading && !premisesLoading,
214
+ steps,
215
+ getDefaultStep,
216
+ onStepChange,
217
+ });
249
218
 
250
219
  // Check if entity is Slovenian
251
220
  if (entity.country_code !== "SI") {
@@ -274,88 +243,39 @@ export const FursSettingsForm: FC<FursSettingsFormProps> = ({
274
243
  // Check if entity is in sandbox (test) mode
275
244
  const isSandboxMode = entity.environment === "sandbox";
276
245
 
277
- // Shared tabs navigation component - in left column only
278
- const tabsNavigation = (
279
- <div className="grid items-start gap-6 lg:grid-cols-[1fr_280px]">
280
- <Tabs
281
- value={activeStep}
282
- onValueChange={(value) => handleStepChange(value as typeof activeStep)}
283
- className="w-full"
284
- >
285
- <TabsList className="grid w-full grid-cols-4 rounded-none p-0">
286
- {steps.map((step, index) => {
287
- const isLocked = !step.unlocked;
288
- let tooltipText = "";
289
-
290
- if (isLocked) {
291
- if (step.id === "certificate") {
292
- if (!hasEntityTaxNumber) {
293
- tooltipText = translate("Set entity tax number in General Settings first");
294
- } else {
295
- tooltipText = translate("Set operator tax number and label in General Settings first");
296
- }
297
- } else if (step.id === "premises") {
298
- if (!hasEntityTaxNumber) {
299
- tooltipText = translate("Set entity tax number in General Settings first");
300
- } else if (!hasOperatorSettings) {
301
- tooltipText = translate("Set operator tax number and label in General Settings first");
302
- } else {
303
- tooltipText = translate("Upload and validate digital certificate first");
304
- }
305
- } else if (step.id === "enable") {
306
- if (!hasEntityTaxNumber || !hasOperatorSettings) {
307
- tooltipText = translate("Complete General Settings first");
308
- } else if (!certificateValid) {
309
- tooltipText = translate("Upload and validate digital certificate first");
310
- } else if (!hasPremises) {
311
- tooltipText = translate("Register at least one business premise first");
312
- } else {
313
- tooltipText = translate("Register at least one electronic device first");
314
- }
315
- }
316
- }
317
-
318
- const trigger = (
319
- <TabsTrigger
320
- value={step.id}
321
- disabled={isLocked}
322
- className={cn("cursor-pointer justify-center", !step.unlocked && "opacity-50")}
323
- >
324
- <span className="flex items-center gap-2">
325
- {step.complete ? (
326
- <CheckCircle2 className="h-4 w-4 text-green-500" />
327
- ) : (
328
- <span className="text-xs">{index + 1}</span>
329
- )}
330
- {step.title}
331
- </span>
332
- </TabsTrigger>
333
- );
334
-
335
- if (isLocked) {
336
- return (
337
- <Tooltip key={step.id} delayDuration={0}>
338
- <TooltipTrigger asChild>
339
- <span className="flex cursor-not-allowed justify-center">{trigger}</span>
340
- </TooltipTrigger>
341
- <TooltipContent>
342
- <p>{tooltipText}</p>
343
- </TooltipContent>
344
- </Tooltip>
345
- );
346
- }
347
-
348
- return (
349
- <span key={step.id} className="flex justify-center">
350
- {trigger}
351
- </span>
352
- );
353
- })}
354
- </TabsList>
355
- </Tabs>
356
- <div className="hidden lg:block" />
357
- </div>
358
- );
246
+ const getStepTooltipText = (step: (typeof steps)[number]) => {
247
+ if (step.id === "certificate") {
248
+ if (!hasEntityTaxNumber) {
249
+ return translate("Set entity tax number in General Settings first");
250
+ }
251
+ return translate("Set operator tax number and label in General Settings first");
252
+ }
253
+
254
+ if (step.id === "premises") {
255
+ if (!hasEntityTaxNumber) {
256
+ return translate("Set entity tax number in General Settings first");
257
+ }
258
+ if (!hasOperatorSettings) {
259
+ return translate("Set operator tax number and label in General Settings first");
260
+ }
261
+ return translate("Upload and validate digital certificate first");
262
+ }
263
+
264
+ if (step.id === "enable") {
265
+ if (!hasEntityTaxNumber || !hasOperatorSettings) {
266
+ return translate("Complete General Settings first");
267
+ }
268
+ if (!certificateValid) {
269
+ return translate("Upload and validate digital certificate first");
270
+ }
271
+ if (!hasPremises) {
272
+ return translate("Register at least one business premise first");
273
+ }
274
+ return translate("Register at least one electronic device first");
275
+ }
276
+
277
+ return "";
278
+ };
359
279
 
360
280
  // Helper to wrap section content with render prop if provided
361
281
  const wrapSection = (section: SectionType, content: ReactNode) => {
@@ -385,7 +305,13 @@ export const FursSettingsForm: FC<FursSettingsFormProps> = ({
385
305
  )}
386
306
 
387
307
  {/* Tabs navigation */}
388
- {tabsNavigation}
308
+ <FiscalizationStepTabs
309
+ activeStep={activeStep}
310
+ steps={steps}
311
+ onStepChange={handleStepChange}
312
+ getTooltipText={getStepTooltipText}
313
+ testIdPrefix="furs-tab"
314
+ />
389
315
 
390
316
  {/* Step content - each section wrapped with its own help */}
391
317
  {activeStep === "settings" && (
@@ -171,7 +171,11 @@ export const CertificateSettingsSection: FC<CertificateSettingsSectionProps> = (
171
171
  {/* Certificate Status - Show when certificate exists */}
172
172
  {hasCertificate && (
173
173
  <div className="space-y-4">
174
- <Alert variant={currentStatus.variant}>
174
+ <Alert
175
+ variant={currentStatus.variant}
176
+ data-testid="furs-certificate-status"
177
+ data-status={fursSettings?.certificate_status || "unknown"}
178
+ >
175
179
  <StatusIcon className={cn("h-4 w-4", currentStatus.iconColor)} />
176
180
  <AlertTitle>Certificate Status: {currentStatus.label}</AlertTitle>
177
181
  <AlertDescription className="space-y-2">
@@ -221,6 +225,7 @@ export const CertificateSettingsSection: FC<CertificateSettingsSectionProps> = (
221
225
  onChange={handleFileChange}
222
226
  disabled={isPending}
223
227
  className="flex-1"
228
+ data-testid="furs-certificate-file-input"
224
229
  />
225
230
  {certificateFile && <CheckCircle2 className="h-5 w-5 text-green-500" />}
226
231
  </div>
@@ -240,6 +245,7 @@ export const CertificateSettingsSection: FC<CertificateSettingsSectionProps> = (
240
245
  onChange={(e) => setPassphrase(e.target.value)}
241
246
  disabled={isPending}
242
247
  placeholder={t("Enter certificate passphrase")}
248
+ data-testid="furs-certificate-passphrase-input"
243
249
  />
244
250
  </div>
245
251
 
@@ -264,6 +270,7 @@ export const CertificateSettingsSection: FC<CertificateSettingsSectionProps> = (
264
270
  onClick={handleUpload}
265
271
  disabled={!certificateFile || !passphrase || isPending}
266
272
  className={cn(hasCertificate && showUploadForm ? "flex-1" : "w-full", "cursor-pointer")}
273
+ data-testid="furs-certificate-upload-submit"
267
274
  >
268
275
  <Upload className="mr-2 h-4 w-4" />
269
276
  {isPending ? t("Loading...") : hasCertificate ? t("Upload New Certificate") : t("Upload Certificate")}
@@ -116,6 +116,7 @@ export const EnableFiscalizationSection: FC<EnableFiscalizationSectionProps> = (
116
116
  onCheckedChange={field.onChange}
117
117
  disabled={!allPrerequisitesMet && !field.value}
118
118
  className="cursor-pointer"
119
+ data-testid="furs-enable-switch"
119
120
  />
120
121
  </FormControl>
121
122
  </FormItem>
@@ -175,6 +175,7 @@ export const GeneralSettingsSection: FC<GeneralSettingsSectionProps> = ({
175
175
  onClick={handleSaveEntityInfo}
176
176
  disabled={isEntityUpdatePending}
177
177
  className="cursor-pointer"
178
+ data-testid="furs-entity-info-save"
178
179
  >
179
180
  {isEntityUpdatePending ? t("Saving...") : t("Save Entity Info")}
180
181
  </Button>
@@ -240,6 +241,7 @@ export const GeneralSettingsSection: FC<GeneralSettingsSectionProps> = ({
240
241
  onClick={handleSaveUserSettings}
241
242
  disabled={isUserSettingsPending || userSettingsLoading}
242
243
  className="cursor-pointer"
244
+ data-testid="furs-operator-settings-save"
243
245
  >
244
246
  {isUserSettingsPending ? t("Saving...") : t("Save Operator Settings")}
245
247
  </Button>
@@ -320,6 +322,7 @@ export const GeneralSettingsSection: FC<GeneralSettingsSectionProps> = ({
320
322
  type="button"
321
323
  onClick={() => setIsAdvancedOpen(!isAdvancedOpen)}
322
324
  className="flex w-full items-center gap-2 py-2 text-muted-foreground hover:text-foreground"
325
+ data-testid="furs-advanced-toggle"
323
326
  >
324
327
  <ChevronRight className={cn("h-4 w-4 transition-transform", isAdvancedOpen && "rotate-90")} />
325
328
  <span className="font-medium text-sm">{t("Advanced Settings")}</span>
@@ -342,7 +345,12 @@ export const GeneralSettingsSection: FC<GeneralSettingsSectionProps> = ({
342
345
  <FormItem>
343
346
  <FormLabel className="text-sm">{t("Operator Tax Number")}</FormLabel>
344
347
  <FormControl>
345
- <Input placeholder="12345678" {...field} className="h-10" />
348
+ <Input
349
+ placeholder="12345678"
350
+ {...field}
351
+ className="h-10"
352
+ data-testid="furs-api-operator-tax-number-input"
353
+ />
346
354
  </FormControl>
347
355
  <FormDescription className="text-xs">
348
356
  {t("Tax number for API key usage (optional)")}
@@ -359,7 +367,12 @@ export const GeneralSettingsSection: FC<GeneralSettingsSectionProps> = ({
359
367
  <FormItem>
360
368
  <FormLabel className="text-sm">{t("Operator Label")}</FormLabel>
361
369
  <FormControl>
362
- <Input placeholder="API Default" {...field} className="h-10" />
370
+ <Input
371
+ placeholder="API Default"
372
+ {...field}
373
+ className="h-10"
374
+ data-testid="furs-api-operator-label-input"
375
+ />
363
376
  </FormControl>
364
377
  <FormDescription className="text-xs">
365
378
  {t("Operator label for API key usage (optional)")}
@@ -150,11 +150,21 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
150
150
  <div className="space-y-6">
151
151
  {/* Add Premise Buttons */}
152
152
  <div className="flex gap-3">
153
- <Button onClick={() => handleAddPremise("real-estate")} variant="default" className="flex-1 cursor-pointer">
153
+ <Button
154
+ onClick={() => handleAddPremise("real-estate")}
155
+ variant="default"
156
+ className="flex-1 cursor-pointer"
157
+ data-testid="furs-add-real-estate-premise"
158
+ >
154
159
  <Building2 className="mr-2 h-4 w-4" />
155
160
  {t("Add Real Estate")}
156
161
  </Button>
157
- <Button onClick={() => handleAddPremise("movable")} variant="outline" className="flex-1 cursor-pointer">
162
+ <Button
163
+ onClick={() => handleAddPremise("movable")}
164
+ variant="outline"
165
+ className="flex-1 cursor-pointer"
166
+ data-testid="furs-add-movable-premise"
167
+ >
158
168
  <Truck className="mr-2 h-4 w-4" />
159
169
  {t("Add Movable")}
160
170
  </Button>
@@ -196,7 +206,11 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
196
206
  </Button>
197
207
  </DropdownMenuTrigger>
198
208
  <DropdownMenuContent align="end">
199
- <DropdownMenuItem onClick={() => handleAddDevice(premise.id)} className="cursor-pointer">
209
+ <DropdownMenuItem
210
+ onClick={() => handleAddDevice(premise.id)}
211
+ className="cursor-pointer"
212
+ data-testid={`furs-add-device-${premise.business_premise_name}`}
213
+ >
200
214
  <Cpu className="mr-2 h-4 w-4" />
201
215
  {t("Add Electronic Device")}
202
216
  </DropdownMenuItem>
@@ -274,6 +288,7 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
274
288
  variant="outline"
275
289
  onClick={() => handleAddDevice(premise.id)}
276
290
  className="cursor-pointer"
291
+ data-testid={`furs-add-device-${premise.business_premise_name}`}
277
292
  >
278
293
  <Cpu className="mr-2 h-4 w-4" />
279
294
  {t("Add Electronic Device")}
@@ -337,6 +352,7 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
337
352
  handleRegisterDevice();
338
353
  }
339
354
  }}
355
+ data-testid="furs-device-name-input"
340
356
  />
341
357
  <p className="text-muted-foreground text-sm">
342
358
  {t("Enter a unique name for this device (e.g., E1, E2, POS1, DEVICE1)")}
@@ -357,6 +373,7 @@ export const PremisesManagementSection: FC<PremisesManagementSectionProps> = ({
357
373
  onClick={handleRegisterDevice}
358
374
  disabled={!deviceName.trim() || isRegisteringDevice}
359
375
  className="cursor-pointer"
376
+ data-testid="furs-register-device-submit"
360
377
  >
361
378
  {isRegisteringDevice ? t("Registering...") : t("Register Device")}
362
379
  </Button>