@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
@@ -1,14 +1,26 @@
1
1
  "use client";
2
2
 
3
- import type { AdvanceInvoice, CreditNote, Estimate, Invoice } from "@spaceinvoices/js-sdk";
4
- import { AlertCircle, FileText, Loader2 } from "lucide-react";
5
- import { useEffect, useState } from "react";
3
+ import { getClientHeaders, type AdvanceInvoice, type CreditNote, type Estimate, type Invoice } from "@spaceinvoices/js-sdk";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { AlertCircle, FileText } from "lucide-react";
6
+ import { useEffect, useMemo } from "react";
6
7
  import { cn } from "@/ui/lib/utils";
7
8
  import { useEntitiesOptional } from "@/ui/providers/entities-context";
8
9
  import { useSDK } from "@/ui/providers/sdk-provider";
10
+ import { DocumentPreviewSkeleton } from "./document-preview-skeleton";
9
11
  import { ScaledDocumentPreview } from "./scaled-document-preview";
10
12
  import { useA4Scaling } from "./use-a4-scaling";
11
13
 
14
+ const SAVED_PREVIEW_TIMING_EVENT = "si:saved-preview-timing";
15
+ const SAVED_DOCUMENT_PREVIEW_QUERY_KEY = "document-preview-html";
16
+ const SAVED_DOCUMENT_PREVIEW_STALE_TIME = 1000 * 60 * 5;
17
+ const SAVED_DOCUMENT_PREVIEW_GC_TIME = 1000 * 60 * 30;
18
+
19
+ function emitSavedPreviewDebug(detail: Record<string, unknown>) {
20
+ if (!import.meta.env.DEV || typeof window === "undefined") return;
21
+ window.dispatchEvent(new CustomEvent(SAVED_PREVIEW_TIMING_EVENT, { detail }));
22
+ }
23
+
12
24
  type Document = Invoice | Estimate | CreditNote | AdvanceInvoice;
13
25
 
14
26
  /**
@@ -24,10 +36,36 @@ function getDocTypePathFromShareableId(shareableId: string): string {
24
36
  return "invoices";
25
37
  }
26
38
 
39
+ export function getSavedDocumentPreviewQueryKey({
40
+ documentId,
41
+ documentUpdatedAt,
42
+ entityId,
43
+ template,
44
+ isPublicView,
45
+ shareableId,
46
+ }: {
47
+ documentId: string;
48
+ documentUpdatedAt?: string | null;
49
+ entityId?: string | null;
50
+ template?: "modern" | "classic" | "condensed" | "minimal" | "fashion";
51
+ isPublicView?: boolean;
52
+ shareableId?: string | null;
53
+ }) {
54
+ return [
55
+ SAVED_DOCUMENT_PREVIEW_QUERY_KEY,
56
+ documentId,
57
+ documentUpdatedAt ?? null,
58
+ entityId ?? null,
59
+ template ?? null,
60
+ isPublicView ?? false,
61
+ shareableId ?? null,
62
+ ] as const;
63
+ }
64
+
27
65
  type DocumentPreviewDisplayProps = {
28
66
  /** The document to display (invoice, estimate, credit note, or advance invoice) */
29
67
  document: Document;
30
- template?: "modern" | "classic" | "minimal" | "fashion";
68
+ template?: "modern" | "classic" | "condensed" | "minimal" | "fashion";
31
69
  className?: string;
32
70
  apiBaseUrl?: string;
33
71
  /** Locale for document rendering (e.g., "en-US", "sl-SI"). Uses user's UI language. */
@@ -40,6 +78,8 @@ type DocumentPreviewDisplayProps = {
40
78
  t?: (key: string) => string;
41
79
  /** Document type label for display (e.g., "Invoice", "Estimate") */
42
80
  documentTypeLabel?: string;
81
+ /** Whether preview fetching is currently allowed */
82
+ fetchEnabled?: boolean;
43
83
  };
44
84
 
45
85
  /**
@@ -54,95 +94,124 @@ export function DocumentPreviewDisplay({
54
94
  template,
55
95
  className,
56
96
  apiBaseUrl,
57
- locale,
97
+ locale: _locale,
58
98
  isPublicView = false,
59
99
  shareableId,
60
100
  t: tProp,
61
101
  documentTypeLabel,
102
+ fetchEnabled = true,
62
103
  }: DocumentPreviewDisplayProps) {
63
104
  const t = tProp ?? ((key: string) => key);
64
- const [previewHtml, setPreviewHtml] = useState<string>("");
65
- const [isLoading, setIsLoading] = useState(true);
66
- const [error, setError] = useState<string | null>(null);
67
105
  const entitiesContext = useEntitiesOptional();
68
106
  const activeEntity = entitiesContext?.activeEntity;
69
107
  const { sdk } = useSDK();
108
+ const documentId = document?.id;
109
+ const documentUpdatedAt = document?.updated_at ?? null;
70
110
 
71
- const { containerRef, contentRef, scale, contentHeight, A4_WIDTH_PX } = useA4Scaling(previewHtml);
111
+ const queryKey = useMemo(() => {
112
+ return getSavedDocumentPreviewQueryKey({
113
+ documentId: documentId ?? shareableId ?? "",
114
+ documentUpdatedAt,
115
+ entityId: activeEntity?.id,
116
+ template,
117
+ isPublicView,
118
+ shareableId,
119
+ });
120
+ }, [activeEntity?.id, documentId, documentUpdatedAt, isPublicView, shareableId, template]);
72
121
 
73
- // biome-ignore lint/correctness/useExhaustiveDependencies: document.updated_at intentionally triggers re-fetch when document changes server-side (e.g. after payment)
74
- useEffect(() => {
75
- const fetchPreview = async () => {
76
- // For public view, use per-type shareable HTML endpoint
77
- if (isPublicView && shareableId && apiBaseUrl) {
78
- setIsLoading(true);
79
- setError(null);
80
- try {
81
- // Determine document type from shareable ID prefix
122
+ const prerequisitesReady = isPublicView ? !!shareableId && !!apiBaseUrl : !!documentId && !!activeEntity?.id && !!sdk;
123
+
124
+ const previewQuery = useQuery({
125
+ queryKey,
126
+ enabled: fetchEnabled && prerequisitesReady,
127
+ staleTime: SAVED_DOCUMENT_PREVIEW_STALE_TIME,
128
+ gcTime: SAVED_DOCUMENT_PREVIEW_GC_TIME,
129
+ queryFn: async ({ signal }) => {
130
+ const abortController = new AbortController();
131
+ const abortRequest = () => abortController.abort();
132
+ signal.addEventListener("abort", abortRequest, { once: true });
133
+
134
+ try {
135
+ if (isPublicView && shareableId && apiBaseUrl) {
82
136
  const docTypePath = getDocTypePathFromShareableId(shareableId);
83
- const response = await fetch(`${apiBaseUrl}/${docTypePath}/shareable/${shareableId}/html`);
137
+ const response = await fetch(`${apiBaseUrl}/${docTypePath}/shareable/${shareableId}/html`, {
138
+ headers: getClientHeaders("ui"),
139
+ signal: abortController.signal,
140
+ });
84
141
  if (!response.ok) {
85
142
  throw new Error("Failed to load preview");
86
143
  }
87
- const html = await response.text();
88
- setPreviewHtml(html);
89
- } catch (err) {
90
- setError(err instanceof Error ? err.message : "Failed to load preview");
91
- setPreviewHtml("");
92
- } finally {
93
- setIsLoading(false);
144
+ return await response.text();
94
145
  }
95
- return;
96
- }
97
146
 
98
- // Authenticated view - require entity context and SDK
99
- if (!document?.id || !activeEntity?.id || !sdk) {
100
- return;
101
- }
147
+ if (!documentId || !activeEntity?.id || !sdk) {
148
+ throw new Error("Preview context unavailable");
149
+ }
102
150
 
103
- setIsLoading(true);
104
- setError(null);
151
+ const startedAt = performance.now();
152
+ emitSavedPreviewDebug({
153
+ stage: "request_started",
154
+ documentId,
155
+ });
105
156
 
106
- try {
107
- // Fetch the rendered HTML by document ID using SDK wrapper
108
- // Document type is auto-detected from ID prefix (inv_, est_, cre_, adv_)
109
- // Don't send locale — let entity locale drive formatting (decimal separators, date format)
110
- const html = await sdk.invoices.renderHtml(document.id, { template }, { entity_id: activeEntity.id });
157
+ const html = await sdk.invoices.renderHtml(
158
+ documentId,
159
+ { template },
160
+ {
161
+ entity_id: activeEntity.id,
162
+ signal: abortController.signal,
163
+ },
164
+ );
165
+
166
+ emitSavedPreviewDebug({
167
+ stage: "request_succeeded",
168
+ documentId,
169
+ elapsedMs: Number((performance.now() - startedAt).toFixed(1)),
170
+ });
111
171
 
112
- setPreviewHtml(html);
113
- setError(null);
172
+ return html;
114
173
  } catch (err) {
115
- setError(err instanceof Error ? err.message : "Failed to load preview");
116
- setPreviewHtml("");
174
+ if (err instanceof Error && err.name === "AbortError") {
175
+ emitSavedPreviewDebug({
176
+ stage: "request_aborted",
177
+ documentId,
178
+ });
179
+ } else {
180
+ emitSavedPreviewDebug({
181
+ stage: "request_failed",
182
+ documentId,
183
+ message: err instanceof Error ? err.message : "Failed to load preview",
184
+ });
185
+ }
186
+ throw err;
117
187
  } finally {
118
- setIsLoading(false);
188
+ signal.removeEventListener("abort", abortRequest);
119
189
  }
120
- };
121
-
122
- fetchPreview();
123
- }, [
124
- document?.id,
125
- document?.updated_at,
126
- activeEntity?.id,
127
- template,
128
- apiBaseUrl,
129
- locale,
130
- isPublicView,
131
- shareableId,
132
- sdk,
133
- ]);
190
+ },
191
+ });
192
+
193
+ const previewHtml = previewQuery.data ?? "";
194
+ const error = previewQuery.error instanceof Error ? previewQuery.error.message : null;
195
+ const isWaitingForFetch = prerequisitesReady && !fetchEnabled && !previewHtml && !error;
196
+ const isLoading = previewQuery.isPending || isWaitingForFetch;
197
+ const { containerRef, contentRef, scale, contentHeight, A4_WIDTH_PX } = useA4Scaling(previewHtml);
198
+
199
+ useEffect(() => {
200
+ if (isPublicView || prerequisitesReady) return;
201
+
202
+ emitSavedPreviewDebug({
203
+ stage: "skipped",
204
+ reason: "missing_context",
205
+ documentId,
206
+ hasEntityId: !!activeEntity?.id,
207
+ hasSdk: !!sdk,
208
+ });
209
+ }, [activeEntity?.id, documentId, isPublicView, prerequisitesReady, sdk]);
134
210
 
135
211
  return (
136
- <div ref={containerRef} className={cn("relative h-full", className)}>
212
+ <div ref={containerRef} className={cn("relative min-h-full", className)}>
137
213
  {/* Loading state */}
138
- {isLoading && (
139
- <div className="flex h-full items-center justify-center rounded-lg border bg-muted/50">
140
- <div className="flex flex-col items-center gap-2">
141
- <Loader2 className="h-8 w-8 animate-spin text-primary" />
142
- <p className="text-muted-foreground text-sm">{t("Loading preview...")}</p>
143
- </div>
144
- </div>
145
- )}
214
+ {isLoading && <DocumentPreviewSkeleton />}
146
215
 
147
216
  {/* Error state */}
148
217
  {error && !isLoading && (
@@ -11,16 +11,54 @@ interface ScaledDocumentPreviewProps {
11
11
  entityUpdatedAt?: Date | null;
12
12
  }
13
13
 
14
+ /** Extract @font-face rules from CSS so they can be hoisted to the document head */
15
+ const FONT_FACE_RE = /@font-face\s*\{[^}]*\}/g;
16
+
17
+ /**
18
+ * Hoist @font-face rules from HTML <style> into the document <head>.
19
+ * Shadow DOM isolates styles, but @font-face must be at the document level to load reliably.
20
+ * Returns the HTML with @font-face rules removed from inline <style>.
21
+ */
22
+ function hoistFontFaces(html: string): string {
23
+ // Find all <style> tags and extract @font-face rules
24
+ const styleTagRe = /<style>([\s\S]*?)<\/style>/gi;
25
+ let fontFaceCss = "";
26
+ const cleanedHtml = html.replace(styleTagRe, (_match, cssContent: string) => {
27
+ const fontFaces = cssContent.match(FONT_FACE_RE);
28
+ if (fontFaces) {
29
+ fontFaceCss += fontFaces.join("\n");
30
+ }
31
+ const remaining = cssContent.replace(FONT_FACE_RE, "");
32
+ return `<style>${remaining}</style>`;
33
+ });
34
+
35
+ if (fontFaceCss) {
36
+ const id = "document-preview-fonts";
37
+ let existing = document.getElementById(id);
38
+ if (!existing) {
39
+ existing = document.createElement("style");
40
+ existing.id = id;
41
+ document.head.appendChild(existing);
42
+ }
43
+ existing.textContent = fontFaceCss;
44
+ }
45
+
46
+ return cleanedHtml;
47
+ }
48
+
14
49
  /**
15
50
  * Scaled Document Preview Component
16
51
  *
17
52
  * Renders HTML content in a Shadow DOM with A4 scaling applied using CSS transforms.
18
53
  * Uses Shadow DOM to completely isolate template CSS from the parent page.
54
+ * @font-face rules are hoisted to document <head> for reliable font loading.
19
55
  */
20
56
  export const ScaledDocumentPreview: FC<ScaledDocumentPreviewProps> = ({ htmlContent, scale, A4_WIDTH_PX }) => {
21
57
  const shadowHostRef = useRef<HTMLDivElement>(null);
22
58
  const shadowRootRef = useRef<ShadowRoot | null>(null);
23
- const [contentHeight, setContentHeight] = useState<number>(1123); // A4 height default
59
+ // A4 height in pixels at 96 DPI (297mm)
60
+ const A4_HEIGHT_PX = 1123;
61
+ const [contentHeight, setContentHeight] = useState<number>(A4_HEIGHT_PX);
24
62
 
25
63
  useEffect(() => {
26
64
  const host = shadowHostRef.current;
@@ -32,17 +70,18 @@ export const ScaledDocumentPreview: FC<ScaledDocumentPreviewProps> = ({ htmlCont
32
70
  }
33
71
 
34
72
  const shadowRoot = shadowRootRef.current;
35
- shadowRoot.innerHTML = htmlContent;
73
+ shadowRoot.innerHTML = hoistFontFaces(htmlContent);
36
74
 
37
- // Measure content height after render
75
+ // Measure content height after render (wait for fonts to load)
38
76
  const measureHeight = () => {
39
77
  const firstChild = shadowRoot.firstElementChild as HTMLElement;
40
78
  if (firstChild) {
41
- setContentHeight(firstChild.scrollHeight || 1123);
79
+ // Ensure minimum A4 page height
80
+ setContentHeight(Math.max(firstChild.scrollHeight, A4_HEIGHT_PX));
42
81
  }
43
82
  };
44
83
 
45
- setTimeout(measureHeight, 50);
84
+ setTimeout(measureHeight, 100);
46
85
  }, [htmlContent]);
47
86
 
48
87
  return (
@@ -59,6 +98,7 @@ export const ScaledDocumentPreview: FC<ScaledDocumentPreviewProps> = ({ htmlCont
59
98
  ref={shadowHostRef}
60
99
  style={{
61
100
  width: A4_WIDTH_PX,
101
+ minHeight: contentHeight,
62
102
  transform: `scale(${scale})`,
63
103
  transformOrigin: "top left",
64
104
  background: "white",