@spaceinvoices/react-ui 0.1.1

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 (352) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +340 -0
  3. package/cli/dist/index.js +922 -0
  4. package/package.json +87 -0
  5. package/registry.json +600 -0
  6. package/spaceinvoices.schema.json +47 -0
  7. package/src/app.tsx +25 -0
  8. package/src/common/autocomplete.tsx +135 -0
  9. package/src/components/activities/activity-timeline.tsx +160 -0
  10. package/src/components/activities/index.ts +1 -0
  11. package/src/components/activities/locales/de.ts +30 -0
  12. package/src/components/activities/locales/sl.ts +30 -0
  13. package/src/components/advance-invoices/advance-invoices.hooks.ts +75 -0
  14. package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +702 -0
  15. package/src/components/advance-invoices/create/locales/de.ts +29 -0
  16. package/src/components/advance-invoices/create/locales/sl.ts +25 -0
  17. package/src/components/advance-invoices/create/prepare-advance-invoice-submission.ts +74 -0
  18. package/src/components/advance-invoices/index.ts +5 -0
  19. package/src/components/advance-invoices/list/index.ts +3 -0
  20. package/src/components/advance-invoices/list/list-row-actions.tsx +119 -0
  21. package/src/components/advance-invoices/list/list-table.tsx +178 -0
  22. package/src/components/advance-invoices/list/locales/de.ts +32 -0
  23. package/src/components/advance-invoices/list/locales/sl.ts +32 -0
  24. package/src/components/advance-invoices/list/use-advance-invoice-download.ts +63 -0
  25. package/src/components/button-loader.tsx +11 -0
  26. package/src/components/combobox.tsx +96 -0
  27. package/src/components/company-registry/company-registry-autocomplete.tsx +151 -0
  28. package/src/components/company-registry/company-registry.hooks.ts +67 -0
  29. package/src/components/company-registry/index.ts +7 -0
  30. package/src/components/credit-notes/create/create-credit-note-form.tsx +332 -0
  31. package/src/components/credit-notes/create/index.ts +1 -0
  32. package/src/components/credit-notes/create/locales/de.ts +69 -0
  33. package/src/components/credit-notes/create/locales/sl.ts +67 -0
  34. package/src/components/credit-notes/credit-notes.hooks.ts +22 -0
  35. package/src/components/credit-notes/index.ts +10 -0
  36. package/src/components/credit-notes/list/index.ts +3 -0
  37. package/src/components/credit-notes/list/list-row-actions.tsx +116 -0
  38. package/src/components/credit-notes/list/list-table.tsx +183 -0
  39. package/src/components/credit-notes/list/locales/de.ts +33 -0
  40. package/src/components/credit-notes/list/locales/sl.ts +33 -0
  41. package/src/components/credit-notes/list/use-credit-note-download.ts +65 -0
  42. package/src/components/customers/create-customer-form/create-customer-form.tsx +134 -0
  43. package/src/components/customers/create-customer-form/locales/de.ts +20 -0
  44. package/src/components/customers/create-customer-form/locales/sl.ts +20 -0
  45. package/src/components/customers/customer-autocomplete.tsx +173 -0
  46. package/src/components/customers/customer-combobox.tsx +130 -0
  47. package/src/components/customers/customer-list-table/customer-list-row-actions.tsx +48 -0
  48. package/src/components/customers/customer-list-table/customer-list-table.tsx +124 -0
  49. package/src/components/customers/customer-list-table/index.ts +2 -0
  50. package/src/components/customers/customer-list-table/locales/de.ts +16 -0
  51. package/src/components/customers/customer-list-table/locales/sl.ts +16 -0
  52. package/src/components/customers/customers.hooks.test.ts +348 -0
  53. package/src/components/customers/customers.hooks.ts +57 -0
  54. package/src/components/customers/index.ts +5 -0
  55. package/src/components/dashboard/chart-empty-state.tsx +29 -0
  56. package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +80 -0
  57. package/src/components/dashboard/collection-rate-card/index.ts +4 -0
  58. package/src/components/dashboard/collection-rate-card/locales/sl.ts +3 -0
  59. package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +74 -0
  60. package/src/components/dashboard/index.ts +54 -0
  61. package/src/components/dashboard/invoice-status-chart/index.ts +4 -0
  62. package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +130 -0
  63. package/src/components/dashboard/invoice-status-chart/locales/sl.ts +9 -0
  64. package/src/components/dashboard/invoice-status-chart/use-invoice-status.ts +105 -0
  65. package/src/components/dashboard/loading-card.tsx +19 -0
  66. package/src/components/dashboard/payment-methods-chart/index.ts +4 -0
  67. package/src/components/dashboard/payment-methods-chart/locales/sl.ts +12 -0
  68. package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +152 -0
  69. package/src/components/dashboard/payment-methods-chart/use-payment-methods.ts +50 -0
  70. package/src/components/dashboard/payment-trend-chart/index.ts +4 -0
  71. package/src/components/dashboard/payment-trend-chart/locales/sl.ts +5 -0
  72. package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +137 -0
  73. package/src/components/dashboard/payment-trend-chart/use-payment-trend.ts +92 -0
  74. package/src/components/dashboard/revenue-card.tsx +49 -0
  75. package/src/components/dashboard/revenue-trend-chart/index.ts +4 -0
  76. package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +5 -0
  77. package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +137 -0
  78. package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +93 -0
  79. package/src/components/dashboard/shared/index.ts +5 -0
  80. package/src/components/dashboard/shared/use-revenue-data.ts +160 -0
  81. package/src/components/dashboard/shared/use-stats-counts.ts +89 -0
  82. package/src/components/dashboard/shared/use-stats-query.ts +38 -0
  83. package/src/components/dashboard/stat-card.tsx +41 -0
  84. package/src/components/dashboard/tax-collected-card/index.ts +2 -0
  85. package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +77 -0
  86. package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +145 -0
  87. package/src/components/dashboard/top-customers-chart/index.ts +4 -0
  88. package/src/components/dashboard/top-customers-chart/locales/sl.ts +5 -0
  89. package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +130 -0
  90. package/src/components/dashboard/top-customers-chart/use-top-customers.ts +72 -0
  91. package/src/components/documents/create/document-add-item-form.tsx +379 -0
  92. package/src/components/documents/create/document-add-item-tax-rate-field.tsx +120 -0
  93. package/src/components/documents/create/document-details-section.tsx +597 -0
  94. package/src/components/documents/create/document-items-section.tsx +133 -0
  95. package/src/components/documents/create/document-recipient-section.tsx +101 -0
  96. package/src/components/documents/create/form-types.ts +36 -0
  97. package/src/components/documents/create/index.ts +9 -0
  98. package/src/components/documents/create/live-preview.tsx +235 -0
  99. package/src/components/documents/create/mark-as-paid-section.tsx +82 -0
  100. package/src/components/documents/create/prepare-document-submission.test.ts +132 -0
  101. package/src/components/documents/create/prepare-document-submission.ts +187 -0
  102. package/src/components/documents/create/prepare-preview-data.test.ts +155 -0
  103. package/src/components/documents/create/prepare-preview-data.ts +16 -0
  104. package/src/components/documents/create/smart-code-insert-button.tsx +139 -0
  105. package/src/components/documents/create/use-document-customer-form.ts +161 -0
  106. package/src/components/documents/document-preview.tsx +13 -0
  107. package/src/components/documents/documents.hooks.ts +146 -0
  108. package/src/components/documents/index.ts +23 -0
  109. package/src/components/documents/shared/document-preview-display.tsx +172 -0
  110. package/src/components/documents/shared/index.ts +3 -0
  111. package/src/components/documents/shared/scaled-document-preview.tsx +70 -0
  112. package/src/components/documents/shared/use-a4-scaling.ts +62 -0
  113. package/src/components/documents/types.ts +61 -0
  114. package/src/components/documents/view/document-actions-bar.tsx +328 -0
  115. package/src/components/documents/view/document-details-card.tsx +179 -0
  116. package/src/components/documents/view/document-payments-list.tsx +256 -0
  117. package/src/components/documents/view/index.ts +4 -0
  118. package/src/components/documents/view/locales/de.ts +85 -0
  119. package/src/components/documents/view/locales/sl.ts +84 -0
  120. package/src/components/documents/view/use-document-download.ts +125 -0
  121. package/src/components/entities/create-entity-form.tsx +105 -0
  122. package/src/components/entities/entities.hooks.ts +50 -0
  123. package/src/components/entities/entity-settings-form/email-template-variables-info.tsx +103 -0
  124. package/src/components/entities/entity-settings-form/entity-settings-form.tsx +1326 -0
  125. package/src/components/entities/entity-settings-form/image-upload-with-crop.tsx +222 -0
  126. package/src/components/entities/entity-settings-form/index.ts +2 -0
  127. package/src/components/entities/entity-settings-form/input-with-preview.tsx +190 -0
  128. package/src/components/entities/entity-settings-form/locales/de.ts +192 -0
  129. package/src/components/entities/entity-settings-form/locales/sl.ts +188 -0
  130. package/src/components/entities/furs-settings-form/furs-settings-form.tsx +410 -0
  131. package/src/components/entities/furs-settings-form/furs-settings.hooks.ts +320 -0
  132. package/src/components/entities/furs-settings-form/index.ts +3 -0
  133. package/src/components/entities/furs-settings-form/locales/de.ts +233 -0
  134. package/src/components/entities/furs-settings-form/locales/en.ts +194 -0
  135. package/src/components/entities/furs-settings-form/locales/sl.ts +196 -0
  136. package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +242 -0
  137. package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +139 -0
  138. package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +252 -0
  139. package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +370 -0
  140. package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +420 -0
  141. package/src/components/entities/keys.ts +2 -0
  142. package/src/components/entities/settings/branding-settings-form.tsx +274 -0
  143. package/src/components/entities/settings/company-settings-form.tsx +256 -0
  144. package/src/components/entities/settings/defaults-settings-form.tsx +501 -0
  145. package/src/components/entities/settings/email-settings-form.tsx +288 -0
  146. package/src/components/entities/settings/eslog-settings-form.tsx +113 -0
  147. package/src/components/entities/settings/index.ts +8 -0
  148. package/src/components/entities/settings/number-format-settings-form.tsx +244 -0
  149. package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +164 -0
  150. package/src/components/entities/settings/pdf-template-selector/index.ts +2 -0
  151. package/src/components/entities/settings/pdf-template-selector/locales/de.ts +18 -0
  152. package/src/components/entities/settings/pdf-template-selector/locales/sl.ts +18 -0
  153. package/src/components/entities/settings/pdf-template-selector/pdf-template-cards.tsx +49 -0
  154. package/src/components/entities/settings/settings-footer.tsx +16 -0
  155. package/src/components/entities/settings/tax-rules-settings-form.tsx +346 -0
  156. package/src/components/estimates/create/create-estimate-form.tsx +384 -0
  157. package/src/components/estimates/create/locales/de.ts +64 -0
  158. package/src/components/estimates/create/locales/sl.ts +63 -0
  159. package/src/components/estimates/create/prepare-estimate-submission.ts +39 -0
  160. package/src/components/estimates/create/use-estimate-customer-form.ts +5 -0
  161. package/src/components/estimates/estimates.hooks.ts +15 -0
  162. package/src/components/estimates/index.ts +6 -0
  163. package/src/components/estimates/list/index.ts +3 -0
  164. package/src/components/estimates/list/list-row-actions.tsx +103 -0
  165. package/src/components/estimates/list/list-table.tsx +171 -0
  166. package/src/components/estimates/list/locales/de.ts +26 -0
  167. package/src/components/estimates/list/locales/sl.ts +26 -0
  168. package/src/components/estimates/list/use-estimate-download.ts +63 -0
  169. package/src/components/export/document-export-form.tsx +288 -0
  170. package/src/components/export/index.ts +2 -0
  171. package/src/components/form/form-input.tsx +89 -0
  172. package/src/components/form/index.ts +1 -0
  173. package/src/components/invoices/create/create-invoice-form.tsx +852 -0
  174. package/src/components/invoices/create/eslog-validation.test.ts +242 -0
  175. package/src/components/invoices/create/eslog-validation.ts +208 -0
  176. package/src/components/invoices/create/locales/de.ts +118 -0
  177. package/src/components/invoices/create/locales/sl.ts +114 -0
  178. package/src/components/invoices/create/prepare-invoice-submission.test.ts +777 -0
  179. package/src/components/invoices/create/prepare-invoice-submission.ts +79 -0
  180. package/src/components/invoices/create/use-invoice-customer-form.ts +5 -0
  181. package/src/components/invoices/index.ts +9 -0
  182. package/src/components/invoices/invoices-furs.hooks.ts +28 -0
  183. package/src/components/invoices/invoices.hooks.ts +110 -0
  184. package/src/components/invoices/list/index.ts +3 -0
  185. package/src/components/invoices/list/list-row-actions.tsx +132 -0
  186. package/src/components/invoices/list/list-table.tsx +165 -0
  187. package/src/components/invoices/list/locales/de.ts +33 -0
  188. package/src/components/invoices/list/locales/sl.ts +33 -0
  189. package/src/components/invoices/list/use-invoice-download.ts +62 -0
  190. package/src/components/invoices/send-email-dialog/index.ts +1 -0
  191. package/src/components/invoices/send-email-dialog/locales/de.ts +18 -0
  192. package/src/components/invoices/send-email-dialog/locales/sl.ts +17 -0
  193. package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +289 -0
  194. package/src/components/invoices/send-email-dialog.tsx +2 -0
  195. package/src/components/invoices/shared/index.ts +2 -0
  196. package/src/components/invoices/shared/scaled-document-preview.tsx +32 -0
  197. package/src/components/invoices/shared/use-a4-scaling.tsx +39 -0
  198. package/src/components/invoices/view/eslog-info-display.tsx +160 -0
  199. package/src/components/invoices/view/furs-info-display.tsx +213 -0
  200. package/src/components/items/create-item-form/create-item-form.tsx +155 -0
  201. package/src/components/items/create-item-form/locales/de.ts +14 -0
  202. package/src/components/items/create-item-form/locales/en.ts +9 -0
  203. package/src/components/items/create-item-form/locales/sl.ts +14 -0
  204. package/src/components/items/item-combobox.tsx +147 -0
  205. package/src/components/items/item-list-table/item-list-header.tsx +33 -0
  206. package/src/components/items/item-list-table/item-list-row-actions.tsx +48 -0
  207. package/src/components/items/item-list-table/item-list-row.tsx +32 -0
  208. package/src/components/items/item-list-table/item-list-table.tsx +76 -0
  209. package/src/components/items/item-list-table/locales/de.ts +10 -0
  210. package/src/components/items/item-list-table/locales/en.ts +10 -0
  211. package/src/components/items/item-list-table/locales/sl.ts +10 -0
  212. package/src/components/items/items.hooks.ts +63 -0
  213. package/src/components/loading-spinner.tsx +24 -0
  214. package/src/components/payments/create-payment-form/create-payment-form.tsx +222 -0
  215. package/src/components/payments/create-payment-form/locales/de.ts +20 -0
  216. package/src/components/payments/create-payment-form/locales/sl.ts +20 -0
  217. package/src/components/payments/edit-payment-form/edit-payment-form.tsx +230 -0
  218. package/src/components/payments/edit-payment-form/index.ts +1 -0
  219. package/src/components/payments/edit-payment-form/locales/de.ts +20 -0
  220. package/src/components/payments/edit-payment-form/locales/sl.ts +20 -0
  221. package/src/components/payments/index.ts +4 -0
  222. package/src/components/payments/list/index.ts +2 -0
  223. package/src/components/payments/list/list-row-actions.tsx +98 -0
  224. package/src/components/payments/list/list-table.tsx +186 -0
  225. package/src/components/payments/list/locales/de.ts +19 -0
  226. package/src/components/payments/list/locales/sl.ts +19 -0
  227. package/src/components/payments/payments.hooks.ts +15 -0
  228. package/src/components/request-logs/index.ts +3 -0
  229. package/src/components/request-logs/request-log-detail.tsx +242 -0
  230. package/src/components/request-logs/request-log-list-table.tsx +266 -0
  231. package/src/components/request-logs/request-logs-page.tsx +10 -0
  232. package/src/components/table/README.md +410 -0
  233. package/src/components/table/data-table.tsx +251 -0
  234. package/src/components/table/date-cell.tsx +35 -0
  235. package/src/components/table/filter-bar.tsx +114 -0
  236. package/src/components/table/filter-panel.tsx +407 -0
  237. package/src/components/table/hooks/use-table-fetch.ts +17 -0
  238. package/src/components/table/hooks/use-table-query.ts +36 -0
  239. package/src/components/table/hooks/use-table-state.ts +293 -0
  240. package/src/components/table/index.ts +35 -0
  241. package/src/components/table/search-input.tsx +85 -0
  242. package/src/components/table/sortable-header.tsx +56 -0
  243. package/src/components/table/table-empty-state.tsx +40 -0
  244. package/src/components/table/table-no-results.tsx +41 -0
  245. package/src/components/table/table-pagination.tsx +42 -0
  246. package/src/components/table/table-skeleton.tsx +54 -0
  247. package/src/components/table/types.ts +136 -0
  248. package/src/components/tax-reports/index.ts +1 -0
  249. package/src/components/tax-reports/kir-export-form.tsx +172 -0
  250. package/src/components/taxes/create-tax-form/create-tax-form.tsx +112 -0
  251. package/src/components/taxes/create-tax-form/locales/de.ts +8 -0
  252. package/src/components/taxes/create-tax-form/locales/en.ts +7 -0
  253. package/src/components/taxes/create-tax-form/locales/sl.ts +8 -0
  254. package/src/components/taxes/tax-list-table/locales/de.ts +11 -0
  255. package/src/components/taxes/tax-list-table/locales/en.ts +10 -0
  256. package/src/components/taxes/tax-list-table/locales/sl.ts +11 -0
  257. package/src/components/taxes/tax-list-table/tax-list-header.tsx +29 -0
  258. package/src/components/taxes/tax-list-table/tax-list-row-actions.tsx +43 -0
  259. package/src/components/taxes/tax-list-table/tax-list-row.tsx +46 -0
  260. package/src/components/taxes/tax-list-table/tax-list-table.tsx +59 -0
  261. package/src/components/taxes/taxes.hooks.ts +35 -0
  262. package/src/components/ui/alert-dialog.tsx +61 -0
  263. package/src/components/ui/alert.tsx +72 -0
  264. package/src/components/ui/badge.tsx +48 -0
  265. package/src/components/ui/breadcrumb.tsx +132 -0
  266. package/src/components/ui/button.tsx +61 -0
  267. package/src/components/ui/calendar.tsx +213 -0
  268. package/src/components/ui/card.tsx +94 -0
  269. package/src/components/ui/chart.tsx +380 -0
  270. package/src/components/ui/checkbox.tsx +27 -0
  271. package/src/components/ui/collapsible.tsx +56 -0
  272. package/src/components/ui/command.tsx +187 -0
  273. package/src/components/ui/dialog.tsx +187 -0
  274. package/src/components/ui/drawer.tsx +123 -0
  275. package/src/components/ui/dropdown-menu.tsx +291 -0
  276. package/src/components/ui/form.tsx +166 -0
  277. package/src/components/ui/input-group.tsx +149 -0
  278. package/src/components/ui/input.tsx +20 -0
  279. package/src/components/ui/label.tsx +18 -0
  280. package/src/components/ui/loading-spinner.tsx +16 -0
  281. package/src/components/ui/popover.tsx +108 -0
  282. package/src/components/ui/radio-group.tsx +37 -0
  283. package/src/components/ui/select.tsx +200 -0
  284. package/src/components/ui/separator.tsx +23 -0
  285. package/src/components/ui/sheet.tsx +145 -0
  286. package/src/components/ui/sidebar.tsx +771 -0
  287. package/src/components/ui/skeleton.tsx +13 -0
  288. package/src/components/ui/sonner.tsx +60 -0
  289. package/src/components/ui/spinner.tsx +10 -0
  290. package/src/components/ui/sticky-form-footer.tsx +55 -0
  291. package/src/components/ui/switch.tsx +30 -0
  292. package/src/components/ui/table.tsx +101 -0
  293. package/src/components/ui/tabs.tsx +80 -0
  294. package/src/components/ui/textarea.tsx +18 -0
  295. package/src/components/ui/tooltip.tsx +89 -0
  296. package/src/components/wl-subscription/index.ts +2 -0
  297. package/src/components/wl-subscription/locked-feature.tsx +173 -0
  298. package/src/components/wl-subscription/upgrade-modal.tsx +209 -0
  299. package/src/frontend.tsx +28 -0
  300. package/src/generate-schemas.ts +265 -0
  301. package/src/generated/schemas/advanceinvoice.ts +177 -0
  302. package/src/generated/schemas/creditnote.ts +187 -0
  303. package/src/generated/schemas/customer.ts +29 -0
  304. package/src/generated/schemas/entity.ts +252 -0
  305. package/src/generated/schemas/estimate.ts +159 -0
  306. package/src/generated/schemas/furssettings.ts +25 -0
  307. package/src/generated/schemas/index.ts +24 -0
  308. package/src/generated/schemas/invoice.ts +167 -0
  309. package/src/generated/schemas/item.ts +38 -0
  310. package/src/generated/schemas/payment.ts +44 -0
  311. package/src/generated/schemas/previewadvanceinvoice_body.ts +354 -0
  312. package/src/generated/schemas/previewestimate_body.ts +309 -0
  313. package/src/generated/schemas/registerfursmovablepremise_body.ts +22 -0
  314. package/src/generated/schemas/registerfursrealestatepremise_body.ts +32 -0
  315. package/src/generated/schemas/renderdocument_body.ts +594 -0
  316. package/src/generated/schemas/sendemail_body.ts +26 -0
  317. package/src/generated/schemas/startpdfexport_body.ts +20 -0
  318. package/src/generated/schemas/tax.ts +48 -0
  319. package/src/generated/schemas/uploadfile_body.ts +23 -0
  320. package/src/generated/schemas/uploadfurscertificate_body.ts +20 -0
  321. package/src/generated/schemas/userfurssettings.ts +19 -0
  322. package/src/hooks/create-resource-hooks.test.ts +483 -0
  323. package/src/hooks/create-resource-hooks.ts +300 -0
  324. package/src/hooks/use-debounce.ts +12 -0
  325. package/src/hooks/use-duplicate-document.ts +185 -0
  326. package/src/hooks/use-media-query.tsx +19 -0
  327. package/src/hooks/use-mobile.ts +39 -0
  328. package/src/hooks/use-next-document-number.ts +57 -0
  329. package/src/hooks/use-resource-mutation.ts +118 -0
  330. package/src/hooks/use-vies-check.ts +130 -0
  331. package/src/index.css +11 -0
  332. package/src/index.html +13 -0
  333. package/src/index.tsx +12 -0
  334. package/src/lib/auth.ts +4 -0
  335. package/src/lib/browser-cookies.ts +70 -0
  336. package/src/lib/constants.ts +287 -0
  337. package/src/lib/cookies.ts +36 -0
  338. package/src/lib/schemas/advance-invoice.ts +43 -0
  339. package/src/lib/schemas/credit-note.ts +32 -0
  340. package/src/lib/schemas/estimate.ts +31 -0
  341. package/src/lib/schemas/index.ts +18 -0
  342. package/src/lib/schemas/invoice.ts +43 -0
  343. package/src/lib/schemas/shared.ts +79 -0
  344. package/src/lib/translation.ts +38 -0
  345. package/src/lib/utils.ts +6 -0
  346. package/src/providers/entities-context.tsx +41 -0
  347. package/src/providers/entities-provider.tsx +201 -0
  348. package/src/providers/form-footer-context.tsx +72 -0
  349. package/src/providers/sdk-provider.tsx +164 -0
  350. package/src/providers/white-label-provider.tsx +91 -0
  351. package/src/providers/wl-subscription-provider.tsx +277 -0
  352. package/src/utils/string-helpers.ts +111 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Payment trend hook using the entity stats API.
3
+ * Server-side aggregation by month for accurate trend data.
4
+ */
5
+ import type { StatsQueryDataItem } from "@spaceinvoices/js-sdk";
6
+ import { useQuery } from "@tanstack/react-query";
7
+ import { useSDK } from "@/ui/providers/sdk-provider";
8
+ import { STATS_QUERY_CACHE_KEY } from "../shared/use-stats-query";
9
+
10
+ export const PAYMENT_TREND_CACHE_KEY = "dashboard-payment-trend";
11
+
12
+ function getLastMonths(count: number): { months: string[]; startDate: string; endDate: string } {
13
+ const months: string[] = [];
14
+ const now = new Date();
15
+
16
+ // Start of the month 'count-1' months ago
17
+ const startDate = new Date(now.getFullYear(), now.getMonth() - (count - 1), 1);
18
+ // End of current month
19
+ const endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
20
+
21
+ for (let i = count - 1; i >= 0; i--) {
22
+ const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
23
+ months.push(d.toISOString().substring(0, 7));
24
+ }
25
+
26
+ return {
27
+ months,
28
+ startDate: startDate.toISOString().substring(0, 10),
29
+ endDate: endDate.toISOString().substring(0, 10),
30
+ };
31
+ }
32
+
33
+ export type PaymentTrendData = { month: string; amount: number }[];
34
+
35
+ export function usePaymentTrendData(entityId: string | undefined) {
36
+ const { sdk } = useSDK();
37
+
38
+ const { months, startDate, endDate } = getLastMonths(6);
39
+
40
+ const query = useQuery({
41
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "payment-trend", startDate, endDate],
42
+ queryFn: async () => {
43
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
44
+ return sdk.entityStats.queryEntityStats(
45
+ {
46
+ metrics: [{ type: "sum", field: "amount_converted", alias: "amount" }],
47
+ table: "payments",
48
+ date_from: startDate,
49
+ date_to: endDate,
50
+ group_by: ["month", "currency_code"], // Include currency for display
51
+ order_by: [{ field: "month", direction: "asc" }],
52
+ },
53
+ { entity_id: entityId },
54
+ );
55
+ },
56
+ enabled: !!entityId && !!sdk,
57
+ staleTime: 30_000,
58
+ select: (response) => {
59
+ // Build a map of all months with 0 amount
60
+ const monthMap: Record<string, number> = {};
61
+ for (const month of months) {
62
+ monthMap[month] = 0;
63
+ }
64
+
65
+ // Fill in the actual amounts from the API response
66
+ // Sum up amounts per month (in case of multiple rows due to currency_code grouping)
67
+ const data = response.data || [];
68
+ let currency = "EUR";
69
+ for (const row of data as StatsQueryDataItem[]) {
70
+ const month = String(row.month);
71
+ if (month in monthMap) {
72
+ monthMap[month] += Number(row.amount) || 0;
73
+ }
74
+ // Get currency from first row with data
75
+ if (row.currency_code && currency === "EUR") {
76
+ currency = String(row.currency_code);
77
+ }
78
+ }
79
+
80
+ return {
81
+ data: months.map((month) => ({ month, amount: monthMap[month] })),
82
+ currency, // Currency from payment data
83
+ };
84
+ },
85
+ });
86
+
87
+ return {
88
+ data: query.data?.data || [],
89
+ currency: query.data?.currency || "EUR",
90
+ isLoading: query.isLoading,
91
+ };
92
+ }
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ import { AlertTriangle, TrendingDown, TrendingUp } from "lucide-react";
4
+ import { Card, CardContent, CardHeader, CardTitle } from "@/ui/components/ui/card";
5
+
6
+ export type RevenueCardProps = {
7
+ title: string;
8
+ value: number;
9
+ currency: string;
10
+ variant?: "default" | "success" | "warning" | "danger";
11
+ subtitle?: string;
12
+ locale?: string;
13
+ };
14
+
15
+ const variantStyles = {
16
+ default: "",
17
+ success: "text-green-600 dark:text-green-400",
18
+ warning: "text-yellow-600 dark:text-yellow-400",
19
+ danger: "text-red-600 dark:text-red-400",
20
+ };
21
+
22
+ const variantIcons = {
23
+ default: null,
24
+ success: TrendingUp,
25
+ warning: AlertTriangle,
26
+ danger: TrendingDown,
27
+ };
28
+
29
+ export function RevenueCard({ title, value, currency, variant = "default", subtitle, locale }: RevenueCardProps) {
30
+ const Icon = variantIcons[variant];
31
+ const formattedValue = new Intl.NumberFormat(locale, {
32
+ style: "currency",
33
+ currency,
34
+ minimumFractionDigits: 2,
35
+ }).format(value);
36
+
37
+ return (
38
+ <Card>
39
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
40
+ <CardTitle className="font-medium text-muted-foreground text-sm">{title}</CardTitle>
41
+ {Icon && <Icon className={`h-4 w-4 ${variantStyles[variant]}`} />}
42
+ </CardHeader>
43
+ <CardContent>
44
+ <div className={`font-bold text-2xl ${variantStyles[variant]}`}>{formattedValue}</div>
45
+ {subtitle && <p className="mt-1 text-muted-foreground text-xs">{subtitle}</p>}
46
+ </CardContent>
47
+ </Card>
48
+ );
49
+ }
@@ -0,0 +1,4 @@
1
+ export type { RevenueTrendChartData, RevenueTrendChartProps } from "./revenue-trend-chart";
2
+ export { RevenueTrendChart } from "./revenue-trend-chart";
3
+ export type { RevenueTrendData } from "./use-revenue-trend";
4
+ export { REVENUE_TREND_CACHE_KEY, useRevenueTrendData } from "./use-revenue-trend";
@@ -0,0 +1,5 @@
1
+ export default {
2
+ "Revenue Trend": "Trend prihodkov",
3
+ "Monthly revenue over the last 6 months": "Mesečni prihodki v zadnjih 6 mesecih",
4
+ "No data available": "Ni podatkov",
5
+ } as const;
@@ -0,0 +1,137 @@
1
+ "use client";
2
+
3
+ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/ui/components/ui/card";
5
+ import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/ui/components/ui/chart";
6
+ import { createTranslation } from "@/ui/lib/translation";
7
+ import { ChartEmptyState } from "../chart-empty-state";
8
+ import { LoadingCard } from "../loading-card";
9
+ import sl from "./locales/sl";
10
+ import { useRevenueTrendData } from "./use-revenue-trend";
11
+
12
+ const translations = { sl } as const;
13
+
14
+ export type RevenueTrendChartData = { month: string; revenue: number }[];
15
+
16
+ type BaseProps = {
17
+ locale?: string;
18
+ t?: (key: string) => string;
19
+ namespace?: string;
20
+ };
21
+
22
+ type DataProps = BaseProps & {
23
+ data: RevenueTrendChartData;
24
+ currency: string;
25
+ entityId?: never;
26
+ };
27
+
28
+ type TurnkeyProps = BaseProps & {
29
+ entityId: string;
30
+ data?: never;
31
+ currency?: never;
32
+ };
33
+
34
+ export type RevenueTrendChartProps = DataProps | TurnkeyProps;
35
+
36
+ const chartConfig = {
37
+ revenue: {
38
+ label: "Revenue",
39
+ color: "var(--chart-1)",
40
+ },
41
+ } satisfies ChartConfig;
42
+
43
+ function formatMonth(month: string, locale?: string): string {
44
+ const date = new Date(`${month}-01`);
45
+ return date.toLocaleDateString(locale, { month: "short" });
46
+ }
47
+
48
+ function formatCurrency(value: number, currency: string, locale?: string): string {
49
+ return new Intl.NumberFormat(locale, {
50
+ style: "currency",
51
+ currency,
52
+ minimumFractionDigits: 0,
53
+ maximumFractionDigits: 0,
54
+ }).format(value);
55
+ }
56
+
57
+ export function RevenueTrendChart(props: RevenueTrendChartProps) {
58
+ const { locale, t: externalT, namespace } = props;
59
+ const t = createTranslation({ t: externalT, namespace, locale, translations });
60
+
61
+ // Turnkey mode - fetch own data
62
+ const hookResult = useRevenueTrendData("entityId" in props ? props.entityId : undefined);
63
+
64
+ // Determine data source
65
+ const data = "entityId" in props ? hookResult.data : props.data;
66
+ const currency = "entityId" in props ? hookResult.currency : props.currency;
67
+ const isLoading = "entityId" in props ? hookResult.isLoading : false;
68
+
69
+ if (isLoading) {
70
+ return <LoadingCard className="h-[280px]" />;
71
+ }
72
+
73
+ const hasData = data.some((d) => d.revenue > 0);
74
+
75
+ // Placeholder data for empty state
76
+ const placeholderData =
77
+ data.length > 0
78
+ ? data.map((d) => ({ ...d, revenue: 100 }))
79
+ : [
80
+ { month: "2024-01", revenue: 80 },
81
+ { month: "2024-02", revenue: 120 },
82
+ { month: "2024-03", revenue: 90 },
83
+ { month: "2024-04", revenue: 140 },
84
+ { month: "2024-05", revenue: 100 },
85
+ { month: "2024-06", revenue: 130 },
86
+ ];
87
+
88
+ const chartContent = (
89
+ <ChartContainer config={chartConfig} className="h-[200px] w-full">
90
+ <AreaChart data={hasData ? data : placeholderData} margin={{ left: 12, right: 12 }}>
91
+ <CartesianGrid vertical={false} strokeDasharray="3 3" />
92
+ <XAxis
93
+ dataKey="month"
94
+ tickLine={false}
95
+ axisLine={false}
96
+ tickMargin={8}
97
+ tickFormatter={(m) => formatMonth(m, locale)}
98
+ />
99
+ <YAxis
100
+ tickLine={false}
101
+ axisLine={false}
102
+ tickMargin={8}
103
+ tickFormatter={(value) => formatCurrency(value, currency, locale)}
104
+ width={80}
105
+ />
106
+ <defs>
107
+ <linearGradient id="fillRevenue" x1="0" y1="0" x2="0" y2="1">
108
+ <stop offset="5%" stopColor="var(--chart-1)" stopOpacity={0.8} />
109
+ <stop offset="95%" stopColor="var(--chart-1)" stopOpacity={0.1} />
110
+ </linearGradient>
111
+ </defs>
112
+ <ChartTooltip
113
+ cursor={false}
114
+ content={
115
+ <ChartTooltipContent
116
+ labelFormatter={(label) => formatMonth(label, locale)}
117
+ formatter={(value) => formatCurrency(Number(value), currency, locale)}
118
+ />
119
+ }
120
+ />
121
+ <Area dataKey="revenue" type="monotone" fill="url(#fillRevenue)" stroke="var(--chart-1)" strokeWidth={2} />
122
+ </AreaChart>
123
+ </ChartContainer>
124
+ );
125
+
126
+ return (
127
+ <Card>
128
+ <CardHeader>
129
+ <CardTitle>{t("Revenue Trend")}</CardTitle>
130
+ <CardDescription>{t("Monthly revenue over the last 6 months")}</CardDescription>
131
+ </CardHeader>
132
+ <CardContent>
133
+ {hasData ? chartContent : <ChartEmptyState label={t("No data available")}>{chartContent}</ChartEmptyState>}
134
+ </CardContent>
135
+ </Card>
136
+ );
137
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Revenue trend hook using the entity stats API.
3
+ * Server-side aggregation by month for accurate trend data.
4
+ */
5
+ import type { StatsQueryDataItem } from "@spaceinvoices/js-sdk";
6
+ import { useQuery } from "@tanstack/react-query";
7
+ import { useSDK } from "@/ui/providers/sdk-provider";
8
+ import { STATS_QUERY_CACHE_KEY } from "../shared/use-stats-query";
9
+
10
+ export const REVENUE_TREND_CACHE_KEY = "dashboard-revenue-trend";
11
+
12
+ function getLastMonths(count: number): { months: string[]; startDate: string; endDate: string } {
13
+ const months: string[] = [];
14
+ const now = new Date();
15
+
16
+ // Start of the month 'count-1' months ago
17
+ const startDate = new Date(now.getFullYear(), now.getMonth() - (count - 1), 1);
18
+ // End of current month
19
+ const endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0);
20
+
21
+ for (let i = count - 1; i >= 0; i--) {
22
+ const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
23
+ months.push(d.toISOString().substring(0, 7));
24
+ }
25
+
26
+ return {
27
+ months,
28
+ startDate: startDate.toISOString().substring(0, 10),
29
+ endDate: endDate.toISOString().substring(0, 10),
30
+ };
31
+ }
32
+
33
+ export type RevenueTrendData = { month: string; revenue: number }[];
34
+
35
+ export function useRevenueTrendData(entityId: string | undefined) {
36
+ const { sdk } = useSDK();
37
+
38
+ const { months, startDate, endDate } = getLastMonths(6);
39
+
40
+ const query = useQuery({
41
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "revenue-trend", startDate, endDate],
42
+ queryFn: async () => {
43
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
44
+ return sdk.entityStats.queryEntityStats(
45
+ {
46
+ metrics: [{ type: "sum", field: "total_with_tax_converted", alias: "revenue" }],
47
+ table: "invoices",
48
+ date_from: startDate,
49
+ date_to: endDate,
50
+ filters: { is_draft: false, voided_at: null },
51
+ group_by: ["month", "quote_currency"], // Include currency for display
52
+ order_by: [{ field: "month", direction: "asc" }],
53
+ },
54
+ { entity_id: entityId },
55
+ );
56
+ },
57
+ enabled: !!entityId && !!sdk,
58
+ staleTime: 30_000,
59
+ select: (response) => {
60
+ // Build a map of all months with 0 revenue
61
+ const monthMap: Record<string, number> = {};
62
+ for (const month of months) {
63
+ monthMap[month] = 0;
64
+ }
65
+
66
+ // Fill in the actual revenue from the API response
67
+ // Sum up revenues per month (in case of multiple rows due to quote_currency grouping)
68
+ const data = response.data || [];
69
+ let currency = "EUR";
70
+ for (const row of data as StatsQueryDataItem[]) {
71
+ const month = String(row.month);
72
+ if (month in monthMap) {
73
+ monthMap[month] += Number(row.revenue) || 0;
74
+ }
75
+ // Get currency from first row with data
76
+ if (row.quote_currency && currency === "EUR") {
77
+ currency = String(row.quote_currency);
78
+ }
79
+ }
80
+
81
+ return {
82
+ data: months.map((month) => ({ month, revenue: monthMap[month] })),
83
+ currency, // Currency from document data
84
+ };
85
+ },
86
+ });
87
+
88
+ return {
89
+ data: query.data?.data || [],
90
+ currency: query.data?.currency || "EUR",
91
+ isLoading: query.isLoading,
92
+ };
93
+ }
@@ -0,0 +1,5 @@
1
+ export type { RevenueData } from "./use-revenue-data";
2
+ export { REVENUE_DATA_CACHE_KEY, useRevenueData } from "./use-revenue-data";
3
+ export type { StatsCountsData } from "./use-stats-counts";
4
+ export { STATS_COUNTS_CACHE_KEY, useStatsCountsData } from "./use-stats-counts";
5
+ export { STATS_QUERY_CACHE_KEY, useStatsQuery } from "./use-stats-query";
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Revenue data hook using the entity stats API.
3
+ * Server-side aggregation for accurate calculations.
4
+ */
5
+ import type { StatsQueryDataItem } from "@spaceinvoices/js-sdk";
6
+ import { useQueries } from "@tanstack/react-query";
7
+ import { useSDK } from "@/ui/providers/sdk-provider";
8
+ import { STATS_QUERY_CACHE_KEY } from "./use-stats-query";
9
+
10
+ export const REVENUE_DATA_CACHE_KEY = "dashboard-revenue-data";
11
+
12
+ function getMonthDateRange() {
13
+ const now = new Date();
14
+ const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
15
+ const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
16
+ return {
17
+ from: firstDay.toISOString().split("T")[0],
18
+ to: lastDay.toISOString().split("T")[0],
19
+ };
20
+ }
21
+
22
+ function getYearDateRange() {
23
+ const year = new Date().getFullYear();
24
+ return {
25
+ from: `${year}-01-01`,
26
+ to: `${year}-12-31`,
27
+ };
28
+ }
29
+
30
+ export type RevenueData = {
31
+ thisMonth: number;
32
+ thisYear: number;
33
+ outstanding: number;
34
+ overdue: number;
35
+ overdueCount: number;
36
+ currency: string;
37
+ };
38
+
39
+ export function useRevenueData(entityId: string | undefined) {
40
+ const { sdk } = useSDK();
41
+ const monthRange = getMonthDateRange();
42
+ const yearRange = getYearDateRange();
43
+
44
+ const queries = useQueries({
45
+ queries: [
46
+ // This month revenue (using converted amounts for multi-currency support)
47
+ {
48
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "revenue-this-month", monthRange.from],
49
+ queryFn: async () => {
50
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
51
+ return sdk.entityStats.queryEntityStats(
52
+ {
53
+ metrics: [{ type: "sum", field: "total_with_tax_converted", alias: "revenue" }],
54
+ table: "invoices",
55
+ date_from: monthRange.from,
56
+ date_to: monthRange.to,
57
+ filters: { is_draft: false, voided_at: null },
58
+ group_by: ["quote_currency"], // Get the currency for display
59
+ },
60
+ { entity_id: entityId },
61
+ );
62
+ },
63
+ enabled: !!entityId && !!sdk,
64
+ staleTime: 30_000,
65
+ },
66
+ // This year revenue (using converted amounts for multi-currency support)
67
+ {
68
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "revenue-this-year", yearRange.from],
69
+ queryFn: async () => {
70
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
71
+ return sdk.entityStats.queryEntityStats(
72
+ {
73
+ metrics: [{ type: "sum", field: "total_with_tax_converted", alias: "revenue" }],
74
+ table: "invoices",
75
+ date_from: yearRange.from,
76
+ date_to: yearRange.to,
77
+ filters: { is_draft: false, voided_at: null },
78
+ },
79
+ { entity_id: entityId },
80
+ );
81
+ },
82
+ enabled: !!entityId && !!sdk,
83
+ staleTime: 30_000,
84
+ },
85
+ // Outstanding (unpaid, not voided)
86
+ {
87
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "outstanding"],
88
+ queryFn: async () => {
89
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
90
+ return sdk.entityStats.queryEntityStats(
91
+ {
92
+ metrics: [{ type: "sum", field: "total_due", alias: "outstanding" }],
93
+ table: "invoices",
94
+ filters: { is_draft: false, voided_at: null, paid_in_full: false },
95
+ },
96
+ { entity_id: entityId },
97
+ );
98
+ },
99
+ enabled: !!entityId && !!sdk,
100
+ staleTime: 30_000,
101
+ },
102
+ // Overdue (past due date, unpaid)
103
+ {
104
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "overdue"],
105
+ queryFn: async () => {
106
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
107
+ return sdk.entityStats.queryEntityStats(
108
+ {
109
+ metrics: [
110
+ { type: "sum", field: "total_due", alias: "overdue" },
111
+ { type: "count", alias: "count" },
112
+ ],
113
+ table: "invoices",
114
+ filters: { is_draft: false, voided_at: null, paid_in_full: false },
115
+ group_by: ["overdue_bucket"],
116
+ },
117
+ { entity_id: entityId },
118
+ );
119
+ },
120
+ enabled: !!entityId && !!sdk,
121
+ staleTime: 30_000,
122
+ },
123
+ ],
124
+ });
125
+
126
+ const [thisMonthQuery, thisYearQuery, outstandingQuery, overdueQuery] = queries;
127
+
128
+ // Extract this month revenue and currency (may have multiple rows if grouped by quote_currency)
129
+ const thisMonthData = thisMonthQuery.data?.data || [];
130
+ const thisMonthRevenue = thisMonthData.reduce(
131
+ (sum: number, row: StatsQueryDataItem) => sum + (Number(row.revenue) || 0),
132
+ 0,
133
+ );
134
+ // Get currency from first row with data
135
+ const currency = (thisMonthData[0]?.quote_currency as string) || "EUR";
136
+
137
+ // Extract overdue data (buckets other than "current")
138
+ const overdueData = overdueQuery.data?.data || [];
139
+ const overdueBuckets = overdueData.filter((row: StatsQueryDataItem) => row.overdue_bucket !== "current");
140
+ const totalOverdue = overdueBuckets.reduce(
141
+ (sum: number, row: StatsQueryDataItem) => sum + (Number(row.overdue) || 0),
142
+ 0,
143
+ );
144
+ const overdueCount = overdueBuckets.reduce(
145
+ (sum: number, row: StatsQueryDataItem) => sum + (Number(row.count) || 0),
146
+ 0,
147
+ );
148
+
149
+ return {
150
+ data: {
151
+ thisMonth: thisMonthRevenue,
152
+ thisYear: Number(thisYearQuery.data?.data?.[0]?.revenue) || 0,
153
+ outstanding: Number(outstandingQuery.data?.data?.[0]?.outstanding) || 0,
154
+ overdue: totalOverdue,
155
+ overdueCount,
156
+ currency, // Currency from document data
157
+ } as RevenueData,
158
+ isLoading: queries.some((q) => q.isLoading),
159
+ };
160
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Stats counts hook using the entity stats API.
3
+ * Server-side counting for accurate totals.
4
+ */
5
+ import { useQueries } from "@tanstack/react-query";
6
+ import { useSDK } from "@/ui/providers/sdk-provider";
7
+ import { STATS_QUERY_CACHE_KEY } from "./use-stats-query";
8
+
9
+ export const STATS_COUNTS_CACHE_KEY = "dashboard-stats-counts";
10
+
11
+ export type StatsCountsData = {
12
+ invoices: number;
13
+ estimates: number;
14
+ customers: number;
15
+ items: number;
16
+ };
17
+
18
+ export function useStatsCountsData(entityId: string | undefined) {
19
+ const { sdk } = useSDK();
20
+
21
+ const queries = useQueries({
22
+ queries: [
23
+ // Invoices count
24
+ {
25
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "count-invoices"],
26
+ queryFn: async () => {
27
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
28
+ return sdk.entityStats.queryEntityStats(
29
+ { metrics: [{ type: "count", alias: "total" }], table: "invoices" },
30
+ { entity_id: entityId },
31
+ );
32
+ },
33
+ enabled: !!entityId && !!sdk,
34
+ staleTime: 30_000,
35
+ },
36
+ // Estimates count
37
+ {
38
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "count-estimates"],
39
+ queryFn: async () => {
40
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
41
+ return sdk.entityStats.queryEntityStats(
42
+ { metrics: [{ type: "count", alias: "total" }], table: "estimates" },
43
+ { entity_id: entityId },
44
+ );
45
+ },
46
+ enabled: !!entityId && !!sdk,
47
+ staleTime: 30_000,
48
+ },
49
+ // Customers count
50
+ {
51
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "count-customers"],
52
+ queryFn: async () => {
53
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
54
+ return sdk.entityStats.queryEntityStats(
55
+ { metrics: [{ type: "count", alias: "total" }], table: "customers" },
56
+ { entity_id: entityId },
57
+ );
58
+ },
59
+ enabled: !!entityId && !!sdk,
60
+ staleTime: 30_000,
61
+ },
62
+ // Items count
63
+ {
64
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, "count-items"],
65
+ queryFn: async () => {
66
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
67
+ return sdk.entityStats.queryEntityStats(
68
+ { metrics: [{ type: "count", alias: "total" }], table: "items" },
69
+ { entity_id: entityId },
70
+ );
71
+ },
72
+ enabled: !!entityId && !!sdk,
73
+ staleTime: 30_000,
74
+ },
75
+ ],
76
+ });
77
+
78
+ const [invoicesQuery, estimatesQuery, customersQuery, itemsQuery] = queries;
79
+
80
+ return {
81
+ data: {
82
+ invoices: Number(invoicesQuery.data?.data?.[0]?.total) || 0,
83
+ estimates: Number(estimatesQuery.data?.data?.[0]?.total) || 0,
84
+ customers: Number(customersQuery.data?.data?.[0]?.total) || 0,
85
+ items: Number(itemsQuery.data?.data?.[0]?.total) || 0,
86
+ } as StatsCountsData,
87
+ isLoading: queries.some((q) => q.isLoading),
88
+ };
89
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Base hook for stats queries using the entity stats API.
3
+ * Provides server-side aggregation instead of client-side calculation.
4
+ */
5
+ import type { StatsQueryRequest, StatsQueryResponse } from "@spaceinvoices/js-sdk";
6
+ import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
7
+ import { useSDK } from "@/ui/providers/sdk-provider";
8
+
9
+ export const STATS_QUERY_CACHE_KEY = "entity-stats-query";
10
+
11
+ export type StatsQueryOptions<TData = StatsQueryResponse> = Omit<
12
+ UseQueryOptions<StatsQueryResponse, Error, TData>,
13
+ "queryKey" | "queryFn"
14
+ >;
15
+
16
+ /**
17
+ * Generic hook for executing stats queries.
18
+ * Use this as a base for specific stats hooks.
19
+ */
20
+ export function useStatsQuery<TData = StatsQueryResponse>(
21
+ entityId: string | undefined,
22
+ query: StatsQueryRequest,
23
+ options?: StatsQueryOptions<TData>,
24
+ ) {
25
+ const { sdk } = useSDK();
26
+
27
+ return useQuery({
28
+ queryKey: [STATS_QUERY_CACHE_KEY, entityId, query],
29
+ queryFn: async () => {
30
+ if (!entityId || !sdk) throw new Error("Missing entity or SDK");
31
+ // SDK's wrapMethod already unwraps success response and throws on error
32
+ return await sdk.entityStats.queryEntityStats(query, { entity_id: entityId });
33
+ },
34
+ enabled: !!entityId && !!sdk,
35
+ staleTime: 30_000, // 30 seconds
36
+ ...options,
37
+ });
38
+ }