@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 @@
1
+ export { default as EditPaymentForm } from "./edit-payment-form";
@@ -0,0 +1,20 @@
1
+ export default {
2
+ Amount: "Betrag",
3
+ "Enter amount": "Betrag eingeben",
4
+ "Payment Type": "Zahlungsart",
5
+ "Select payment type": "Zahlungsart auswählen",
6
+ Cash: "Bargeld",
7
+ "Bank Transfer": "Banküberweisung",
8
+ Card: "Karte",
9
+ Check: "Scheck",
10
+ "Credit Note": "Gutschrift",
11
+ Advance: "Vorauszahlung",
12
+ Other: "Andere",
13
+ "Payment Date": "Zahlungsdatum",
14
+ "Pick a date": "Datum auswählen",
15
+ Reference: "Referenz",
16
+ "Enter reference number": "Referenznummer eingeben",
17
+ Note: "Notiz",
18
+ "Enter payment notes": "Zahlungsnotizen eingeben",
19
+ "There was an error updating the payment": "Beim Aktualisieren der Zahlung ist ein Fehler aufgetreten",
20
+ } as const;
@@ -0,0 +1,20 @@
1
+ export default {
2
+ Amount: "Znesek",
3
+ "Enter amount": "Vnesite znesek",
4
+ "Payment Type": "Vrsta plačila",
5
+ "Select payment type": "Izberite vrsto plačila",
6
+ Cash: "Gotovina",
7
+ "Bank Transfer": "Bančno nakazilo",
8
+ Card: "Kartica",
9
+ Check: "Ček",
10
+ "Credit Note": "Dobropis",
11
+ Advance: "Avans",
12
+ Other: "Drugo",
13
+ "Payment Date": "Datum plačila",
14
+ "Pick a date": "Izberite datum",
15
+ Reference: "Referenca",
16
+ "Enter reference number": "Vnesite referenčno številko",
17
+ Note: "Opomba",
18
+ "Enter payment notes": "Vnesite opombe plačila",
19
+ "There was an error updating the payment": "Prišlo je do napake pri posodabljanju plačila",
20
+ } as const;
@@ -0,0 +1,4 @@
1
+ export { default as CreatePaymentForm } from "./create-payment-form/create-payment-form";
2
+ export { default as EditPaymentForm } from "./edit-payment-form/edit-payment-form";
3
+ export * from "./list";
4
+ export * from "./payments.hooks";
@@ -0,0 +1,2 @@
1
+ export { default as PaymentListRowActions } from "./list-row-actions";
2
+ export { default as PaymentListTable } from "./list-table";
@@ -0,0 +1,98 @@
1
+ import type { Payment } from "@spaceinvoices/js-sdk";
2
+ import { Eye, MoreHorizontal, Pencil, Trash2 } from "lucide-react";
3
+ import { Button } from "@/ui/components/ui/button";
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuGroup,
8
+ DropdownMenuItem,
9
+ DropdownMenuLabel,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from "@/ui/components/ui/dropdown-menu";
13
+ import type { ComponentTranslationProps } from "@/ui/lib/translation";
14
+ import { createTranslation } from "@/ui/lib/translation";
15
+
16
+ import { useDeletePayment } from "../payments.hooks";
17
+ import de from "./locales/de";
18
+ import sl from "./locales/sl";
19
+
20
+ const translations = {
21
+ sl,
22
+ de,
23
+ } as const;
24
+
25
+ type PaymentListRowActionsProps = {
26
+ payment: Payment;
27
+ entityId?: string;
28
+ onViewInvoice?: (invoiceId: string) => void;
29
+ onEditPayment?: (payment: Payment) => void;
30
+ onDeleteSuccess?: () => void;
31
+ onDeleteError?: (error: string) => void;
32
+ } & ComponentTranslationProps;
33
+
34
+ export default function PaymentListRowActions({
35
+ payment,
36
+ entityId,
37
+ onViewInvoice,
38
+ onEditPayment,
39
+ onDeleteSuccess,
40
+ onDeleteError,
41
+ ...i18nProps
42
+ }: PaymentListRowActionsProps) {
43
+ const t = createTranslation({
44
+ translations,
45
+ ...i18nProps,
46
+ });
47
+
48
+ const { mutate: deletePayment, isPending: isDeleting } = useDeletePayment({
49
+ entityId,
50
+ onSuccess: () => {
51
+ onDeleteSuccess?.();
52
+ },
53
+ onError: (error: Error) => {
54
+ onDeleteError?.(error.message);
55
+ },
56
+ });
57
+
58
+ const handleDelete = () => {
59
+ deletePayment({ id: payment.id });
60
+ };
61
+
62
+ return (
63
+ <DropdownMenu>
64
+ <DropdownMenuTrigger asChild>
65
+ <Button variant="ghost" className="h-8 w-8 cursor-pointer p-0" id="action-menu-trigger">
66
+ <span className="sr-only">{t("Open menu")}</span>
67
+ <MoreHorizontal className="h-4 w-4" />
68
+ </Button>
69
+ </DropdownMenuTrigger>
70
+ <DropdownMenuContent align="end">
71
+ <DropdownMenuLabel>{t("Actions")}</DropdownMenuLabel>
72
+ <DropdownMenuGroup>
73
+ <DropdownMenuItem className="cursor-pointer" onClick={() => onEditPayment?.(payment)}>
74
+ <Pencil className="h-4 w-4" />
75
+ {t("Edit payment")}
76
+ </DropdownMenuItem>
77
+ {payment.invoice_id && (
78
+ <DropdownMenuItem className="cursor-pointer" onClick={() => onViewInvoice?.(payment.invoice_id!)}>
79
+ <Eye className="h-4 w-4" />
80
+ {t("View invoice")}
81
+ </DropdownMenuItem>
82
+ )}
83
+ </DropdownMenuGroup>
84
+ <DropdownMenuSeparator />
85
+ <DropdownMenuGroup>
86
+ <DropdownMenuItem
87
+ className="cursor-pointer text-destructive focus:text-destructive"
88
+ onClick={handleDelete}
89
+ disabled={isDeleting}
90
+ >
91
+ <Trash2 className="h-4 w-4" />
92
+ {isDeleting ? t("Deleting...") : t("Delete payment")}
93
+ </DropdownMenuItem>
94
+ </DropdownMenuGroup>
95
+ </DropdownMenuContent>
96
+ </DropdownMenu>
97
+ );
98
+ }
@@ -0,0 +1,186 @@
1
+ import type { Payment } from "@spaceinvoices/js-sdk";
2
+ import { Info } from "lucide-react";
3
+ import { useMemo } from "react";
4
+ import { DataTable } from "@/ui/components/table/data-table";
5
+ import { FormattedDate } from "@/ui/components/table/date-cell";
6
+ import { useTableFetch } from "@/ui/components/table/hooks/use-table-fetch";
7
+ import type { Column, ListTableProps, TableQueryParams, TableQueryResponse } from "@/ui/components/table/types";
8
+ import { Button } from "@/ui/components/ui/button";
9
+ import { Popover, PopoverContent, PopoverTrigger } from "@/ui/components/ui/popover";
10
+ import { createTranslation } from "@/ui/lib/translation";
11
+ import { useSDK } from "@/ui/providers/sdk-provider";
12
+
13
+ import PaymentListRowActions from "./list-row-actions";
14
+ import de from "./locales/de";
15
+ import sl from "./locales/sl";
16
+
17
+ const translations = {
18
+ sl,
19
+ de,
20
+ } as const;
21
+
22
+ // Extended payment type that includes Invoice relation from API
23
+ type PaymentWithInvoice = Payment & {
24
+ Invoice?: { id: string; number: string } | null;
25
+ };
26
+
27
+ type PaymentListTableProps = {
28
+ t?: (key: string) => string;
29
+ namespace?: string;
30
+ locale?: string;
31
+ entityId?: string;
32
+ onViewInvoice?: (invoiceId: string) => void;
33
+ onEditPayment?: (payment: Payment) => void;
34
+ onDeleteSuccess?: () => void;
35
+ onDeleteError?: (error: string) => void;
36
+ } & ListTableProps<Payment>;
37
+
38
+ export default function PaymentListTable({
39
+ queryParams,
40
+ onChangeParams,
41
+ entityId,
42
+ onViewInvoice,
43
+ onEditPayment,
44
+ onDeleteSuccess,
45
+ onDeleteError,
46
+ ...i18nProps
47
+ }: PaymentListTableProps) {
48
+ const t = createTranslation({
49
+ translations,
50
+ ...i18nProps,
51
+ });
52
+
53
+ const { sdk } = useSDK();
54
+
55
+ const handleFetch = useTableFetch(async (params: TableQueryParams) => {
56
+ if (!sdk) throw new Error("SDK not initialized");
57
+ if (!params.entity_id) throw new Error("Entity ID required");
58
+
59
+ const response = await sdk.payments.list({
60
+ entity_id: params.entity_id,
61
+ limit: params.limit,
62
+ next_cursor: params.next_cursor,
63
+ prev_cursor: params.prev_cursor,
64
+ order_by: params.order_by,
65
+ search: params.search,
66
+ query: params.query,
67
+ });
68
+ return response as unknown as TableQueryResponse<Payment>;
69
+ }, entityId);
70
+
71
+ const typeLabels: Record<string, string> = useMemo(
72
+ () => ({
73
+ cash: t("type.cash"),
74
+ bank_transfer: t("type.bank_transfer"),
75
+ card: t("type.card"),
76
+ check: t("type.check"),
77
+ other: t("type.other"),
78
+ }),
79
+ [t],
80
+ );
81
+
82
+ const columns: Column<PaymentWithInvoice>[] = useMemo(
83
+ () => [
84
+ {
85
+ id: "date",
86
+ header: t("Date"),
87
+ sortable: true,
88
+ cell: (payment) => <FormattedDate date={payment.date} />,
89
+ },
90
+ {
91
+ id: "amount",
92
+ header: t("Amount"),
93
+ sortable: true,
94
+ align: "right",
95
+ cell: (payment) => <span className="font-medium">{payment.amount.toFixed(2)}</span>,
96
+ },
97
+ {
98
+ id: "type",
99
+ header: t("Type"),
100
+ sortable: true,
101
+ cell: (payment) => typeLabels[payment.type] ?? payment.type,
102
+ },
103
+ {
104
+ id: "invoice",
105
+ header: t("Invoice"),
106
+ cell: (payment) =>
107
+ payment.Invoice ? (
108
+ <Button
109
+ variant="link"
110
+ className="h-auto cursor-pointer p-0 text-foreground underline"
111
+ onClick={() => onViewInvoice?.(payment.Invoice!.id)}
112
+ >
113
+ {payment.Invoice.number}
114
+ </Button>
115
+ ) : (
116
+ <span className="text-muted-foreground">-</span>
117
+ ),
118
+ },
119
+ {
120
+ id: "reference",
121
+ header: t("Reference"),
122
+ cell: (payment) => payment.reference ?? "-",
123
+ },
124
+ {
125
+ id: "note",
126
+ header: t("Note"),
127
+ className: "max-w-[200px]",
128
+ cell: (payment) => <NoteCell note={payment.note} />,
129
+ },
130
+ {
131
+ id: "actions",
132
+ header: "",
133
+ align: "right",
134
+ cell: (payment) => (
135
+ <PaymentListRowActions
136
+ payment={payment}
137
+ entityId={entityId}
138
+ onViewInvoice={onViewInvoice}
139
+ onEditPayment={onEditPayment}
140
+ onDeleteSuccess={onDeleteSuccess}
141
+ onDeleteError={onDeleteError}
142
+ {...i18nProps}
143
+ />
144
+ ),
145
+ },
146
+ ],
147
+ [t, typeLabels, entityId, onViewInvoice, onEditPayment, onDeleteSuccess, onDeleteError, i18nProps],
148
+ );
149
+
150
+ return (
151
+ <DataTable
152
+ columns={columns}
153
+ queryParams={queryParams}
154
+ resourceName="payment"
155
+ cacheKey="payments"
156
+ onFetch={handleFetch}
157
+ onChangeParams={onChangeParams}
158
+ entityId={entityId}
159
+ />
160
+ );
161
+ }
162
+
163
+ /** Note cell with popover for long notes */
164
+ function NoteCell({ note }: { note?: string | null }) {
165
+ if (!note) return "-";
166
+
167
+ const hasLongNote = note.length > 30;
168
+
169
+ return (
170
+ <div className="flex items-center gap-1">
171
+ <span className="truncate">{note}</span>
172
+ {hasLongNote && (
173
+ <Popover>
174
+ <PopoverTrigger asChild>
175
+ <Button variant="ghost" size="icon" className="h-6 w-6 shrink-0">
176
+ <Info className="h-3.5 w-3.5 text-muted-foreground" />
177
+ </Button>
178
+ </PopoverTrigger>
179
+ <PopoverContent className="w-80">
180
+ <p className="text-sm">{note}</p>
181
+ </PopoverContent>
182
+ </Popover>
183
+ )}
184
+ </div>
185
+ );
186
+ }
@@ -0,0 +1,19 @@
1
+ export default {
2
+ Date: "Datum",
3
+ Amount: "Betrag",
4
+ Type: "Typ",
5
+ Invoice: "Rechnung",
6
+ Reference: "Referenz",
7
+ Note: "Notiz",
8
+ Actions: "Aktionen",
9
+ "Open menu": "Menü öffnen",
10
+ "Edit payment": "Zahlung bearbeiten",
11
+ "Delete payment": "Zahlung löschen",
12
+ "Deleting...": "Wird gelöscht...",
13
+ "View invoice": "Rechnung anzeigen",
14
+ "type.cash": "Bargeld",
15
+ "type.bank_transfer": "Banküberweisung",
16
+ "type.card": "Karte",
17
+ "type.check": "Scheck",
18
+ "type.other": "Andere",
19
+ } as const;
@@ -0,0 +1,19 @@
1
+ export default {
2
+ Date: "Datum",
3
+ Amount: "Znesek",
4
+ Type: "Vrsta",
5
+ Invoice: "Račun",
6
+ Reference: "Referenca",
7
+ Note: "Opomba",
8
+ Actions: "Akcije",
9
+ "Open menu": "Odpri meni",
10
+ "Edit payment": "Uredi plačilo",
11
+ "Delete payment": "Izbriši plačilo",
12
+ "Deleting...": "Brišem...",
13
+ "View invoice": "Prikaži račun",
14
+ "type.cash": "Gotovina",
15
+ "type.bank_transfer": "Bančno nakazilo",
16
+ "type.card": "Kartica",
17
+ "type.check": "Ček",
18
+ "type.other": "Drugo",
19
+ } as const;
@@ -0,0 +1,15 @@
1
+ import type { CreatePaymentRequest, Payment } from "@spaceinvoices/js-sdk";
2
+
3
+ import { createResourceHooks } from "@/ui/hooks/create-resource-hooks";
4
+
5
+ // Define a constant for the payments cache key
6
+ export const PAYMENTS_CACHE_KEY = "payments";
7
+
8
+ // Create payment-specific hooks using the factory
9
+ const {
10
+ useCreateResource: useCreatePayment,
11
+ useUpdateResource: useUpdatePayment,
12
+ useDeleteResource: useDeletePayment,
13
+ } = createResourceHooks<Payment, CreatePaymentRequest>("payments", PAYMENTS_CACHE_KEY);
14
+
15
+ export { useCreatePayment, useUpdatePayment, useDeletePayment };
@@ -0,0 +1,3 @@
1
+ export { RequestLogDetail } from "./request-log-detail";
2
+ export type { RequestLogResponse, RequestLogsPageProps } from "./request-logs-page";
3
+ export { REQUEST_LOGS_CACHE_KEY, RequestLogsPage } from "./request-logs-page";
@@ -0,0 +1,242 @@
1
+ "use client";
2
+
3
+ import { format, formatDistanceToNow } from "date-fns";
4
+ import { Check, Copy } from "lucide-react";
5
+ import { useState } from "react";
6
+ import { Button } from "@/ui/components/ui/button";
7
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/ui/components/ui/tabs";
8
+ import { cn } from "../../lib/utils";
9
+ import type { RequestLogResponse } from "./request-log-list-table";
10
+
11
+ type TranslationFn = (key: string, fallback: string) => string;
12
+
13
+ const defaultT: TranslationFn = (_key, fallback) => fallback;
14
+
15
+ export interface RequestLogDetailProps {
16
+ log: RequestLogResponse;
17
+ /** Translation function - defaults to returning fallback */
18
+ t?: TranslationFn;
19
+ }
20
+
21
+ /**
22
+ * Try to unwrap response data that may be wrapped as { text: "json string" }
23
+ */
24
+ function unwrapResponseData(data: unknown): unknown {
25
+ if (
26
+ data &&
27
+ typeof data === "object" &&
28
+ "text" in data &&
29
+ Object.keys(data).length === 1 &&
30
+ typeof (data as { text: unknown }).text === "string"
31
+ ) {
32
+ try {
33
+ return JSON.parse((data as { text: string }).text);
34
+ } catch {
35
+ // Not valid JSON, return original
36
+ return data;
37
+ }
38
+ }
39
+ return data;
40
+ }
41
+
42
+ function JsonViewer({ data, label }: { data: unknown; label: string }) {
43
+ const [copied, setCopied] = useState(false);
44
+ // Unwrap { text: "json" } format if present
45
+ const unwrappedData = unwrapResponseData(data);
46
+ const jsonString = unwrappedData ? JSON.stringify(unwrappedData, null, 2) : null;
47
+
48
+ const handleCopy = async () => {
49
+ if (jsonString) {
50
+ await navigator.clipboard.writeText(jsonString);
51
+ setCopied(true);
52
+ setTimeout(() => setCopied(false), 2000);
53
+ }
54
+ };
55
+
56
+ if (
57
+ !unwrappedData ||
58
+ (typeof unwrappedData === "object" && Object.keys(unwrappedData as Record<string, unknown>).length === 0)
59
+ ) {
60
+ return (
61
+ <div className="rounded-lg border bg-muted/30 p-4 text-center text-muted-foreground text-sm">
62
+ No {label.toLowerCase()} data
63
+ </div>
64
+ );
65
+ }
66
+
67
+ return (
68
+ <div className="relative">
69
+ <Button
70
+ variant="ghost"
71
+ size="sm"
72
+ className="absolute top-2 right-2 h-8 w-8 p-0"
73
+ onClick={handleCopy}
74
+ title="Copy to clipboard"
75
+ >
76
+ {copied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
77
+ </Button>
78
+ <pre className="overflow-x-auto rounded-lg border bg-muted/30 p-4 font-mono text-sm">
79
+ <JsonHighlight json={jsonString ?? ""} />
80
+ </pre>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ function JsonHighlight({ json }: { json: string }) {
86
+ // Parse JSON into tokens for safe rendering
87
+ const tokens: { type: string; value: string }[] = [];
88
+ const lines = json.split("\n");
89
+
90
+ for (const line of lines) {
91
+ // Match key-value patterns
92
+ const keyMatch = line.match(/^(\s*)"([^"]+)":/);
93
+ if (keyMatch) {
94
+ const [, indent, key] = keyMatch;
95
+ const rest = line.slice(keyMatch[0].length);
96
+ tokens.push({ type: "indent", value: indent });
97
+ tokens.push({ type: "key", value: `"${key}"` });
98
+ tokens.push({ type: "punctuation", value: ":" });
99
+
100
+ // Parse value
101
+ const valueMatch = rest.match(/^\s*(.+?)(,?)$/);
102
+ if (valueMatch) {
103
+ const [, value, comma] = valueMatch;
104
+ tokens.push({ type: "space", value: " " });
105
+ if (value.startsWith('"')) {
106
+ tokens.push({ type: "string", value: value.replace(/,$/, "") });
107
+ } else if (value === "true" || value === "false") {
108
+ tokens.push({ type: "boolean", value });
109
+ } else if (value === "null") {
110
+ tokens.push({ type: "null", value });
111
+ } else if (!Number.isNaN(Number(value.replace(/,$/, "")))) {
112
+ tokens.push({ type: "number", value: value.replace(/,$/, "") });
113
+ } else {
114
+ tokens.push({ type: "other", value: value.replace(/,$/, "") });
115
+ }
116
+ if (comma) tokens.push({ type: "punctuation", value: comma });
117
+ }
118
+ } else {
119
+ tokens.push({ type: "other", value: line });
120
+ }
121
+ tokens.push({ type: "newline", value: "\n" });
122
+ }
123
+
124
+ const colorMap: Record<string, string> = {
125
+ key: "text-blue-600 dark:text-blue-400",
126
+ string: "text-green-600 dark:text-green-400",
127
+ number: "text-orange-600 dark:text-orange-400",
128
+ boolean: "text-purple-600 dark:text-purple-400",
129
+ null: "text-gray-500",
130
+ };
131
+
132
+ return (
133
+ <code>
134
+ {tokens.map((token, i) => (
135
+ <span key={`${i}-${token.type}`} className={colorMap[token.type] || ""}>
136
+ {token.value}
137
+ </span>
138
+ ))}
139
+ </code>
140
+ );
141
+ }
142
+
143
+ export function RequestLogDetail({ log, t = defaultT }: RequestLogDetailProps) {
144
+ const createdAt = new Date(log.created_at);
145
+ const statusCode = log.res_status ? Number.parseInt(log.res_status, 10) : 0;
146
+
147
+ let statusColor = "text-gray-500";
148
+ let statusBg = "bg-gray-100 dark:bg-gray-800";
149
+ if (statusCode >= 200 && statusCode < 300) {
150
+ statusColor = "text-green-700 dark:text-green-400";
151
+ statusBg = "bg-green-100 dark:bg-green-900";
152
+ } else if (statusCode >= 400 && statusCode < 500) {
153
+ statusColor = "text-yellow-700 dark:text-yellow-400";
154
+ statusBg = "bg-yellow-100 dark:bg-yellow-900";
155
+ } else if (statusCode >= 500) {
156
+ statusColor = "text-red-700 dark:text-red-400";
157
+ statusBg = "bg-red-100 dark:bg-red-900";
158
+ }
159
+
160
+ return (
161
+ <div className="space-y-6 p-4 pt-6">
162
+ {/* Summary */}
163
+ <div className="flex flex-wrap items-center gap-4">
164
+ <span className={cn("rounded-md px-2.5 py-1 font-medium font-mono text-sm", statusBg, statusColor)}>
165
+ {log.res_status || "—"}
166
+ </span>
167
+ <div className="text-muted-foreground text-sm">
168
+ <time dateTime={log.created_at} title={format(createdAt, "PPpp")}>
169
+ {formatDistanceToNow(createdAt, { addSuffix: true })}
170
+ </time>
171
+ <span className="mx-2">•</span>
172
+ <span>{format(createdAt, "PPpp")}</span>
173
+ </div>
174
+ </div>
175
+
176
+ {/* Metadata */}
177
+ {(log.resource_type || log.resource_id || log.action) && (
178
+ <div className="flex flex-wrap gap-4 text-sm">
179
+ {log.resource_type && (
180
+ <div>
181
+ <span className="text-muted-foreground">{t("request-logs.detail.resource-type", "Resource")}:</span>{" "}
182
+ <span className="font-medium">{log.resource_type}</span>
183
+ </div>
184
+ )}
185
+ {log.resource_id && (
186
+ <div>
187
+ <span className="text-muted-foreground">{t("request-logs.detail.resource-id", "ID")}:</span>{" "}
188
+ <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">{log.resource_id}</code>
189
+ </div>
190
+ )}
191
+ {log.action && (
192
+ <div>
193
+ <span className="text-muted-foreground">{t("request-logs.detail.action", "Action")}:</span>{" "}
194
+ <span className="font-medium">{log.action}</span>
195
+ </div>
196
+ )}
197
+ </div>
198
+ )}
199
+
200
+ {/* Tabs for Request/Response/Headers */}
201
+ <Tabs defaultValue="request" className="w-full">
202
+ <TabsList className="grid w-full grid-cols-3">
203
+ <TabsTrigger value="request">{t("request-logs.detail.request", "Request")}</TabsTrigger>
204
+ <TabsTrigger value="response">{t("request-logs.detail.response", "Response")}</TabsTrigger>
205
+ <TabsTrigger value="headers">{t("request-logs.detail.headers", "Headers")}</TabsTrigger>
206
+ </TabsList>
207
+ <TabsContent value="request" className="mt-4">
208
+ <JsonViewer data={log.req_body} label="Request body" />
209
+ </TabsContent>
210
+ <TabsContent value="response" className="mt-4">
211
+ <JsonViewer data={log.res_body} label="Response body" />
212
+ </TabsContent>
213
+ <TabsContent value="headers" className="mt-4">
214
+ <JsonViewer data={log.headers} label="Headers" />
215
+ </TabsContent>
216
+ </Tabs>
217
+
218
+ {/* Request ID */}
219
+ <RequestIdDisplay requestId={log.request_id} t={t} />
220
+ </div>
221
+ );
222
+ }
223
+
224
+ function RequestIdDisplay({ requestId, t }: { requestId: string; t: (key: string, fallback: string) => string }) {
225
+ const [copied, setCopied] = useState(false);
226
+
227
+ const handleCopy = async () => {
228
+ await navigator.clipboard.writeText(requestId);
229
+ setCopied(true);
230
+ setTimeout(() => setCopied(false), 2000);
231
+ };
232
+
233
+ return (
234
+ <div className="flex items-center gap-2 border-t pt-4 text-muted-foreground text-xs">
235
+ <span>{t("request-logs.detail.request-id", "Request ID")}:</span>
236
+ <code className="font-mono">{requestId}</code>
237
+ <Button variant="ghost" size="sm" className="h-6 w-6 p-0" onClick={handleCopy} title="Copy to clipboard">
238
+ {copied ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3" />}
239
+ </Button>
240
+ </div>
241
+ );
242
+ }