@hub-kit/core 0.3.0

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 (499) hide show
  1. package/README.md +125 -0
  2. package/cli/hub-kit.mjs +112 -0
  3. package/dist/adapters/bank-reconciliation.d.ts +100 -0
  4. package/dist/adapters/bank-reconciliation.js +1 -0
  5. package/dist/adapters/bank-sync-log.d.ts +46 -0
  6. package/dist/adapters/bank-sync-log.js +1 -0
  7. package/dist/adapters/category-rules.d.ts +94 -0
  8. package/dist/adapters/category-rules.js +1 -0
  9. package/dist/adapters/companies.d.ts +85 -0
  10. package/dist/adapters/companies.js +3 -0
  11. package/dist/adapters/customers.d.ts +71 -0
  12. package/dist/adapters/customers.js +1 -0
  13. package/dist/adapters/exclusion-rules.d.ts +34 -0
  14. package/dist/adapters/exclusion-rules.js +15 -0
  15. package/dist/adapters/file-naming.d.ts +36 -0
  16. package/dist/adapters/file-naming.js +1 -0
  17. package/dist/adapters/index.d.ts +24 -0
  18. package/dist/adapters/index.js +1 -0
  19. package/dist/adapters/invoice-detail.d.ts +126 -0
  20. package/dist/adapters/invoice-detail.js +1 -0
  21. package/dist/adapters/invoice-list.d.ts +63 -0
  22. package/dist/adapters/invoice-list.js +1 -0
  23. package/dist/adapters/invoice-upload.d.ts +26 -0
  24. package/dist/adapters/invoice-upload.js +1 -0
  25. package/dist/adapters/manual-bookings.d.ts +56 -0
  26. package/dist/adapters/manual-bookings.js +1 -0
  27. package/dist/adapters/notifications.d.ts +31 -0
  28. package/dist/adapters/notifications.js +1 -0
  29. package/dist/adapters/open-items.d.ts +42 -0
  30. package/dist/adapters/open-items.js +1 -0
  31. package/dist/adapters/opos-whitelist.d.ts +38 -0
  32. package/dist/adapters/opos-whitelist.js +1 -0
  33. package/dist/adapters/outgoing-invoices.d.ts +32 -0
  34. package/dist/adapters/outgoing-invoices.js +1 -0
  35. package/dist/adapters/overview.d.ts +68 -0
  36. package/dist/adapters/overview.js +1 -0
  37. package/dist/adapters/processing-log.d.ts +48 -0
  38. package/dist/adapters/processing-log.js +1 -0
  39. package/dist/adapters/properties.d.ts +87 -0
  40. package/dist/adapters/properties.js +1 -0
  41. package/dist/adapters/supplier-detail.d.ts +64 -0
  42. package/dist/adapters/supplier-detail.js +1 -0
  43. package/dist/adapters/supplier-list.d.ts +21 -0
  44. package/dist/adapters/supplier-list.js +1 -0
  45. package/dist/adapters/team.d.ts +79 -0
  46. package/dist/adapters/team.js +1 -0
  47. package/dist/adapters/trash.d.ts +26 -0
  48. package/dist/adapters/trash.js +1 -0
  49. package/dist/adapters/vat-rules.d.ts +83 -0
  50. package/dist/adapters/vat-rules.js +1 -0
  51. package/dist/components/access/index.d.ts +3 -0
  52. package/dist/components/access/index.js +3 -0
  53. package/dist/components/access/permission-checklist.d.ts +13 -0
  54. package/dist/components/access/permission-checklist.js +19 -0
  55. package/dist/components/access/permission-matrix.d.ts +12 -0
  56. package/dist/components/access/permission-matrix.js +18 -0
  57. package/dist/components/access/types.d.ts +15 -0
  58. package/dist/components/access/types.js +1 -0
  59. package/dist/components/dashboard/date-range-calendar.d.ts +19 -0
  60. package/dist/components/dashboard/date-range-calendar.js +97 -0
  61. package/dist/components/dashboard/index.d.ts +23 -0
  62. package/dist/components/dashboard/index.js +14 -0
  63. package/dist/components/dashboard/kpi-card.d.ts +13 -0
  64. package/dist/components/dashboard/kpi-card.js +7 -0
  65. package/dist/components/dashboard/money-cards-row.d.ts +29 -0
  66. package/dist/components/dashboard/money-cards-row.js +28 -0
  67. package/dist/components/dashboard/money-trend-chart.d.ts +13 -0
  68. package/dist/components/dashboard/money-trend-chart.js +16 -0
  69. package/dist/components/dashboard/panel.d.ts +9 -0
  70. package/dist/components/dashboard/panel.js +5 -0
  71. package/dist/components/dashboard/period-picker.d.ts +25 -0
  72. package/dist/components/dashboard/period-picker.js +82 -0
  73. package/dist/components/dashboard/periods.d.ts +13 -0
  74. package/dist/components/dashboard/periods.js +63 -0
  75. package/dist/components/dashboard/pipeline-stages-panel.d.ts +14 -0
  76. package/dist/components/dashboard/pipeline-stages-panel.js +12 -0
  77. package/dist/components/dashboard/processing-summary-panel.d.ts +34 -0
  78. package/dist/components/dashboard/processing-summary-panel.js +11 -0
  79. package/dist/components/dashboard/ranked-bars.d.ts +15 -0
  80. package/dist/components/dashboard/ranked-bars.js +16 -0
  81. package/dist/components/dashboard/ranked-list-panel.d.ts +14 -0
  82. package/dist/components/dashboard/ranked-list-panel.js +9 -0
  83. package/dist/components/dashboard/stage-tiles.d.ts +15 -0
  84. package/dist/components/dashboard/stage-tiles.js +6 -0
  85. package/dist/components/dashboard/stat-rows-panel.d.ts +25 -0
  86. package/dist/components/dashboard/stat-rows-panel.js +7 -0
  87. package/dist/components/dashboard/stat-tile.d.ts +20 -0
  88. package/dist/components/dashboard/stat-tile.js +9 -0
  89. package/dist/components/feedback/index.d.ts +4 -0
  90. package/dist/components/feedback/index.js +2 -0
  91. package/dist/components/feedback/query-states.d.ts +27 -0
  92. package/dist/components/feedback/query-states.js +38 -0
  93. package/dist/components/feedback/table-pagination.d.ts +20 -0
  94. package/dist/components/feedback/table-pagination.js +15 -0
  95. package/dist/components/invoice-queue/index.d.ts +5 -0
  96. package/dist/components/invoice-queue/index.js +3 -0
  97. package/dist/components/invoice-queue/next-action-cell.d.ts +5 -0
  98. package/dist/components/invoice-queue/next-action-cell.js +9 -0
  99. package/dist/components/invoice-queue/queue-kpi-card.d.ts +19 -0
  100. package/dist/components/invoice-queue/queue-kpi-card.js +31 -0
  101. package/dist/components/invoice-queue/queue-kpi-row.d.ts +22 -0
  102. package/dist/components/invoice-queue/queue-kpi-row.js +12 -0
  103. package/dist/components/notifications/alerts.d.ts +25 -0
  104. package/dist/components/notifications/alerts.js +66 -0
  105. package/dist/components/notifications/bell.d.ts +14 -0
  106. package/dist/components/notifications/bell.js +17 -0
  107. package/dist/components/notifications/highlight.d.ts +4 -0
  108. package/dist/components/notifications/highlight.js +7 -0
  109. package/dist/components/notifications/index.d.ts +7 -0
  110. package/dist/components/notifications/index.js +6 -0
  111. package/dist/components/notifications/reminder-dialog.d.ts +24 -0
  112. package/dist/components/notifications/reminder-dialog.js +54 -0
  113. package/dist/components/notifications/types.d.ts +27 -0
  114. package/dist/components/notifications/types.js +1 -0
  115. package/dist/components/notifications/use-notification-acks.d.ts +7 -0
  116. package/dist/components/notifications/use-notification-acks.js +37 -0
  117. package/dist/components/shell/breadcrumbs.d.ts +7 -0
  118. package/dist/components/shell/breadcrumbs.js +38 -0
  119. package/dist/components/shell/index.d.ts +3 -0
  120. package/dist/components/shell/index.js +3 -0
  121. package/dist/components/shell/shell.d.ts +30 -0
  122. package/dist/components/shell/shell.js +110 -0
  123. package/dist/components/shell/types.d.ts +23 -0
  124. package/dist/components/shell/types.js +3 -0
  125. package/dist/components/tour/find-target.d.ts +1 -0
  126. package/dist/components/tour/find-target.js +3 -0
  127. package/dist/components/tour/index.d.ts +14 -0
  128. package/dist/components/tour/index.js +8 -0
  129. package/dist/components/tour/labels.d.ts +10 -0
  130. package/dist/components/tour/labels.js +9 -0
  131. package/dist/components/tour/seen-store.d.ts +3 -0
  132. package/dist/components/tour/seen-store.js +34 -0
  133. package/dist/components/tour/tour-blocks.d.ts +4 -0
  134. package/dist/components/tour/tour-blocks.js +27 -0
  135. package/dist/components/tour/tour-button.d.ts +4 -0
  136. package/dist/components/tour/tour-button.js +12 -0
  137. package/dist/components/tour/tour-card.d.ts +11 -0
  138. package/dist/components/tour/tour-card.js +7 -0
  139. package/dist/components/tour/tour-context.d.ts +16 -0
  140. package/dist/components/tour/tour-context.js +2 -0
  141. package/dist/components/tour/tour-overlay.d.ts +1 -0
  142. package/dist/components/tour/tour-overlay.js +165 -0
  143. package/dist/components/tour/tour-provider.d.ts +9 -0
  144. package/dist/components/tour/tour-provider.js +80 -0
  145. package/dist/components/tour/types.d.ts +44 -0
  146. package/dist/components/tour/types.js +1 -0
  147. package/dist/components/tour/use-tour.d.ts +3 -0
  148. package/dist/components/tour/use-tour.js +9 -0
  149. package/dist/hooks/use-mobile.d.ts +1 -0
  150. package/dist/hooks/use-mobile.js +15 -0
  151. package/dist/i18n/index.d.ts +15 -0
  152. package/dist/i18n/index.js +15 -0
  153. package/dist/lib/bank-import/classify.d.ts +8 -0
  154. package/dist/lib/bank-import/classify.js +89 -0
  155. package/dist/lib/bank-import/column-guess.d.ts +2 -0
  156. package/dist/lib/bank-import/column-guess.js +40 -0
  157. package/dist/lib/bank-import/csv.d.ts +3 -0
  158. package/dist/lib/bank-import/csv.js +20 -0
  159. package/dist/lib/bank-import/duplicates.d.ts +2 -0
  160. package/dist/lib/bank-import/duplicates.js +21 -0
  161. package/dist/lib/bank-import/index.d.ts +12 -0
  162. package/dist/lib/bank-import/index.js +9 -0
  163. package/dist/lib/bank-import/mapping-storage.d.ts +3 -0
  164. package/dist/lib/bank-import/mapping-storage.js +29 -0
  165. package/dist/lib/bank-import/normalize.d.ts +4 -0
  166. package/dist/lib/bank-import/normalize.js +82 -0
  167. package/dist/lib/bank-import/parse-file.d.ts +7 -0
  168. package/dist/lib/bank-import/parse-file.js +23 -0
  169. package/dist/lib/bank-import/types.d.ts +31 -0
  170. package/dist/lib/bank-import/types.js +1 -0
  171. package/dist/lib/bank-import/xlsx.d.ts +2 -0
  172. package/dist/lib/bank-import/xlsx.js +25 -0
  173. package/dist/lib/class-names.d.ts +2 -0
  174. package/dist/lib/class-names.js +5 -0
  175. package/dist/lib/download.d.ts +2 -0
  176. package/dist/lib/download.js +14 -0
  177. package/dist/lib/formatters.d.ts +6 -0
  178. package/dist/lib/formatters.js +24 -0
  179. package/dist/lib/index.d.ts +9 -0
  180. package/dist/lib/index.js +6 -0
  181. package/dist/lib/opos-whitelist/index.d.ts +2 -0
  182. package/dist/lib/opos-whitelist/index.js +1 -0
  183. package/dist/lib/opos-whitelist/opos.d.ts +14 -0
  184. package/dist/lib/opos-whitelist/opos.js +49 -0
  185. package/dist/lib/query-result.d.ts +12 -0
  186. package/dist/lib/query-result.js +1 -0
  187. package/dist/lib/tour-placeholders/index.d.ts +2 -0
  188. package/dist/lib/tour-placeholders/index.js +1 -0
  189. package/dist/lib/tour-placeholders/overview.d.ts +12 -0
  190. package/dist/lib/tour-placeholders/overview.js +139 -0
  191. package/dist/pages/bank-accounts/BankAccountsPage.d.ts +7 -0
  192. package/dist/pages/bank-accounts/BankAccountsPage.js +21 -0
  193. package/dist/pages/bank-accounts/index.d.ts +4 -0
  194. package/dist/pages/bank-accounts/index.js +2 -0
  195. package/dist/pages/bank-accounts/labels.d.ts +15 -0
  196. package/dist/pages/bank-accounts/labels.js +14 -0
  197. package/dist/pages/bank-import/BankImportWizard.d.ts +9 -0
  198. package/dist/pages/bank-import/BankImportWizard.js +158 -0
  199. package/dist/pages/bank-import/index.d.ts +4 -0
  200. package/dist/pages/bank-import/index.js +2 -0
  201. package/dist/pages/bank-import/labels.d.ts +44 -0
  202. package/dist/pages/bank-import/labels.js +43 -0
  203. package/dist/pages/bank-sync-log/BankSyncLogPage.d.ts +7 -0
  204. package/dist/pages/bank-sync-log/BankSyncLogPage.js +77 -0
  205. package/dist/pages/bank-sync-log/index.d.ts +4 -0
  206. package/dist/pages/bank-sync-log/index.js +2 -0
  207. package/dist/pages/bank-sync-log/labels.d.ts +21 -0
  208. package/dist/pages/bank-sync-log/labels.js +20 -0
  209. package/dist/pages/bank-transactions/BankMatchPanel.d.ts +8 -0
  210. package/dist/pages/bank-transactions/BankMatchPanel.js +75 -0
  211. package/dist/pages/bank-transactions/BankTransactionsPage.d.ts +7 -0
  212. package/dist/pages/bank-transactions/BankTransactionsPage.js +59 -0
  213. package/dist/pages/bank-transactions/badges.d.ts +8 -0
  214. package/dist/pages/bank-transactions/badges.js +24 -0
  215. package/dist/pages/bank-transactions/index.d.ts +6 -0
  216. package/dist/pages/bank-transactions/index.js +4 -0
  217. package/dist/pages/bank-transactions/labels.d.ts +41 -0
  218. package/dist/pages/bank-transactions/labels.js +40 -0
  219. package/dist/pages/bank-transactions/sort-control.d.ts +17 -0
  220. package/dist/pages/bank-transactions/sort-control.js +8 -0
  221. package/dist/pages/bank-transactions/use-table-view.d.ts +23 -0
  222. package/dist/pages/bank-transactions/use-table-view.js +54 -0
  223. package/dist/pages/category-rules/CategoryRulesPage.d.ts +7 -0
  224. package/dist/pages/category-rules/CategoryRulesPage.js +160 -0
  225. package/dist/pages/category-rules/index.d.ts +4 -0
  226. package/dist/pages/category-rules/index.js +2 -0
  227. package/dist/pages/category-rules/labels.d.ts +66 -0
  228. package/dist/pages/category-rules/labels.js +72 -0
  229. package/dist/pages/companies/company-detail-page.d.ts +10 -0
  230. package/dist/pages/companies/company-detail-page.js +184 -0
  231. package/dist/pages/companies/company-form.d.ts +20 -0
  232. package/dist/pages/companies/company-form.js +23 -0
  233. package/dist/pages/companies/company-list-page.d.ts +9 -0
  234. package/dist/pages/companies/company-list-page.js +109 -0
  235. package/dist/pages/companies/known-spellings-card.d.ts +8 -0
  236. package/dist/pages/companies/known-spellings-card.js +65 -0
  237. package/dist/pages/companies/labels.d.ts +105 -0
  238. package/dist/pages/companies/labels.js +104 -0
  239. package/dist/pages/companies/sort-control.d.ts +17 -0
  240. package/dist/pages/companies/sort-control.js +8 -0
  241. package/dist/pages/companies/use-infinite-rows.d.ts +6 -0
  242. package/dist/pages/companies/use-infinite-rows.js +24 -0
  243. package/dist/pages/companies/use-table-view.d.ts +23 -0
  244. package/dist/pages/companies/use-table-view.js +56 -0
  245. package/dist/pages/customers/company-chip.d.ts +6 -0
  246. package/dist/pages/customers/company-chip.js +9 -0
  247. package/dist/pages/customers/copy-button.d.ts +6 -0
  248. package/dist/pages/customers/copy-button.js +31 -0
  249. package/dist/pages/customers/customer-detail-page.d.ts +10 -0
  250. package/dist/pages/customers/customer-detail-page.js +83 -0
  251. package/dist/pages/customers/customer-list-page.d.ts +9 -0
  252. package/dist/pages/customers/customer-list-page.js +65 -0
  253. package/dist/pages/customers/edit-customer-dialog.d.ts +9 -0
  254. package/dist/pages/customers/edit-customer-dialog.js +72 -0
  255. package/dist/pages/customers/form-hints.d.ts +6 -0
  256. package/dist/pages/customers/form-hints.js +9 -0
  257. package/dist/pages/customers/invoice-status-badge.d.ts +10 -0
  258. package/dist/pages/customers/invoice-status-badge.js +22 -0
  259. package/dist/pages/customers/labels.d.ts +116 -0
  260. package/dist/pages/customers/labels.js +117 -0
  261. package/dist/pages/customers/new-customer-dialog.d.ts +6 -0
  262. package/dist/pages/customers/new-customer-dialog.js +93 -0
  263. package/dist/pages/customers/sort-control.d.ts +14 -0
  264. package/dist/pages/customers/sort-control.js +9 -0
  265. package/dist/pages/customers/use-table-view.d.ts +23 -0
  266. package/dist/pages/customers/use-table-view.js +56 -0
  267. package/dist/pages/exclusion-rules/exclusion-rules-page.d.ts +7 -0
  268. package/dist/pages/exclusion-rules/exclusion-rules-page.js +114 -0
  269. package/dist/pages/exclusion-rules/labels.d.ts +53 -0
  270. package/dist/pages/exclusion-rules/labels.js +61 -0
  271. package/dist/pages/file-naming/build-filename.d.ts +3 -0
  272. package/dist/pages/file-naming/build-filename.js +82 -0
  273. package/dist/pages/file-naming/file-naming-page.d.ts +10 -0
  274. package/dist/pages/file-naming/file-naming-page.js +120 -0
  275. package/dist/pages/file-naming/labels.d.ts +44 -0
  276. package/dist/pages/file-naming/labels.js +44 -0
  277. package/dist/pages/index.d.ts +63 -0
  278. package/dist/pages/index.js +48 -0
  279. package/dist/pages/invoice-detail/InvoiceDetailPage.d.ts +11 -0
  280. package/dist/pages/invoice-detail/InvoiceDetailPage.js +292 -0
  281. package/dist/pages/invoice-detail/OutgoingInvoiceFlag.d.ts +26 -0
  282. package/dist/pages/invoice-detail/OutgoingInvoiceFlag.js +16 -0
  283. package/dist/pages/invoice-detail/ReviewBadge.d.ts +15 -0
  284. package/dist/pages/invoice-detail/ReviewBadge.js +23 -0
  285. package/dist/pages/invoice-detail/ReviewCard.d.ts +17 -0
  286. package/dist/pages/invoice-detail/ReviewCard.js +12 -0
  287. package/dist/pages/invoice-detail/SideBySide.d.ts +5 -0
  288. package/dist/pages/invoice-detail/SideBySide.js +5 -0
  289. package/dist/pages/invoice-detail/WorkflowHistoryList.d.ts +23 -0
  290. package/dist/pages/invoice-detail/WorkflowHistoryList.js +32 -0
  291. package/dist/pages/invoice-detail/WorkflowLadder.d.ts +20 -0
  292. package/dist/pages/invoice-detail/WorkflowLadder.js +16 -0
  293. package/dist/pages/invoice-detail/history.d.ts +23 -0
  294. package/dist/pages/invoice-detail/history.js +76 -0
  295. package/dist/pages/invoice-detail/index.d.ts +21 -0
  296. package/dist/pages/invoice-detail/index.js +11 -0
  297. package/dist/pages/invoice-detail/labels.d.ts +79 -0
  298. package/dist/pages/invoice-detail/labels.js +139 -0
  299. package/dist/pages/invoice-detail/pipeline-types.d.ts +58 -0
  300. package/dist/pages/invoice-detail/pipeline-types.js +1 -0
  301. package/dist/pages/invoice-detail/review-recheck.d.ts +8 -0
  302. package/dist/pages/invoice-detail/review-recheck.js +103 -0
  303. package/dist/pages/invoice-detail/review.d.ts +91 -0
  304. package/dist/pages/invoice-detail/review.js +310 -0
  305. package/dist/pages/invoice-list/badges.d.ts +23 -0
  306. package/dist/pages/invoice-list/badges.js +26 -0
  307. package/dist/pages/invoice-list/index.d.ts +5 -0
  308. package/dist/pages/invoice-list/index.js +3 -0
  309. package/dist/pages/invoice-list/invoice-list-page.d.ts +13 -0
  310. package/dist/pages/invoice-list/invoice-list-page.js +114 -0
  311. package/dist/pages/invoice-list/labels.d.ts +36 -0
  312. package/dist/pages/invoice-list/labels.js +29 -0
  313. package/dist/pages/invoice-list/sort-control.d.ts +17 -0
  314. package/dist/pages/invoice-list/sort-control.js +8 -0
  315. package/dist/pages/invoice-list/use-table-view.d.ts +23 -0
  316. package/dist/pages/invoice-list/use-table-view.js +54 -0
  317. package/dist/pages/invoice-upload/InvoiceUploadPage.d.ts +7 -0
  318. package/dist/pages/invoice-upload/InvoiceUploadPage.js +115 -0
  319. package/dist/pages/invoice-upload/index.d.ts +4 -0
  320. package/dist/pages/invoice-upload/index.js +2 -0
  321. package/dist/pages/invoice-upload/labels.d.ts +29 -0
  322. package/dist/pages/invoice-upload/labels.js +28 -0
  323. package/dist/pages/manual-bookings/ManualBookingsPage.d.ts +7 -0
  324. package/dist/pages/manual-bookings/ManualBookingsPage.js +161 -0
  325. package/dist/pages/manual-bookings/index.d.ts +4 -0
  326. package/dist/pages/manual-bookings/index.js +2 -0
  327. package/dist/pages/manual-bookings/labels.d.ts +53 -0
  328. package/dist/pages/manual-bookings/labels.js +52 -0
  329. package/dist/pages/notifications/labels.d.ts +22 -0
  330. package/dist/pages/notifications/labels.js +21 -0
  331. package/dist/pages/notifications/notifications-page.d.ts +14 -0
  332. package/dist/pages/notifications/notifications-page.js +81 -0
  333. package/dist/pages/open-items/OpenItemsPage.d.ts +11 -0
  334. package/dist/pages/open-items/OpenItemsPage.js +110 -0
  335. package/dist/pages/open-items/index.d.ts +6 -0
  336. package/dist/pages/open-items/index.js +3 -0
  337. package/dist/pages/open-items/labels.d.ts +37 -0
  338. package/dist/pages/open-items/labels.js +36 -0
  339. package/dist/pages/open-items/urgency.d.ts +8 -0
  340. package/dist/pages/open-items/urgency.js +27 -0
  341. package/dist/pages/opos-whitelist/OposWhitelistPage.d.ts +7 -0
  342. package/dist/pages/opos-whitelist/OposWhitelistPage.js +165 -0
  343. package/dist/pages/opos-whitelist/index.d.ts +4 -0
  344. package/dist/pages/opos-whitelist/index.js +2 -0
  345. package/dist/pages/opos-whitelist/labels.d.ts +56 -0
  346. package/dist/pages/opos-whitelist/labels.js +70 -0
  347. package/dist/pages/outgoing-invoices/OutgoingInvoicesPage.d.ts +7 -0
  348. package/dist/pages/outgoing-invoices/OutgoingInvoicesPage.js +169 -0
  349. package/dist/pages/outgoing-invoices/index.d.ts +4 -0
  350. package/dist/pages/outgoing-invoices/index.js +2 -0
  351. package/dist/pages/outgoing-invoices/labels.d.ts +43 -0
  352. package/dist/pages/outgoing-invoices/labels.js +42 -0
  353. package/dist/pages/outgoing-invoices/sort-control.d.ts +17 -0
  354. package/dist/pages/outgoing-invoices/sort-control.js +8 -0
  355. package/dist/pages/outgoing-invoices/use-table-view.d.ts +23 -0
  356. package/dist/pages/outgoing-invoices/use-table-view.js +54 -0
  357. package/dist/pages/overview/labels.d.ts +35 -0
  358. package/dist/pages/overview/labels.js +34 -0
  359. package/dist/pages/overview/overview-page.d.ts +22 -0
  360. package/dist/pages/overview/overview-page.js +127 -0
  361. package/dist/pages/processing-log/labels.d.ts +54 -0
  362. package/dist/pages/processing-log/labels.js +53 -0
  363. package/dist/pages/processing-log/mail-parsing.d.ts +21 -0
  364. package/dist/pages/processing-log/mail-parsing.js +52 -0
  365. package/dist/pages/processing-log/processing-log-page.d.ts +9 -0
  366. package/dist/pages/processing-log/processing-log-page.js +206 -0
  367. package/dist/pages/properties/badges.d.ts +20 -0
  368. package/dist/pages/properties/badges.js +45 -0
  369. package/dist/pages/properties/company-assignment-field.d.ts +11 -0
  370. package/dist/pages/properties/company-assignment-field.js +13 -0
  371. package/dist/pages/properties/form-field.d.ts +6 -0
  372. package/dist/pages/properties/form-field.js +9 -0
  373. package/dist/pages/properties/labels.d.ts +124 -0
  374. package/dist/pages/properties/labels.js +128 -0
  375. package/dist/pages/properties/name-variants-card.d.ts +7 -0
  376. package/dist/pages/properties/name-variants-card.js +60 -0
  377. package/dist/pages/properties/property-detail-page.d.ts +10 -0
  378. package/dist/pages/properties/property-detail-page.js +150 -0
  379. package/dist/pages/properties/property-list-page.d.ts +13 -0
  380. package/dist/pages/properties/property-list-page.js +201 -0
  381. package/dist/pages/properties/sort-control.d.ts +14 -0
  382. package/dist/pages/properties/sort-control.js +9 -0
  383. package/dist/pages/properties/use-infinite-rows.d.ts +6 -0
  384. package/dist/pages/properties/use-infinite-rows.js +26 -0
  385. package/dist/pages/properties/use-table-view.d.ts +23 -0
  386. package/dist/pages/properties/use-table-view.js +56 -0
  387. package/dist/pages/supplier-detail/SupplierDetailPage.d.ts +10 -0
  388. package/dist/pages/supplier-detail/SupplierDetailPage.js +157 -0
  389. package/dist/pages/supplier-detail/index.d.ts +4 -0
  390. package/dist/pages/supplier-detail/index.js +2 -0
  391. package/dist/pages/supplier-detail/labels.d.ts +46 -0
  392. package/dist/pages/supplier-detail/labels.js +45 -0
  393. package/dist/pages/supplier-list/SupplierListPage.d.ts +9 -0
  394. package/dist/pages/supplier-list/SupplierListPage.js +102 -0
  395. package/dist/pages/supplier-list/index.d.ts +4 -0
  396. package/dist/pages/supplier-list/index.js +2 -0
  397. package/dist/pages/supplier-list/labels.d.ts +33 -0
  398. package/dist/pages/supplier-list/labels.js +32 -0
  399. package/dist/pages/supplier-list/sort-control.d.ts +17 -0
  400. package/dist/pages/supplier-list/sort-control.js +8 -0
  401. package/dist/pages/supplier-list/use-table-view.d.ts +23 -0
  402. package/dist/pages/supplier-list/use-table-view.js +54 -0
  403. package/dist/pages/team/account-dialogs.d.ts +12 -0
  404. package/dist/pages/team/account-dialogs.js +49 -0
  405. package/dist/pages/team/employee-editor.d.ts +19 -0
  406. package/dist/pages/team/employee-editor.js +124 -0
  407. package/dist/pages/team/labels.d.ts +99 -0
  408. package/dist/pages/team/labels.js +98 -0
  409. package/dist/pages/team/new-employee.d.ts +8 -0
  410. package/dist/pages/team/new-employee.js +103 -0
  411. package/dist/pages/team/roles-tab.d.ts +6 -0
  412. package/dist/pages/team/roles-tab.js +46 -0
  413. package/dist/pages/team/team-page.d.ts +7 -0
  414. package/dist/pages/team/team-page.js +81 -0
  415. package/dist/pages/team/temp-password-dialog.d.ts +11 -0
  416. package/dist/pages/team/temp-password-dialog.js +23 -0
  417. package/dist/pages/trash/labels.d.ts +68 -0
  418. package/dist/pages/trash/labels.js +69 -0
  419. package/dist/pages/trash/trash-page.d.ts +9 -0
  420. package/dist/pages/trash/trash-page.js +259 -0
  421. package/dist/pages/vat-rules/labels.d.ts +110 -0
  422. package/dist/pages/vat-rules/labels.js +109 -0
  423. package/dist/pages/vat-rules/new-rule-dialog.d.ts +7 -0
  424. package/dist/pages/vat-rules/new-rule-dialog.js +134 -0
  425. package/dist/pages/vat-rules/rule-row.d.ts +20 -0
  426. package/dist/pages/vat-rules/rule-row.js +120 -0
  427. package/dist/pages/vat-rules/vat-rules-page.d.ts +9 -0
  428. package/dist/pages/vat-rules/vat-rules-page.js +61 -0
  429. package/dist/ui/alert-dialog.d.ts +20 -0
  430. package/dist/ui/alert-dialog.js +41 -0
  431. package/dist/ui/badge.d.ts +9 -0
  432. package/dist/ui/badge.js +20 -0
  433. package/dist/ui/breadcrumb.d.ts +19 -0
  434. package/dist/ui/breadcrumb.js +23 -0
  435. package/dist/ui/button.d.ts +11 -0
  436. package/dist/ui/button.js +33 -0
  437. package/dist/ui/calendar.d.ts +8 -0
  438. package/dist/ui/calendar.js +75 -0
  439. package/dist/ui/card.d.ts +8 -0
  440. package/dist/ui/card.js +16 -0
  441. package/dist/ui/chart.d.ts +62 -0
  442. package/dist/ui/chart.js +118 -0
  443. package/dist/ui/checkbox.d.ts +4 -0
  444. package/dist/ui/checkbox.js +11 -0
  445. package/dist/ui/collapsible.d.ts +5 -0
  446. package/dist/ui/collapsible.js +5 -0
  447. package/dist/ui/combobox.d.ts +24 -0
  448. package/dist/ui/combobox.js +55 -0
  449. package/dist/ui/command.d.ts +80 -0
  450. package/dist/ui/command.js +29 -0
  451. package/dist/ui/dialog.d.ts +19 -0
  452. package/dist/ui/dialog.js +58 -0
  453. package/dist/ui/dropdown-menu.d.ts +27 -0
  454. package/dist/ui/dropdown-menu.js +33 -0
  455. package/dist/ui/field-context.d.ts +4 -0
  456. package/dist/ui/field-context.js +6 -0
  457. package/dist/ui/field.d.ts +3 -0
  458. package/dist/ui/field.js +8 -0
  459. package/dist/ui/icon-action.d.ts +3 -0
  460. package/dist/ui/icon-action.js +8 -0
  461. package/dist/ui/index.d.ts +32 -0
  462. package/dist/ui/index.js +32 -0
  463. package/dist/ui/input.d.ts +3 -0
  464. package/dist/ui/input.js +11 -0
  465. package/dist/ui/label.d.ts +5 -0
  466. package/dist/ui/label.js +15 -0
  467. package/dist/ui/multi-combobox.d.ts +18 -0
  468. package/dist/ui/multi-combobox.js +40 -0
  469. package/dist/ui/popover.d.ts +9 -0
  470. package/dist/ui/popover.js +12 -0
  471. package/dist/ui/scroll-area.d.ts +5 -0
  472. package/dist/ui/scroll-area.js +9 -0
  473. package/dist/ui/scroll-shadow.d.ts +8 -0
  474. package/dist/ui/scroll-shadow.js +57 -0
  475. package/dist/ui/select.d.ts +13 -0
  476. package/dist/ui/select.js +31 -0
  477. package/dist/ui/separator.d.ts +4 -0
  478. package/dist/ui/separator.js +7 -0
  479. package/dist/ui/sheet.d.ts +25 -0
  480. package/dist/ui/sheet.js +40 -0
  481. package/dist/ui/sidebar.d.ts +65 -0
  482. package/dist/ui/sidebar.js +207 -0
  483. package/dist/ui/skeleton.d.ts +2 -0
  484. package/dist/ui/skeleton.js +6 -0
  485. package/dist/ui/sonner.d.ts +5 -0
  486. package/dist/ui/sonner.js +13 -0
  487. package/dist/ui/switch.d.ts +4 -0
  488. package/dist/ui/switch.js +7 -0
  489. package/dist/ui/table.d.ts +12 -0
  490. package/dist/ui/table.js +24 -0
  491. package/dist/ui/tabs.d.ts +7 -0
  492. package/dist/ui/tabs.js +12 -0
  493. package/dist/ui/textarea.d.ts +3 -0
  494. package/dist/ui/textarea.js +11 -0
  495. package/dist/ui/tooltip.d.ts +7 -0
  496. package/dist/ui/tooltip.js +11 -0
  497. package/package.json +80 -0
  498. package/themes/TOKENS.md +57 -0
  499. package/themes/default.css +60 -0
@@ -0,0 +1,127 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { DashboardPanel, MoneyCardsRow, MoneyTrendChart, PeriodPicker, PipelineStagesPanel, ProcessingSummaryPanel, RankedListPanel, StatRowsPanel, englishPeriodLabels, overviewPeriodRange, previousPeriodRange, readStoredPeriod, writeStoredPeriod, useStoredPeriod, } from "../../components/dashboard/index.js";
4
+ import { englishTourLabels, useTour, useTourPlaceholderData } from "../../components/tour/index.js";
5
+ import { createPlaceholderOverviewAdapter } from "../../lib/tour-placeholders/index.js";
6
+ import { englishOverviewLabels } from "./labels.js";
7
+ const ALL_WIDGETS = [
8
+ "moneyCards",
9
+ "trendChart",
10
+ "stages",
11
+ "topSuppliers",
12
+ "spendByCompany",
13
+ "processing",
14
+ "openItems",
15
+ "bank",
16
+ ];
17
+ const NO_MONEY_FIGURES = {
18
+ data: {},
19
+ loading: false,
20
+ };
21
+ const NO_TREND = { data: [], loading: false };
22
+ const NO_STAGES = { data: [], loading: false, error: false };
23
+ const NO_RANKED = { data: { rows: [], totalText: "" }, loading: false };
24
+ const NO_PROCESSING = { data: undefined, loading: false, error: false };
25
+ const NO_STAT_ROWS = { data: [], loading: false, error: false };
26
+ export function OverviewPage({ adapter, links, placeholderAdapter, moneyCards, widgets = ALL_WIDGETS, alertStrip, labels = englishOverviewLabels, periodLabels = englishPeriodLabels, }) {
27
+ const providedWidgets = {
28
+ moneyCards: adapter.useMoneyFigures !== undefined,
29
+ trendChart: adapter.useMoneyTrend !== undefined,
30
+ stages: adapter.useInvoiceStages !== undefined,
31
+ topSuppliers: adapter.useTopSuppliers !== undefined,
32
+ spendByCompany: adapter.useSpendByCompany !== undefined,
33
+ processing: adapter.useProcessingSummary !== undefined,
34
+ openItems: adapter.useOpenItemsSummary !== undefined,
35
+ bank: adapter.useBankSummary !== undefined,
36
+ };
37
+ const show = (widget) => widgets.includes(widget) && providedWidgets[widget];
38
+ const [moneyPeriods, setMoneyPeriod] = useMoneyCardPeriods(moneyCards);
39
+ const [trendPeriod, setTrendPeriod] = useStoredPeriod("chart");
40
+ const [stagesPeriod, setStagesPeriod] = useStoredPeriod("stages");
41
+ const [suppliersPeriod, setSuppliersPeriod] = useStoredPeriod("suppliers");
42
+ const [companiesPeriod, setCompaniesPeriod] = useStoredPeriod("companies");
43
+ const [processingPeriod, setProcessingPeriod] = useStoredPeriod("processing");
44
+ const trendRange = useMemo(() => overviewPeriodRange(trendPeriod.period, new Date(), trendPeriod), [trendPeriod]);
45
+ const stagesRange = useMemo(() => overviewPeriodRange(stagesPeriod.period, new Date(), stagesPeriod), [stagesPeriod]);
46
+ const suppliersRange = useMemo(() => overviewPeriodRange(suppliersPeriod.period, new Date(), suppliersPeriod), [suppliersPeriod]);
47
+ const companiesRange = useMemo(() => overviewPeriodRange(companiesPeriod.period, new Date(), companiesPeriod), [companiesPeriod]);
48
+ const processingRange = useMemo(() => overviewPeriodRange(processingPeriod.period, new Date(), processingPeriod), [processingPeriod]);
49
+ const moneyRanges = useMemo(() => {
50
+ const ranges = {};
51
+ for (const card of moneyCards) {
52
+ const period = moneyPeriods[card.key];
53
+ ranges[card.key] = overviewPeriodRange(period.period, new Date(), period);
54
+ }
55
+ return ranges;
56
+ }, [moneyCards, moneyPeriods]);
57
+ const realMoneyFigures = (adapter.useMoneyFigures ?? (() => NO_MONEY_FIGURES))(moneyRanges);
58
+ const realTrend = (adapter.useMoneyTrend ?? (() => NO_TREND))(trendRange);
59
+ const realStages = (adapter.useInvoiceStages ?? (() => NO_STAGES))(stagesRange);
60
+ const realSuppliers = (adapter.useTopSuppliers ?? (() => NO_RANKED))(suppliersRange);
61
+ const realCompanies = (adapter.useSpendByCompany ?? (() => NO_RANKED))(companiesRange);
62
+ const realProcessing = (adapter.useProcessingSummary ?? (() => NO_PROCESSING))(processingRange);
63
+ const realOpenItems = (adapter.useOpenItemsSummary ?? (() => NO_STAT_ROWS))();
64
+ const realBank = (adapter.useBankSummary ?? (() => NO_STAT_ROWS))();
65
+ const fallbackPlaceholderAdapter = useMemo(() => createPlaceholderOverviewAdapter(undefined, adapter.formatMoney), [adapter.formatMoney]);
66
+ const samples = placeholderAdapter ?? fallbackPlaceholderAdapter;
67
+ const sampleMoneyFigures = (samples.useMoneyFigures ?? (() => NO_MONEY_FIGURES))(moneyRanges);
68
+ const sampleTrend = (samples.useMoneyTrend ?? (() => NO_TREND))(trendRange);
69
+ const sampleStages = (samples.useInvoiceStages ?? (() => NO_STAGES))(stagesRange);
70
+ const sampleSuppliers = (samples.useTopSuppliers ?? (() => NO_RANKED))(suppliersRange);
71
+ const sampleCompanies = (samples.useSpendByCompany ?? (() => NO_RANKED))(companiesRange);
72
+ const sampleProcessing = (samples.useProcessingSummary ?? (() => NO_PROCESSING))(processingRange);
73
+ const sampleOpenItems = (samples.useOpenItemsSummary ?? (() => NO_STAT_ROWS))();
74
+ const sampleBank = (samples.useBankSummary ?? (() => NO_STAT_ROWS))();
75
+ const tour = useTour();
76
+ const tourLabels = tour?.labels ?? englishTourLabels;
77
+ const wantsSampleData = useTourPlaceholderData();
78
+ const moneyIsEmpty = Object.values(realMoneyFigures.data).every((figure) => !figure.loading && figure.value === 0);
79
+ const trendIsEmpty = !realTrend.loading && realTrend.data.length === 0;
80
+ const stagesIsEmpty = !realStages.loading && realStages.data.length === 0;
81
+ const suppliersIsEmpty = !realSuppliers.loading && realSuppliers.data.rows.length === 0;
82
+ const companiesIsEmpty = !realCompanies.loading && realCompanies.data.rows.length === 0;
83
+ const processingIsEmpty = !realProcessing.loading && realProcessing.data === undefined;
84
+ const openItemsIsEmpty = !realOpenItems.loading && realOpenItems.data.length === 0;
85
+ const bankIsEmpty = !realBank.loading && realBank.data.length === 0;
86
+ const showSample = (isEmpty) => wantsSampleData && isEmpty;
87
+ const showingSampleData = showSample(moneyIsEmpty) ||
88
+ showSample(trendIsEmpty) ||
89
+ showSample(stagesIsEmpty) ||
90
+ showSample(suppliersIsEmpty) ||
91
+ showSample(companiesIsEmpty) ||
92
+ showSample(processingIsEmpty) ||
93
+ showSample(openItemsIsEmpty) ||
94
+ showSample(bankIsEmpty);
95
+ const moneyFigures = showSample(moneyIsEmpty) ? sampleMoneyFigures : realMoneyFigures;
96
+ const trend = showSample(trendIsEmpty) ? sampleTrend : realTrend;
97
+ const stages = showSample(stagesIsEmpty) ? sampleStages : realStages;
98
+ const suppliers = showSample(suppliersIsEmpty) ? sampleSuppliers : realSuppliers;
99
+ const companies = showSample(companiesIsEmpty) ? sampleCompanies : realCompanies;
100
+ const processing = showSample(processingIsEmpty) ? sampleProcessing : realProcessing;
101
+ const openItems = showSample(openItemsIsEmpty) ? sampleOpenItems : realOpenItems;
102
+ const bank = showSample(bankIsEmpty) ? sampleBank : realBank;
103
+ return (_jsxs("div", { children: [alertStrip, showingSampleData && _jsx(SampleDataChip, { label: tourLabels.sampleData }), (show("moneyCards") || show("trendChart")) && (_jsxs("div", { className: "grid gap-3 lg:grid-cols-3", children: [show("moneyCards") && (_jsx("section", { "data-tour": "overview-money-cards", className: "min-w-0 rounded-2xl bg-brand-wash p-4 lg:col-span-2", children: _jsx(MoneyCardsRow, { cards: moneyCards, figures: moneyFigures.data, periods: moneyPeriods, onPeriodChange: setMoneyPeriod, periodLabels: periodLabels, formatMoney: adapter.formatMoney, formatDay: adapter.formatDay }) })), show("trendChart") && (_jsx(DashboardPanel, { dataTour: "overview-trend-chart", title: labels.trendChart.title, headerRight: _jsx(PeriodPicker, { value: trendPeriod, onChange: setTrendPeriod, labels: periodLabels, formatDay: adapter.formatDay }), className: "overflow-hidden", children: _jsx("div", { className: "mt-2", children: _jsx(MoneyTrendChart, { data: trend.data, loading: trend.loading, incomingLabel: labels.trendChart.incoming, outgoingLabel: labels.trendChart.outgoing, formatMoney: adapter.formatMoney, formatMoneyCompact: adapter.formatMoneyCompact }) }) }))] })), (show("stages") || show("topSuppliers")) && (_jsxs("div", { className: "mt-3 grid gap-3 lg:grid-cols-3", children: [show("stages") && (_jsx("div", { className: "min-w-0 lg:col-span-2", children: _jsx(PipelineStagesPanel, { dataTour: "overview-stages", title: labels.stagesTitle, stages: stages.data, loading: stages.loading, error: stages.error, period: stagesPeriod, onPeriodChange: setStagesPeriod, periodLabels: periodLabels, formatDay: adapter.formatDay, className: "h-full" }) })), show("topSuppliers") && (_jsx(RankedListPanel, { dataTour: "overview-top-suppliers", title: labels.topSuppliersTitle(suppliers.data.rows.length), totalLabel: labels.rankTotal(suppliers.data.totalText), rows: suppliers.data.rows, loading: suppliers.loading, period: suppliersPeriod, onPeriodChange: setSuppliersPeriod, periodLabels: periodLabels, formatDay: adapter.formatDay }))] })), (show("processing") || show("spendByCompany") || show("openItems") || show("bank")) && (_jsxs("div", { className: "mt-3 grid gap-3 sm:grid-cols-2 xl:grid-cols-4", children: [show("processing") && (_jsx(ProcessingSummaryPanel, { dataTour: "overview-processing", title: labels.processing.title, seeAllLabel: labels.processing.seeAll, seeAllTo: links.processingLog, summary: processing.data, loading: processing.loading, error: processing.error, labels: {
104
+ processed: labels.processing.processed,
105
+ recognized: labels.processing.recognized,
106
+ needsReview: labels.processing.needsReview,
107
+ errors: labels.processing.errors,
108
+ channelsPrefix: labels.processing.channelsPrefix,
109
+ }, period: processingPeriod, onPeriodChange: setProcessingPeriod, periodLabels: periodLabels, formatDay: adapter.formatDay })), show("spendByCompany") && (_jsx(RankedListPanel, { dataTour: "overview-spend-by-company", title: labels.spendByCompanyTitle(companies.data.rows.length), totalLabel: labels.rankTotal(companies.data.totalText), rows: companies.data.rows, loading: companies.loading, period: companiesPeriod, onPeriodChange: setCompaniesPeriod, periodLabels: periodLabels, formatDay: adapter.formatDay })), show("openItems") && (_jsx(StatRowsPanel, { dataTour: "overview-open-items", title: labels.openItems.title, rows: openItems.data, loading: openItems.loading, emptyText: labels.openItems.empty, footerLink: { to: links.openItems, label: labels.openItems.seeAll } })), show("bank") && (_jsx(StatRowsPanel, { dataTour: "overview-bank", title: labels.bank.title, rows: bank.data, loading: bank.loading, footerLink: { to: links.bank, label: labels.bank.seeAll } }))] }))] }));
110
+ }
111
+ function useMoneyCardPeriods(cards) {
112
+ const [periods, setPeriods] = useState(() => {
113
+ const initial = {};
114
+ for (const card of cards)
115
+ initial[card.key] = readStoredPeriod(`card.${card.key}`);
116
+ return initial;
117
+ });
118
+ const setPeriod = (cardKey, value) => {
119
+ setPeriods((current) => ({ ...current, [cardKey]: value }));
120
+ writeStoredPeriod(`card.${cardKey}`, value);
121
+ };
122
+ return [periods, setPeriod];
123
+ }
124
+ export { previousPeriodRange };
125
+ function SampleDataChip({ label }) {
126
+ return (_jsx("span", { className: "mb-3 inline-flex rounded-full border border-border bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground", children: label }));
127
+ }
@@ -0,0 +1,54 @@
1
+ export interface ProcessingLogLabels {
2
+ title: string;
3
+ processingTab: string;
4
+ changesTab: string;
5
+ processingSubtitle: string;
6
+ changesSubtitle: string;
7
+ searchPlaceholder: string;
8
+ changesSearchPlaceholder: string;
9
+ statusFilterPlaceholder: string;
10
+ allStatuses: string;
11
+ periodPlaceholder: string;
12
+ wholePeriod: string;
13
+ today: string;
14
+ lastSevenDays: string;
15
+ lastThirtyDays: string;
16
+ exportCsv: string;
17
+ searchIgnoredNote: string;
18
+ matchCount: (count: number) => string;
19
+ changesCount: (count: number) => string;
20
+ refreshing: string;
21
+ empty: string;
22
+ changesEmpty: string;
23
+ clearFilter: string;
24
+ filterByStatus: string;
25
+ statusLabel: (status: string) => string;
26
+ openInvoice: string;
27
+ showDetails: string;
28
+ verdictNeedsReview: string;
29
+ verdictAccepted: string;
30
+ columns: {
31
+ time: string;
32
+ subject: string;
33
+ sender: string;
34
+ status: string;
35
+ reason: string;
36
+ actions: string;
37
+ actor: string;
38
+ changeType: string;
39
+ tableName: string;
40
+ changeText: string;
41
+ };
42
+ detail: {
43
+ title: string;
44
+ subject: string;
45
+ sender: string;
46
+ status: string;
47
+ reason: string;
48
+ noReason: string;
49
+ invoice: string;
50
+ openInvoice: string;
51
+ noInvoice: string;
52
+ };
53
+ }
54
+ export declare const englishProcessingLogLabels: ProcessingLogLabels;
@@ -0,0 +1,53 @@
1
+ export const englishProcessingLogLabels = {
2
+ title: "Activity log",
3
+ processingTab: "Document processing",
4
+ changesTab: "Changes",
5
+ processingSubtitle: "What the document pipeline did with each incoming mail and file.",
6
+ changesSubtitle: "Who changed what inside the app, with the reason where one was given.",
7
+ searchPlaceholder: "Search subject, sender or reason …",
8
+ changesSearchPlaceholder: "Search actor or text …",
9
+ statusFilterPlaceholder: "Status",
10
+ allStatuses: "All statuses",
11
+ periodPlaceholder: "Period",
12
+ wholePeriod: "Whole period",
13
+ today: "Today",
14
+ lastSevenDays: "Last 7 days",
15
+ lastThirtyDays: "Last 30 days",
16
+ exportCsv: "Export CSV",
17
+ searchIgnoredNote: "This search term only contains characters the search cannot use, so it was ignored.",
18
+ matchCount: (count) => `${count} ${count === 1 ? "entry" : "entries"}`,
19
+ changesCount: (count) => `${count} ${count === 1 ? "change" : "changes"}`,
20
+ refreshing: "Updating …",
21
+ empty: "No log entries found.",
22
+ changesEmpty: "No changes found.",
23
+ clearFilter: "Clear filter",
24
+ filterByStatus: "Filter by this status",
25
+ statusLabel: (status) => status,
26
+ openInvoice: "Open invoice",
27
+ showDetails: "Show details",
28
+ verdictNeedsReview: "Needs review",
29
+ verdictAccepted: "Accepted automatically",
30
+ columns: {
31
+ time: "Time",
32
+ subject: "Subject",
33
+ sender: "Sender",
34
+ status: "Status",
35
+ reason: "Reason",
36
+ actions: "Actions",
37
+ actor: "Actor",
38
+ changeType: "Type",
39
+ tableName: "Table",
40
+ changeText: "Text",
41
+ },
42
+ detail: {
43
+ title: "Log entry",
44
+ subject: "Subject",
45
+ sender: "Sender",
46
+ status: "Status",
47
+ reason: "Reason",
48
+ noReason: "No reason was recorded.",
49
+ invoice: "Invoice",
50
+ openInvoice: "Open invoice",
51
+ noInvoice: "No invoice was created from this entry.",
52
+ },
53
+ };
@@ -0,0 +1,21 @@
1
+ /** A mail sender, split into the two things an RFC-5322 From header actually holds. */
2
+ export type ParsedSender = {
3
+ /** Display name, unquoted. Null when the header carried only an address. */
4
+ name: string | null;
5
+ /** Mail address, without angle brackets. Null when the value is not an address at all. */
6
+ address: string | null;
7
+ /** The header exactly as stored, for the title attribute and the detail view. */
8
+ raw: string;
9
+ };
10
+ export declare function parseSender(raw: string | null | undefined): ParsedSender;
11
+ /** How a parsed sender should read on one line, when only one line is available. */
12
+ export declare function senderOneLine(s: ParsedSender): string;
13
+ export type ReasonVerdict = "needs_review" | "accepted" | null;
14
+ export type ParsedReason = {
15
+ verdict: ReasonVerdict;
16
+ /** The individual findings, in source order, verbatim and without their trailing full stop. */
17
+ causes: string[];
18
+ /** The reason exactly as stored. */
19
+ raw: string;
20
+ };
21
+ export declare function parseReason(raw: string | null | undefined): ParsedReason;
@@ -0,0 +1,52 @@
1
+ export function parseSender(raw) {
2
+ const value = (raw ?? "").trim();
3
+ if (!value)
4
+ return { name: null, address: null, raw: "" };
5
+ // `Name <addr>` or `"Name" <addr>`. The name half is greedy-free so a `>` inside a display name
6
+ // cannot swallow the address.
7
+ const withAngle = value.match(/^(.*?)\s*<([^<>]+)>\s*$/);
8
+ if (withAngle) {
9
+ const name = unquote(withAngle[1]);
10
+ return { name: name || null, address: withAngle[2].trim() || null, raw: value };
11
+ }
12
+ // A bare address.
13
+ if (/^[^\s"<>]+@[^\s"<>]+$/.test(value))
14
+ return { name: null, address: value, raw: value };
15
+ // Anything else — `scan_folder`, a system label — is a name with no address.
16
+ return { name: unquote(value) || null, address: null, raw: value };
17
+ }
18
+ function unquote(s) {
19
+ const t = s.trim();
20
+ return t.startsWith('"') && t.endsWith('"') && t.length > 1 ? t.slice(1, -1).trim() : t;
21
+ }
22
+ /** How a parsed sender should read on one line, when only one line is available. */
23
+ export function senderOneLine(s) {
24
+ if (s.name && s.address)
25
+ return `${s.name} · ${s.address}`;
26
+ return s.address ?? s.name ?? "—";
27
+ }
28
+ export function parseReason(raw) {
29
+ const value = (raw ?? "").trim().replace(/\s+/g, " ");
30
+ if (!value)
31
+ return { verdict: null, causes: [], raw: "" };
32
+ const promoted = value.replace(/\.\s+(?=(?:Needs review|Accepted automatically):)/g, "; ");
33
+ let verdict = null;
34
+ const causes = [];
35
+ for (const part of promoted.split(";")) {
36
+ let segment = part.trim();
37
+ if (!segment)
38
+ continue;
39
+ const lead = segment.match(/^(Needs review|Accepted automatically):\s*/);
40
+ if (lead) {
41
+ // Last one wins only if nothing has claimed it yet — a mail is either accepted or under
42
+ // review, and the first verdict in the string is the one the pipeline reached first.
43
+ verdict ??= lead[1] === "Needs review" ? "needs_review" : "accepted";
44
+ segment = segment.slice(lead[0].length).trim();
45
+ if (!segment)
46
+ continue;
47
+ }
48
+ // Trailing full stop on the last finding only — dropping it everywhere keeps the list even.
49
+ causes.push(segment.replace(/\.$/, "").trim());
50
+ }
51
+ return { verdict, causes, raw: value };
52
+ }
@@ -0,0 +1,9 @@
1
+ import { type Formatters } from "../../lib/formatters";
2
+ import type { ProcessingLogAdapter } from "../../adapters/processing-log";
3
+ import { type ProcessingLogLabels } from "./labels";
4
+ export interface ProcessingLogPageProps {
5
+ adapter: ProcessingLogAdapter;
6
+ labels?: ProcessingLogLabels;
7
+ formatters?: Formatters;
8
+ }
9
+ export declare function ProcessingLogPage({ adapter, labels, formatters, }: ProcessingLogPageProps): import("react").JSX.Element;
@@ -0,0 +1,206 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { ChevronRight, Download, Info, Search } from "lucide-react";
4
+ import { Badge } from "../../ui/badge.js";
5
+ import { Button } from "../../ui/button.js";
6
+ import { Combobox } from "../../ui/combobox.js";
7
+ import { Input } from "../../ui/input.js";
8
+ import { Skeleton } from "../../ui/skeleton.js";
9
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs.js";
10
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "../../ui/dialog.js";
11
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../ui/table.js";
12
+ import { ErrorState, TableSkeleton } from "../../components/feedback/query-states.js";
13
+ import { TablePagination } from "../../components/feedback/table-pagination.js";
14
+ import { cn } from "../../lib/class-names.js";
15
+ import { englishFormatters } from "../../lib/formatters.js";
16
+ import { downloadTextFile, toCsv } from "../../lib/download.js";
17
+ import { parseReason, parseSender } from "./mail-parsing.js";
18
+ import { englishProcessingLogLabels } from "./labels.js";
19
+ const ALL = "__all";
20
+ const PERIODS = { all: "all", today: "today", sevenDays: "sevenDays", thirtyDays: "thirtyDays" };
21
+ const TONE_STYLE = {
22
+ brand: "bg-brand-tint text-brand-dark",
23
+ success: "bg-success-soft text-success",
24
+ warning: "bg-warning-soft text-warning",
25
+ danger: "bg-danger-soft text-danger",
26
+ neutral: "bg-muted text-muted-foreground",
27
+ };
28
+ function isoDaysAgo(daysBack) {
29
+ const date = new Date();
30
+ date.setDate(date.getDate() - daysBack);
31
+ const month = String(date.getMonth() + 1).padStart(2, "0");
32
+ const day = String(date.getDate()).padStart(2, "0");
33
+ return `${date.getFullYear()}-${month}-${day}`;
34
+ }
35
+ function periodBounds(period) {
36
+ if (period === PERIODS.today)
37
+ return { fromDate: isoDaysAgo(0), toDate: isoDaysAgo(0) };
38
+ if (period === PERIODS.sevenDays)
39
+ return { fromDate: isoDaysAgo(6), toDate: isoDaysAgo(0) };
40
+ if (period === PERIODS.thirtyDays)
41
+ return { fromDate: isoDaysAgo(29), toDate: isoDaysAgo(0) };
42
+ return { fromDate: null, toDate: null };
43
+ }
44
+ // Waits 300ms after the last keystroke before the term reaches the server.
45
+ function useDebouncedTerm(input) {
46
+ const [term, setTerm] = useState("");
47
+ useEffect(() => {
48
+ const timer = window.setTimeout(() => setTerm(input.trim()), 300);
49
+ return () => window.clearTimeout(timer);
50
+ }, [input]);
51
+ return term;
52
+ }
53
+ export function ProcessingLogPage({ adapter, labels = englishProcessingLogLabels, formatters = englishFormatters, }) {
54
+ return (_jsxs("div", { children: [_jsx("div", { className: "flex flex-wrap items-end justify-between gap-4", children: _jsx("h1", { className: "mt-2 text-2xl font-semibold tracking-tight text-foreground", children: labels.title }) }), _jsxs(Tabs, { defaultValue: "processing", className: "mt-6", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "processing", children: labels.processingTab }), _jsx(TabsTrigger, { value: "changes", children: labels.changesTab })] }), _jsx(TabsContent, { value: "processing", className: "mt-4", children: _jsx(ProcessingTab, { adapter: adapter, labels: labels, formatters: formatters }) }), _jsx(TabsContent, { value: "changes", className: "mt-4", children: _jsx(ChangesTab, { adapter: adapter, labels: labels, formatters: formatters }) })] })] }));
55
+ }
56
+ function ProcessingTab({ adapter, labels, formatters, }) {
57
+ const [searchInput, setSearchInput] = useState("");
58
+ const searchTerm = useDebouncedTerm(searchInput);
59
+ const [statusFilter, setStatusFilter] = useState(ALL);
60
+ const [period, setPeriod] = useState(PERIODS.all);
61
+ const [page, setPage] = useState(1);
62
+ const [pageSize, setPageSize] = useState(25);
63
+ const [openEntry, setOpenEntry] = useState(null);
64
+ useEffect(() => {
65
+ setPage(1);
66
+ }, [searchTerm, statusFilter, period, pageSize]);
67
+ const bounds = useMemo(() => periodBounds(period), [period]);
68
+ const logQuery = adapter.useLogPage({
69
+ search: searchTerm,
70
+ status: statusFilter === ALL ? undefined : statusFilter,
71
+ fromDate: bounds.fromDate,
72
+ toDate: bounds.toDate,
73
+ page,
74
+ pageSize,
75
+ });
76
+ const countsQuery = adapter.useStatusCounts({
77
+ search: searchTerm,
78
+ fromDate: bounds.fromDate,
79
+ toDate: bounds.toDate,
80
+ });
81
+ const rows = logQuery.data?.rows ?? [];
82
+ const total = logQuery.data?.total ?? 0;
83
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
84
+ const firstShown = total === 0 ? 0 : (page - 1) * pageSize + 1;
85
+ const lastShown = Math.min(page * pageSize, total);
86
+ const searchIgnored = adapter.searchTermWasIgnored(searchTerm);
87
+ const counts = useMemo(() => countsQuery.data ?? {}, [countsQuery.data]);
88
+ const statusValues = useMemo(() => Object.keys(counts).sort((a, b) => (counts[b] ?? 0) - (counts[a] ?? 0)), [counts]);
89
+ function exportCsv() {
90
+ const header = [
91
+ labels.columns.time,
92
+ labels.columns.subject,
93
+ labels.columns.sender,
94
+ labels.columns.status,
95
+ labels.columns.reason,
96
+ ];
97
+ const lines = [
98
+ header,
99
+ ...rows.map((entry) => [
100
+ entry.processedAt ?? "",
101
+ entry.subject ?? "",
102
+ entry.sender ?? "",
103
+ entry.status ?? "",
104
+ entry.reason ?? "",
105
+ ]),
106
+ ];
107
+ downloadTextFile(`processing-log-${isoDaysAgo(0)}.csv`, toCsv(lines));
108
+ }
109
+ function statusBadgeStyle(status) {
110
+ const tone = status ? adapter.statusTone[status] : undefined;
111
+ return TONE_STYLE[tone ?? "neutral"];
112
+ }
113
+ return (_jsxs("div", { children: [_jsx("p", { className: "text-sm text-muted-foreground", children: labels.processingSubtitle }), _jsx("div", { className: "mt-4 flex min-h-[34px] flex-wrap items-center gap-2", children: countsQuery.isLoading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-[26px] w-28 rounded-full" }), _jsx(Skeleton, { className: "h-[26px] w-24 rounded-full" }), _jsx(Skeleton, { className: "h-[26px] w-20 rounded-full" })] })) : (_jsxs(_Fragment, { children: [statusValues.map((status) => {
114
+ const active = statusFilter === status;
115
+ return (_jsxs("button", { type: "button", "aria-pressed": active, title: active ? labels.clearFilter : labels.filterByStatus, onClick: () => setStatusFilter(active ? ALL : status), className: cn("inline-flex cursor-pointer items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium transition-all", statusBadgeStyle(status), active
116
+ ? "ring-2 ring-brand ring-offset-1 ring-offset-background"
117
+ : statusFilter !== ALL && "opacity-40 hover:opacity-100"), children: [labels.statusLabel(status), " \u00B7 ", counts[status]] }, status));
118
+ }), statusFilter !== ALL && (_jsx("button", { type: "button", onClick: () => setStatusFilter(ALL), className: "inline-flex cursor-pointer items-center rounded-full px-3 py-1 text-xs font-medium text-muted-foreground underline hover:text-foreground", children: labels.clearFilter }))] })) }), _jsxs("div", { className: "mt-6 flex flex-wrap items-center gap-3", children: [_jsxs("div", { className: "relative w-full flex-1 sm:w-auto sm:min-w-[240px]", children: [_jsx(Search, { className: "absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: searchInput, onChange: (event) => setSearchInput(event.target.value), placeholder: labels.searchPlaceholder, className: "pl-9" })] }), _jsx(Combobox, { value: statusFilter, onValueChange: setStatusFilter, className: "w-full sm:w-[180px]", placeholder: labels.statusFilterPlaceholder, options: [
119
+ { value: ALL, label: labels.allStatuses },
120
+ ...statusValues.map((status) => ({ value: status, label: labels.statusLabel(status) })),
121
+ ] }), _jsx(Combobox, { value: period, onValueChange: (value) => setPeriod(value), className: "w-full sm:w-[170px]", placeholder: labels.periodPlaceholder, options: [
122
+ { value: PERIODS.all, label: labels.wholePeriod },
123
+ { value: PERIODS.today, label: labels.today },
124
+ { value: PERIODS.sevenDays, label: labels.lastSevenDays },
125
+ { value: PERIODS.thirtyDays, label: labels.lastThirtyDays },
126
+ ] }), _jsx(Button, { type: "button", variant: period === PERIODS.today ? "default" : "outline", size: "sm", onClick: () => setPeriod(period === PERIODS.today ? PERIODS.all : PERIODS.today), children: labels.today }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "gap-1.5", disabled: rows.length === 0, onClick: exportCsv, children: [_jsx(Download, { className: "size-4" }), labels.exportCsv] })] }), searchIgnored && (_jsxs("p", { className: "mt-3 flex items-start gap-2 rounded-lg border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-foreground", children: [_jsx(Info, { className: "mt-0.5 size-3.5 shrink-0" }), labels.searchIgnoredNote] })), logQuery.isError ? (_jsx("div", { className: "mt-4", children: _jsx(ErrorState, { error: logQuery.error, onRetry: logQuery.refetch }) })) : logQuery.isLoading ? (_jsx("div", { className: "mt-4", children: _jsx(TableSkeleton, { rows: 10, columns: 4 }) })) : (_jsxs(_Fragment, { children: [_jsxs("p", { className: "mt-4 text-sm text-muted-foreground", children: [labels.matchCount(total), logQuery.isRefreshing && ` · ${labels.refreshing}`] }), _jsx("div", { className: "mt-3 hidden overflow-hidden rounded-xl border border-border bg-card sm:block", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { className: "bg-muted/40", children: [_jsx(TableHead, { children: labels.columns.time }), _jsx(TableHead, { children: labels.columns.subject }), _jsx(TableHead, { children: labels.columns.sender }), _jsx(TableHead, { children: labels.columns.status }), _jsx(TableHead, { children: labels.columns.reason }), _jsx(TableHead, { className: "w-[92px] text-right", children: labels.columns.actions })] }) }), _jsxs(TableBody, { children: [rows.map((entry) => (_jsx(LogTableRow, { entry: entry, labels: labels, formatters: formatters, badgeStyle: statusBadgeStyle(entry.status), onShowDetails: () => setOpenEntry(entry), onOpenInvoice: () => entry.invoiceId && adapter.openInvoice(entry.invoiceId) }, entry.id))), rows.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "py-12 text-center text-muted-foreground", children: labels.empty }) }))] })] }) }), _jsxs("div", { className: "mt-3 space-y-3 sm:hidden", children: [rows.map((entry) => (_jsx(LogCard, { entry: entry, labels: labels, formatters: formatters, badgeStyle: statusBadgeStyle(entry.status), onShowDetails: () => setOpenEntry(entry), onOpenInvoice: () => entry.invoiceId && adapter.openInvoice(entry.invoiceId) }, entry.id))), rows.length === 0 && (_jsx("p", { className: "rounded-xl border border-border bg-card py-12 text-center text-sm text-muted-foreground", children: labels.empty }))] }), total > 0 && (_jsx(TablePagination, { page: page, totalPages: totalPages, pageSize: pageSize, total: total, from: firstShown, to: lastShown, onPage: setPage, onPageSize: setPageSize }))] })), _jsx(EntryDialog, { entry: openEntry, labels: labels, formatters: formatters, onOpenInvoice: (invoiceId) => {
127
+ setOpenEntry(null);
128
+ adapter.openInvoice(invoiceId);
129
+ }, onClose: () => setOpenEntry(null) })] }));
130
+ }
131
+ // Every row is a real button: with an invoice it opens the invoice, without one it opens details.
132
+ function LogTableRow({ entry, labels, formatters, badgeStyle, onShowDetails, onOpenInvoice, }) {
133
+ const hasInvoice = !!entry.invoiceId;
134
+ const sender = parseSender(entry.sender);
135
+ const reason = parseReason(entry.reason);
136
+ const activate = hasInvoice ? onOpenInvoice : onShowDetails;
137
+ const actionName = hasInvoice ? labels.openInvoice : labels.showDetails;
138
+ return (_jsxs(TableRow, { className: "cursor-pointer", role: "button", tabIndex: 0, "aria-label": `${actionName}: ${entry.subject ?? ""}`, onClick: activate, onKeyDown: (event) => {
139
+ if (event.key === "Enter" || event.key === " ") {
140
+ event.preventDefault();
141
+ activate();
142
+ }
143
+ }, children: [_jsx(TableCell, { className: "text-sm whitespace-nowrap text-muted-foreground tabular-nums", children: formatters.formatDateTime(entry.processedAt) }), _jsx(TableCell, { className: "max-w-[260px] truncate text-sm text-foreground", title: entry.subject ?? "", children: entry.subject ?? "—" }), _jsx(TableCell, { className: "max-w-[220px] text-sm text-muted-foreground", title: sender.raw, children: _jsx(SenderCell, { sender: sender }) }), _jsx(TableCell, { children: _jsx(Badge, { className: cn("border-transparent font-medium", badgeStyle), children: entry.status ? labels.statusLabel(entry.status) : "—" }) }), _jsx(TableCell, { className: "max-w-[300px] text-xs text-muted-foreground", title: reason.raw, children: _jsx(ReasonCell, { reason: reason, labels: labels }) }), _jsx(TableCell, { className: "text-right", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": labels.showDetails, title: labels.showDetails, onClick: (event) => {
144
+ event.stopPropagation();
145
+ onShowDetails();
146
+ }, children: _jsx(Info, { className: "size-4" }) }), hasInvoice && _jsx(ChevronRight, { className: "size-4 shrink-0 text-muted-foreground" })] }) })] }));
147
+ }
148
+ function LogCard({ entry, labels, formatters, badgeStyle, onShowDetails, onOpenInvoice, }) {
149
+ const hasInvoice = !!entry.invoiceId;
150
+ const sender = parseSender(entry.sender);
151
+ const reason = parseReason(entry.reason);
152
+ const activate = hasInvoice ? onOpenInvoice : onShowDetails;
153
+ const actionName = hasInvoice ? labels.openInvoice : labels.showDetails;
154
+ return (_jsxs("div", { className: "cursor-pointer rounded-xl border border-border bg-card p-4", role: "button", tabIndex: 0, "aria-label": `${actionName}: ${entry.subject ?? ""}`, onClick: activate, onKeyDown: (event) => {
155
+ if (event.key === "Enter" || event.key === " ") {
156
+ event.preventDefault();
157
+ activate();
158
+ }
159
+ }, children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsx("p", { className: "min-w-0 flex-1 text-sm font-medium text-foreground", title: entry.subject ?? "", children: entry.subject ?? "—" }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [_jsx(Badge, { className: cn("border-transparent font-medium", badgeStyle), children: entry.status ? labels.statusLabel(entry.status) : "—" }), hasInvoice && _jsx(ChevronRight, { className: "size-4 text-muted-foreground" })] })] }), _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: _jsx(SenderCell, { sender: sender }) }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground tabular-nums", children: formatters.formatDateTime(entry.processedAt) }), reason.causes.length > 0 && (_jsx("div", { className: "mt-2 text-xs text-muted-foreground", children: _jsx(ReasonCell, { reason: reason, labels: labels }) })), hasInvoice && (_jsx(Button, { type: "button", variant: "outline", size: "sm", className: "mt-3 w-full", onClick: (event) => {
160
+ event.stopPropagation();
161
+ onShowDetails();
162
+ }, children: labels.showDetails }))] }));
163
+ }
164
+ function SenderCell({ sender }) {
165
+ if (!sender.raw)
166
+ return _jsx(_Fragment, { children: "\u2014" });
167
+ if (sender.name && sender.address) {
168
+ return (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-foreground", children: sender.name }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: sender.address })] }));
169
+ }
170
+ return _jsx("div", { className: "truncate", children: sender.address ?? sender.name });
171
+ }
172
+ function ReasonCell({ reason, labels }) {
173
+ if (!reason.raw)
174
+ return _jsx(_Fragment, { children: "\u2014" });
175
+ return (_jsxs("div", { className: "min-w-0 space-y-1", children: [reason.verdict && (_jsx("span", { className: cn("inline-flex rounded-full px-2 py-0.5 text-[11px] font-medium", reason.verdict === "needs_review"
176
+ ? "bg-warning-soft text-warning"
177
+ : "bg-brand-tint text-brand-dark"), children: reason.verdict === "needs_review" ? labels.verdictNeedsReview : labels.verdictAccepted })), reason.causes.length > 0 && (_jsx("ul", { className: "space-y-0.5", children: reason.causes.map((cause, index) => (_jsxs("li", { className: "truncate", title: cause, children: ["\u00B7 ", cause] }, index))) }))] }));
178
+ }
179
+ function EntryDialog({ entry, labels, formatters, onOpenInvoice, onClose, }) {
180
+ if (!entry)
181
+ return null;
182
+ const sender = parseSender(entry.sender);
183
+ const reason = parseReason(entry.reason);
184
+ return (_jsx(Dialog, { open: true, onOpenChange: (open) => !open && onClose(), children: _jsxs(DialogContent, { className: "max-w-2xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: labels.detail.title }), _jsx(DialogDescription, { children: formatters.formatDateTime(entry.processedAt) })] }), _jsxs("dl", { className: "space-y-4 text-sm", children: [_jsx(DetailField, { label: labels.detail.subject, children: entry.subject ?? "—" }), _jsxs(DetailField, { label: labels.detail.sender, children: [sender.name && _jsx("div", { children: sender.name }), sender.address && _jsx("div", { className: "text-muted-foreground", children: sender.address }), !sender.name && !sender.address && "—", sender.name && sender.address && (_jsx("div", { className: "mt-1 text-xs break-all text-muted-foreground/70", children: sender.raw }))] }), _jsx(DetailField, { label: labels.detail.status, children: entry.status ? labels.statusLabel(entry.status) : "—" }), _jsx(DetailField, { label: labels.detail.reason, children: reason.causes.length === 0 && !reason.verdict ? (labels.detail.noReason) : (_jsxs("div", { className: "space-y-2", children: [reason.verdict && (_jsx("div", { className: "font-medium", children: reason.verdict === "needs_review"
185
+ ? labels.verdictNeedsReview
186
+ : labels.verdictAccepted })), _jsx("ul", { className: "list-disc space-y-1 pl-5", children: reason.causes.map((cause, index) => (_jsx("li", { className: "break-words", children: cause }, index))) })] })) }), _jsx(DetailField, { label: labels.detail.invoice, children: entry.invoiceId ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onOpenInvoice(entry.invoiceId), children: labels.detail.openInvoice })) : (_jsx("span", { className: "text-muted-foreground", children: labels.detail.noInvoice })) })] })] }) }));
187
+ }
188
+ function DetailField({ label, children }) {
189
+ return (_jsxs("div", { children: [_jsx("dt", { className: "text-xs tracking-wide text-muted-foreground uppercase", children: label }), _jsx("dd", { className: "mt-1 break-words text-foreground", children: children })] }));
190
+ }
191
+ function ChangesTab({ adapter, labels, formatters, }) {
192
+ const [searchInput, setSearchInput] = useState("");
193
+ const searchTerm = useDebouncedTerm(searchInput);
194
+ const [page, setPage] = useState(1);
195
+ const [pageSize, setPageSize] = useState(25);
196
+ useEffect(() => {
197
+ setPage(1);
198
+ }, [searchTerm, pageSize]);
199
+ const query = adapter.useChangeHistoryPage({ search: searchTerm, page, pageSize });
200
+ const rows = query.data?.rows ?? [];
201
+ const total = query.data?.total ?? 0;
202
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
203
+ const firstShown = total === 0 ? 0 : (page - 1) * pageSize + 1;
204
+ const lastShown = Math.min(page * pageSize, total);
205
+ return (_jsxs("div", { children: [_jsx("p", { className: "text-sm text-muted-foreground", children: labels.changesSubtitle }), _jsx("div", { className: "mt-6 flex flex-wrap items-center gap-3", children: _jsxs("div", { className: "relative w-full flex-1 sm:w-auto sm:min-w-[240px]", children: [_jsx(Search, { className: "absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: searchInput, onChange: (event) => setSearchInput(event.target.value), placeholder: labels.changesSearchPlaceholder, className: "pl-9" })] }) }), adapter.searchTermWasIgnored(searchTerm) && (_jsxs("p", { className: "mt-3 flex items-start gap-2 rounded-lg border border-warning/30 bg-warning-soft px-3 py-2 text-xs text-foreground", children: [_jsx(Info, { className: "mt-0.5 size-3.5 shrink-0" }), labels.searchIgnoredNote] })), query.isError ? (_jsx("div", { className: "mt-4", children: _jsx(ErrorState, { error: query.error, onRetry: query.refetch }) })) : query.isLoading ? (_jsx("div", { className: "mt-4", children: _jsx(TableSkeleton, { rows: 10, columns: 5 }) })) : (_jsxs(_Fragment, { children: [_jsxs("p", { className: "mt-4 text-sm text-muted-foreground", children: [labels.changesCount(total), query.isRefreshing && ` · ${labels.refreshing}`] }), _jsx("div", { className: "mt-3 hidden overflow-hidden rounded-xl border border-border bg-card sm:block", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { className: "bg-muted/40", children: [_jsx(TableHead, { children: labels.columns.time }), _jsx(TableHead, { children: labels.columns.actor }), _jsx(TableHead, { children: labels.columns.changeType }), _jsx(TableHead, { children: labels.columns.tableName }), _jsx(TableHead, { children: labels.columns.changeText })] }) }), _jsxs(TableBody, { children: [rows.map((change) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-sm whitespace-nowrap text-muted-foreground tabular-nums", children: formatters.formatDateTime(change.at) }), _jsx(TableCell, { className: "max-w-[200px] truncate text-sm", title: change.actor ?? "", children: change.actor ?? "—" }), _jsx(TableCell, { className: "text-sm", children: _jsx(Badge, { className: "border-transparent bg-muted font-medium text-muted-foreground", children: change.type ?? "—" }) }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: change.tableName ?? "—" }), _jsx(TableCell, { className: "max-w-[360px] truncate text-sm text-muted-foreground", title: change.text ?? "", children: change.text ?? "—" })] }, change.id))), rows.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "py-12 text-center text-muted-foreground", children: labels.changesEmpty }) }))] })] }) }), _jsxs("div", { className: "mt-3 space-y-3 sm:hidden", children: [rows.map((change) => (_jsxs("div", { className: "rounded-xl border border-border bg-card p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsx("p", { className: "min-w-0 flex-1 text-sm font-medium text-foreground", children: change.text ?? "—" }), _jsx(Badge, { className: "shrink-0 border-transparent bg-muted font-medium text-muted-foreground", children: change.type ?? "—" })] }), _jsx("p", { className: "mt-1 truncate text-xs text-muted-foreground", children: change.actor ?? "—" }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: change.tableName ?? "—" }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground tabular-nums", children: formatters.formatDateTime(change.at) })] }, change.id))), rows.length === 0 && (_jsx("p", { className: "rounded-xl border border-border bg-card py-12 text-center text-sm text-muted-foreground", children: labels.changesEmpty }))] }), total > 0 && (_jsx(TablePagination, { page: page, totalPages: totalPages, pageSize: pageSize, total: total, from: firstShown, to: lastShown, onPage: setPage, onPageSize: setPageSize }))] }))] }));
206
+ }
@@ -0,0 +1,20 @@
1
+ import type { PropertyInvoiceVatLine } from "../../adapters/properties";
2
+ import type { StatusTone } from "../../adapters/processing-log";
3
+ import type { PropertiesLabels } from "./labels";
4
+ export declare function CompanyChip({ code, labels, className, }: {
5
+ code: string | null | undefined;
6
+ labels: PropertiesLabels["companyChip"];
7
+ className?: string;
8
+ }): import("react").JSX.Element;
9
+ export declare function InvoiceStatusBadge({ status, tone, statusLabel, className, }: {
10
+ status: string | null | undefined;
11
+ tone: StatusTone | undefined;
12
+ statusLabel: (status: string) => string;
13
+ className?: string;
14
+ }): import("react").JSX.Element;
15
+ export declare function VatBadge({ vatRate, vatLines, labels, className, }: {
16
+ vatRate: number | null | undefined;
17
+ vatLines?: PropertyInvoiceVatLine[] | null;
18
+ labels: PropertiesLabels["vatBadge"];
19
+ className?: string;
20
+ }): import("react").JSX.Element;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Badge } from "../../ui/badge.js";
3
+ import { cn } from "../../lib/class-names.js";
4
+ const TONE_STYLE = {
5
+ brand: "bg-brand-tint text-brand-dark",
6
+ success: "bg-success-soft text-success",
7
+ warning: "bg-warning-soft text-warning",
8
+ danger: "bg-danger-soft text-danger",
9
+ neutral: "bg-muted text-muted-foreground",
10
+ };
11
+ // A missing company is a gap to close, so it renders as a warning rather than a neutral value.
12
+ export function CompanyChip({ code, labels, className, }) {
13
+ if (!code) {
14
+ return (_jsx("span", { className: cn("inline-flex items-center whitespace-nowrap rounded-md bg-warning-soft px-2 py-0.5 text-xs font-medium text-warning", className), title: labels.missingTitle, children: labels.missing }));
15
+ }
16
+ return (_jsx("span", { className: cn("inline-flex items-center rounded-md bg-brand-tint px-2 py-0.5 font-mono text-xs font-medium text-brand-dark", className), children: code }));
17
+ }
18
+ export function InvoiceStatusBadge({ status, tone, statusLabel, className, }) {
19
+ return (_jsx(Badge, { variant: "outline", className: cn("border-transparent font-medium", TONE_STYLE[tone ?? "neutral"], className), children: status ? statusLabel(status) : "—" }));
20
+ }
21
+ // Three cases: a stated positive rate, a stated zero (exempt) and no recognized rate at all.
22
+ export function VatBadge({ vatRate, vatLines, labels, className, }) {
23
+ const rate = vatRate ?? null;
24
+ const relevant = (rate ?? 0) > 0;
25
+ // Only rates that carry net amounts count; a 0 % line beside a 19 % one is not a mixed case.
26
+ const appliedRates = [
27
+ ...new Set((vatLines ?? [])
28
+ .filter((line) => (line?.netAmount ?? 0) !== 0 && (line?.rate ?? 0) > 0)
29
+ .map((line) => line.rate)),
30
+ ].sort((left, right) => left - right);
31
+ const mixed = appliedRates.length > 1;
32
+ return (_jsx("span", { className: cn("inline-flex items-center whitespace-nowrap rounded-md px-2 py-0.5 text-xs font-medium", relevant || mixed ? "bg-brand-tint text-brand-dark" : "bg-muted text-muted-foreground", className), title: mixed
33
+ ? labels.mixedTitle(appliedRates.join(" %, "))
34
+ : relevant
35
+ ? labels.relevantTitle
36
+ : rate === 0
37
+ ? labels.exemptTitle
38
+ : labels.unclearTitle, children: mixed
39
+ ? labels.mixed
40
+ : relevant
41
+ ? `${labels.abbreviation} ${rate} %`
42
+ : rate === 0
43
+ ? labels.exempt
44
+ : labels.unclear }));
45
+ }
@@ -0,0 +1,11 @@
1
+ import type { PropertyCompany } from "../../adapters/properties";
2
+ import type { PropertiesLabels } from "./labels";
3
+ export declare function CompanyAssignmentField({ values, onValuesChange, companies, labels, requiredFieldTitle, error, disabled, }: {
4
+ values: string[];
5
+ onValuesChange: (values: string[]) => void;
6
+ companies: PropertyCompany[];
7
+ labels: PropertiesLabels["companyAssignment"];
8
+ requiredFieldTitle: string;
9
+ error?: boolean;
10
+ disabled?: boolean;
11
+ }): import("react").JSX.Element;