shutro-erp 0.3.4 → 0.3.5

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 (381) hide show
  1. package/dist/account_portal/.kosh/bindings.js +1 -1
  2. package/dist/account_portal/.kosh/schema.d.ts +54 -3
  3. package/dist/account_portal/module.d.ts +359 -6
  4. package/dist/accounting/.kosh/bindings.js +1 -1
  5. package/dist/accounting/.kosh/schema.d.ts +54 -3
  6. package/dist/accounting/index.js +214 -2
  7. package/dist/accounting/module.d.ts +217 -5
  8. package/dist/accounting/module.js +23 -2
  9. package/dist/calendar/.kosh/bindings.d.ts +22 -0
  10. package/dist/calendar/.kosh/bindings.js +15 -0
  11. package/dist/calendar/.kosh/schema.d.ts +795 -0
  12. package/dist/calendar/demo.d.ts +6 -0
  13. package/dist/calendar/demo.js +24 -0
  14. package/dist/calendar/index.d.ts +3 -0
  15. package/dist/calendar/index.js +291 -0
  16. package/dist/calendar/module.d.ts +1705 -0
  17. package/dist/calendar/module.js +180 -0
  18. package/dist/cli/init.js +48 -0
  19. package/dist/cli/templates.d.ts +1 -1
  20. package/dist/cli/templates.js +137 -0
  21. package/dist/contacts/.kosh/bindings.js +1 -1
  22. package/dist/contacts/.kosh/schema.d.ts +78 -3
  23. package/dist/contacts/demo.js +30 -1
  24. package/dist/contacts/index.d.ts +1 -1
  25. package/dist/contacts/index.js +155 -9
  26. package/dist/contacts/module.d.ts +338 -2
  27. package/dist/contacts/module.js +82 -5
  28. package/dist/crm/.kosh/bindings.d.ts +22 -0
  29. package/dist/crm/.kosh/bindings.js +15 -0
  30. package/dist/crm/.kosh/schema.d.ts +799 -0
  31. package/dist/crm/demo.d.ts +6 -0
  32. package/dist/crm/demo.js +45 -0
  33. package/dist/crm/index.d.ts +3 -0
  34. package/dist/crm/index.js +361 -0
  35. package/dist/crm/module.d.ts +1843 -0
  36. package/dist/crm/module.js +207 -0
  37. package/dist/event/.kosh/bindings.d.ts +22 -0
  38. package/dist/event/.kosh/bindings.js +15 -0
  39. package/dist/event/.kosh/schema.d.ts +843 -0
  40. package/dist/event/demo.d.ts +6 -0
  41. package/dist/event/demo.js +65 -0
  42. package/dist/event/index.d.ts +14 -0
  43. package/dist/event/index.js +333 -0
  44. package/dist/event/mails.d.ts +15 -0
  45. package/dist/event/mails.js +112 -0
  46. package/dist/event/module.d.ts +2800 -0
  47. package/dist/event/module.js +323 -0
  48. package/dist/event/web/CheckInBox.d.ts +9 -0
  49. package/dist/event/web/CheckInBox.js +87 -0
  50. package/dist/event/web/index.d.ts +11 -0
  51. package/dist/event/web/index.js +16 -0
  52. package/dist/event/web/messages.d.ts +2 -0
  53. package/dist/event/web/messages.js +23 -0
  54. package/dist/event_account/.kosh/bindings.d.ts +22 -0
  55. package/dist/event_account/.kosh/bindings.js +15 -0
  56. package/dist/event_account/.kosh/schema.d.ts +1639 -0
  57. package/dist/event_account/index.d.ts +3 -0
  58. package/dist/event_account/index.js +28 -0
  59. package/dist/event_account/module.d.ts +6968 -0
  60. package/dist/event_account/module.js +90 -0
  61. package/dist/hr/.kosh/bindings.d.ts +22 -0
  62. package/dist/hr/.kosh/bindings.js +15 -0
  63. package/dist/hr/.kosh/schema.d.ts +878 -0
  64. package/dist/hr/demo.d.ts +6 -0
  65. package/dist/hr/demo.js +60 -0
  66. package/dist/hr/index.d.ts +3 -0
  67. package/dist/hr/index.js +483 -0
  68. package/dist/hr/module.d.ts +2611 -0
  69. package/dist/hr/module.js +350 -0
  70. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  71. package/dist/l10n_bd/.kosh/schema.d.ts +54 -3
  72. package/dist/l10n_bd/module.d.ts +359 -6
  73. package/dist/l10n_in/.kosh/bindings.js +1 -1
  74. package/dist/l10n_in/.kosh/schema.d.ts +54 -3
  75. package/dist/l10n_in/module.d.ts +380 -7
  76. package/dist/maintenance/.kosh/bindings.d.ts +22 -0
  77. package/dist/maintenance/.kosh/bindings.js +15 -0
  78. package/dist/maintenance/.kosh/schema.d.ts +911 -0
  79. package/dist/maintenance/demo.d.ts +6 -0
  80. package/dist/maintenance/demo.js +60 -0
  81. package/dist/maintenance/index.d.ts +3 -0
  82. package/dist/maintenance/index.js +422 -0
  83. package/dist/maintenance/module.d.ts +3545 -0
  84. package/dist/maintenance/module.js +446 -0
  85. package/dist/maintenance_stock/.kosh/bindings.d.ts +22 -0
  86. package/dist/maintenance_stock/.kosh/bindings.js +15 -0
  87. package/dist/maintenance_stock/.kosh/schema.d.ts +1371 -0
  88. package/dist/maintenance_stock/index.d.ts +9 -0
  89. package/dist/maintenance_stock/index.js +52 -0
  90. package/dist/maintenance_stock/module.d.ts +6953 -0
  91. package/dist/maintenance_stock/module.js +192 -0
  92. package/dist/modules-sdk/index.d.ts +1 -1
  93. package/dist/modules-sdk/manifest.d.ts +70 -0
  94. package/dist/modules-sdk/manifest.js +35 -1
  95. package/dist/mrp/.kosh/bindings.d.ts +22 -0
  96. package/dist/mrp/.kosh/bindings.js +15 -0
  97. package/dist/mrp/.kosh/schema.d.ts +1378 -0
  98. package/dist/mrp/demo.d.ts +7 -0
  99. package/dist/mrp/demo.js +42 -0
  100. package/dist/mrp/index.d.ts +3 -0
  101. package/dist/mrp/index.js +392 -0
  102. package/dist/mrp/module.d.ts +8608 -0
  103. package/dist/mrp/module.js +695 -0
  104. package/dist/mrp_account/.kosh/bindings.d.ts +22 -0
  105. package/dist/mrp_account/.kosh/bindings.js +15 -0
  106. package/dist/mrp_account/.kosh/schema.d.ts +2106 -0
  107. package/dist/mrp_account/index.d.ts +3 -0
  108. package/dist/mrp_account/index.js +22 -0
  109. package/dist/mrp_account/module.d.ts +20840 -0
  110. package/dist/mrp_account/module.js +123 -0
  111. package/dist/payment/.kosh/bindings.js +1 -1
  112. package/dist/payment/.kosh/schema.d.ts +54 -3
  113. package/dist/payment/module.d.ts +359 -6
  114. package/dist/portal/.kosh/bindings.js +1 -1
  115. package/dist/portal/.kosh/schema.d.ts +78 -3
  116. package/dist/portal/module.d.ts +166 -2
  117. package/dist/pos/.kosh/bindings.d.ts +22 -0
  118. package/dist/pos/.kosh/bindings.js +15 -0
  119. package/dist/pos/.kosh/schema.d.ts +1690 -0
  120. package/dist/pos/accounting.d.ts +10 -0
  121. package/dist/pos/accounting.js +149 -0
  122. package/dist/pos/demo.d.ts +6 -0
  123. package/dist/pos/demo.js +90 -0
  124. package/dist/pos/index.d.ts +9 -0
  125. package/dist/pos/index.js +349 -0
  126. package/dist/pos/module.d.ts +9636 -0
  127. package/dist/pos/module.js +423 -0
  128. package/dist/pos/routes.d.ts +39 -0
  129. package/dist/pos/routes.js +368 -0
  130. package/dist/pos/web/Register.d.ts +9 -0
  131. package/dist/pos/web/Register.js +252 -0
  132. package/dist/pos/web/index.d.ts +11 -0
  133. package/dist/pos/web/index.js +16 -0
  134. package/dist/pos/web/messages.d.ts +2 -0
  135. package/dist/pos/web/messages.js +87 -0
  136. package/dist/pos_stock/.kosh/bindings.d.ts +22 -0
  137. package/dist/pos_stock/.kosh/bindings.js +15 -0
  138. package/dist/pos_stock/.kosh/schema.d.ts +2015 -0
  139. package/dist/pos_stock/index.d.ts +9 -0
  140. package/dist/pos_stock/index.js +35 -0
  141. package/dist/pos_stock/module.d.ts +11935 -0
  142. package/dist/pos_stock/module.js +115 -0
  143. package/dist/product/.kosh/bindings.js +1 -1
  144. package/dist/product/.kosh/schema.d.ts +51 -1
  145. package/dist/product/index.js +81 -4
  146. package/dist/product/module.d.ts +43 -1
  147. package/dist/product/module.js +9 -2
  148. package/dist/project/.kosh/bindings.js +1 -1
  149. package/dist/project/.kosh/schema.d.ts +54 -3
  150. package/dist/project/index.js +147 -7
  151. package/dist/project/module.d.ts +210 -2
  152. package/dist/project/module.js +52 -5
  153. package/dist/project_portal/.kosh/bindings.js +1 -1
  154. package/dist/project_portal/.kosh/schema.d.ts +54 -3
  155. package/dist/project_portal/module.d.ts +353 -4
  156. package/dist/purchase/.kosh/bindings.js +1 -1
  157. package/dist/purchase/.kosh/schema.d.ts +54 -3
  158. package/dist/purchase/index.js +163 -3
  159. package/dist/purchase/module.d.ts +215 -3
  160. package/dist/purchase/module.js +21 -3
  161. package/dist/purchase_account/.kosh/bindings.js +1 -1
  162. package/dist/purchase_account/.kosh/schema.d.ts +55 -3
  163. package/dist/purchase_account/index.js +26 -1
  164. package/dist/purchase_account/module.d.ts +393 -7
  165. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  166. package/dist/purchase_portal/.kosh/schema.d.ts +54 -3
  167. package/dist/purchase_portal/module.d.ts +366 -5
  168. package/dist/purchase_stock/.kosh/bindings.d.ts +22 -0
  169. package/dist/purchase_stock/.kosh/bindings.js +15 -0
  170. package/dist/purchase_stock/.kosh/schema.d.ts +1310 -0
  171. package/dist/purchase_stock/index.d.ts +7 -0
  172. package/dist/purchase_stock/index.js +91 -0
  173. package/dist/purchase_stock/module.d.ts +6763 -0
  174. package/dist/purchase_stock/module.js +144 -0
  175. package/dist/purchase_stock_account/.kosh/bindings.d.ts +22 -0
  176. package/dist/purchase_stock_account/.kosh/bindings.js +15 -0
  177. package/dist/purchase_stock_account/.kosh/schema.d.ts +1959 -0
  178. package/dist/purchase_stock_account/index.d.ts +3 -0
  179. package/dist/purchase_stock_account/index.js +24 -0
  180. package/dist/purchase_stock_account/module.d.ts +35366 -0
  181. package/dist/purchase_stock_account/module.js +162 -0
  182. package/dist/recruitment/.kosh/bindings.d.ts +22 -0
  183. package/dist/recruitment/.kosh/bindings.js +15 -0
  184. package/dist/recruitment/.kosh/schema.d.ts +990 -0
  185. package/dist/recruitment/demo.d.ts +6 -0
  186. package/dist/recruitment/demo.js +45 -0
  187. package/dist/recruitment/index.d.ts +3 -0
  188. package/dist/recruitment/index.js +385 -0
  189. package/dist/recruitment/module.d.ts +3247 -0
  190. package/dist/recruitment/module.js +193 -0
  191. package/dist/recruitment/web/HireButton.d.ts +9 -0
  192. package/dist/recruitment/web/HireButton.js +32 -0
  193. package/dist/recruitment/web/index.d.ts +11 -0
  194. package/dist/recruitment/web/index.js +16 -0
  195. package/dist/recruitment/web/messages.d.ts +2 -0
  196. package/dist/recruitment/web/messages.js +19 -0
  197. package/dist/reporting/documents.d.ts +5 -0
  198. package/dist/reporting/documents.js +28 -1
  199. package/dist/reporting/index.d.ts +1 -1
  200. package/dist/reporting/index.js +4 -2
  201. package/dist/reporting/models.js +9 -0
  202. package/dist/reporting/overviews.d.ts +16 -0
  203. package/dist/reporting/overviews.js +56 -0
  204. package/dist/reporting/routes-documents.js +1 -1
  205. package/dist/reporting/routes.js +138 -80
  206. package/dist/reporting/web/DashboardView.d.ts +11 -0
  207. package/dist/reporting/web/DashboardView.js +2 -2
  208. package/dist/reporting/web/DesignerView.js +5 -0
  209. package/dist/reporting/web/Overview.d.ts +9 -0
  210. package/dist/reporting/web/Overview.js +53 -0
  211. package/dist/reporting/web/index.d.ts +1 -0
  212. package/dist/reporting/web/index.js +4 -1
  213. package/dist/reporting/web/messages.js +9 -0
  214. package/dist/sale_account/.kosh/bindings.js +1 -1
  215. package/dist/sale_account/.kosh/schema.d.ts +59 -3
  216. package/dist/sale_account/index.js +22 -1
  217. package/dist/sale_account/module.d.ts +461 -7
  218. package/dist/sale_portal/.kosh/bindings.js +1 -1
  219. package/dist/sale_portal/.kosh/schema.d.ts +58 -3
  220. package/dist/sale_portal/module.d.ts +434 -5
  221. package/dist/sale_project/.kosh/bindings.js +1 -1
  222. package/dist/sale_project/.kosh/schema.d.ts +60 -3
  223. package/dist/sale_project/module.d.ts +476 -6
  224. package/dist/sale_stock/.kosh/bindings.d.ts +22 -0
  225. package/dist/sale_stock/.kosh/bindings.js +15 -0
  226. package/dist/sale_stock/.kosh/schema.d.ts +1228 -0
  227. package/dist/sale_stock/index.d.ts +7 -0
  228. package/dist/sale_stock/index.js +84 -0
  229. package/dist/sale_stock/module.d.ts +6791 -0
  230. package/dist/sale_stock/module.js +146 -0
  231. package/dist/sales/.kosh/bindings.js +1 -1
  232. package/dist/sales/.kosh/schema.d.ts +58 -3
  233. package/dist/sales/index.js +191 -4
  234. package/dist/sales/module.d.ts +355 -3
  235. package/dist/sales/module.js +78 -3
  236. package/dist/server/api/metadata.d.ts +5 -0
  237. package/dist/server/api/metadata.js +5 -0
  238. package/dist/server/api/rest.js +64 -1
  239. package/dist/server/api/views.js +1 -0
  240. package/dist/server/app-settings.d.ts +7 -0
  241. package/dist/server/app-settings.js +10 -0
  242. package/dist/server/auth/base.js +9 -0
  243. package/dist/server/index.d.ts +1 -0
  244. package/dist/server/index.js +1 -0
  245. package/dist/settings/app-settings.d.ts +2 -0
  246. package/dist/settings/app-settings.js +518 -0
  247. package/dist/settings/index.d.ts +1 -0
  248. package/dist/settings/index.js +15 -0
  249. package/dist/settings/people.js +29 -5
  250. package/dist/settings/web/AppSettingsPage.d.ts +11 -0
  251. package/dist/settings/web/AppSettingsPage.js +210 -0
  252. package/dist/settings/web/OverviewPage.js +16 -3
  253. package/dist/settings/web/PeoplePage.js +20 -6
  254. package/dist/settings/web/index.d.ts +1 -0
  255. package/dist/settings/web/index.js +3 -0
  256. package/dist/settings/web/messages.js +118 -2
  257. package/dist/stock/.kosh/bindings.d.ts +22 -0
  258. package/dist/stock/.kosh/bindings.js +15 -0
  259. package/dist/stock/.kosh/schema.d.ts +1147 -0
  260. package/dist/stock/demo.d.ts +7 -0
  261. package/dist/stock/demo.js +55 -0
  262. package/dist/stock/index.d.ts +3 -0
  263. package/dist/stock/index.js +674 -0
  264. package/dist/stock/module.d.ts +6330 -0
  265. package/dist/stock/module.js +1325 -0
  266. package/dist/stock/web/ScanBox.d.ts +8 -0
  267. package/dist/stock/web/ScanBox.js +90 -0
  268. package/dist/stock/web/index.d.ts +11 -0
  269. package/dist/stock/web/index.js +16 -0
  270. package/dist/stock/web/messages.d.ts +2 -0
  271. package/dist/stock/web/messages.js +25 -0
  272. package/dist/stock_account/.kosh/bindings.d.ts +22 -0
  273. package/dist/stock_account/.kosh/bindings.js +15 -0
  274. package/dist/stock_account/.kosh/schema.d.ts +1875 -0
  275. package/dist/stock_account/index.d.ts +3 -0
  276. package/dist/stock_account/index.js +130 -0
  277. package/dist/stock_account/module.d.ts +10655 -0
  278. package/dist/stock_account/module.js +339 -0
  279. package/dist/storage/attachments.d.ts +5 -0
  280. package/dist/storage/attachments.js +12 -1
  281. package/dist/storage/models.js +3 -0
  282. package/dist/storage/routes.js +13 -2
  283. package/dist/todo/.kosh/bindings.d.ts +22 -0
  284. package/dist/todo/.kosh/bindings.js +15 -0
  285. package/dist/todo/.kosh/schema.d.ts +732 -0
  286. package/dist/todo/demo.d.ts +6 -0
  287. package/dist/todo/demo.js +35 -0
  288. package/dist/todo/index.d.ts +3 -0
  289. package/dist/todo/index.js +187 -0
  290. package/dist/todo/module.d.ts +1180 -0
  291. package/dist/todo/module.js +130 -0
  292. package/dist/trade_finance/.kosh/bindings.d.ts +22 -0
  293. package/dist/trade_finance/.kosh/bindings.js +15 -0
  294. package/dist/trade_finance/.kosh/schema.d.ts +884 -0
  295. package/dist/trade_finance/demo.d.ts +6 -0
  296. package/dist/trade_finance/demo.js +63 -0
  297. package/dist/trade_finance/index.d.ts +3 -0
  298. package/dist/trade_finance/index.js +413 -0
  299. package/dist/trade_finance/module.d.ts +3779 -0
  300. package/dist/trade_finance/module.js +505 -0
  301. package/dist/trade_finance_account/.kosh/bindings.d.ts +22 -0
  302. package/dist/trade_finance_account/.kosh/bindings.js +15 -0
  303. package/dist/trade_finance_account/.kosh/schema.d.ts +1711 -0
  304. package/dist/trade_finance_account/index.d.ts +3 -0
  305. package/dist/trade_finance_account/index.js +92 -0
  306. package/dist/trade_finance_account/module.d.ts +7928 -0
  307. package/dist/trade_finance_account/module.js +244 -0
  308. package/dist/trade_finance_purchase/.kosh/bindings.d.ts +22 -0
  309. package/dist/trade_finance_purchase/.kosh/bindings.js +15 -0
  310. package/dist/trade_finance_purchase/.kosh/schema.d.ts +1153 -0
  311. package/dist/trade_finance_purchase/index.d.ts +8 -0
  312. package/dist/trade_finance_purchase/index.js +50 -0
  313. package/dist/trade_finance_purchase/module.d.ts +4252 -0
  314. package/dist/trade_finance_purchase/module.js +44 -0
  315. package/dist/trade_finance_sale/.kosh/bindings.d.ts +22 -0
  316. package/dist/trade_finance_sale/.kosh/bindings.js +15 -0
  317. package/dist/trade_finance_sale/.kosh/schema.d.ts +1071 -0
  318. package/dist/trade_finance_sale/index.d.ts +8 -0
  319. package/dist/trade_finance_sale/index.js +50 -0
  320. package/dist/trade_finance_sale/module.d.ts +4280 -0
  321. package/dist/trade_finance_sale/module.js +44 -0
  322. package/dist/ui/api.d.ts +2 -1
  323. package/dist/ui/api.js +2 -1
  324. package/dist/ui/app.js +2 -1
  325. package/dist/ui/components/AppIcon.js +23 -1
  326. package/dist/ui/components/primitives.d.ts +2 -0
  327. package/dist/ui/components/primitives.js +7 -1
  328. package/dist/ui/i18n/messages.d.ts +23 -0
  329. package/dist/ui/i18n/messages.js +38 -0
  330. package/dist/ui/index.d.ts +1 -0
  331. package/dist/ui/index.js +1 -0
  332. package/dist/ui/offline/queue.d.ts +5 -1
  333. package/dist/ui/offline/queue.js +2 -0
  334. package/dist/ui/registry.d.ts +5 -0
  335. package/dist/ui/shell/Home.js +55 -4
  336. package/dist/ui/views/FormView.js +98 -17
  337. package/dist/ui/views/KanbanView.d.ts +2 -0
  338. package/dist/ui/views/KanbanView.js +32 -3
  339. package/dist/ui/views/ListView.js +108 -11
  340. package/dist/ui/views/StatsRow.d.ts +14 -0
  341. package/dist/ui/views/StatsRow.js +88 -0
  342. package/dist/ui/views/widgets.d.ts +2 -1
  343. package/dist/ui/views/widgets.js +194 -1
  344. package/dist/views/builders.d.ts +19 -2
  345. package/dist/views/builders.js +21 -1
  346. package/dist/views/condition.d.ts +6 -0
  347. package/dist/views/condition.js +20 -0
  348. package/dist/views/documents.d.ts +8 -0
  349. package/dist/views/extend.d.ts +2 -2
  350. package/dist/views/extend.js +11 -1
  351. package/dist/views/index.d.ts +2 -2
  352. package/dist/views/index.js +2 -2
  353. package/dist/views/types.d.ts +52 -5
  354. package/dist/views/validate.js +39 -1
  355. package/dist/website/.kosh/bindings.d.ts +22 -0
  356. package/dist/website/.kosh/bindings.js +15 -0
  357. package/dist/website/.kosh/schema.d.ts +881 -0
  358. package/dist/website/demo.d.ts +6 -0
  359. package/dist/website/demo.js +115 -0
  360. package/dist/website/index.d.ts +9 -0
  361. package/dist/website/index.js +250 -0
  362. package/dist/website/module.d.ts +2987 -0
  363. package/dist/website/module.js +281 -0
  364. package/dist/website/render.d.ts +45 -0
  365. package/dist/website/render.js +102 -0
  366. package/dist/website/routes.d.ts +12 -0
  367. package/dist/website/routes.js +246 -0
  368. package/dist/website/web/PagePreview.d.ts +7 -0
  369. package/dist/website/web/PagePreview.js +33 -0
  370. package/dist/website/web/index.d.ts +11 -0
  371. package/dist/website/web/index.js +16 -0
  372. package/dist/website/web/messages.d.ts +2 -0
  373. package/dist/website/web/messages.js +15 -0
  374. package/dist/website_crm/.kosh/bindings.d.ts +22 -0
  375. package/dist/website_crm/.kosh/bindings.js +15 -0
  376. package/dist/website_crm/.kosh/schema.d.ts +966 -0
  377. package/dist/website_crm/index.d.ts +3 -0
  378. package/dist/website_crm/index.js +39 -0
  379. package/dist/website_crm/module.d.ts +2955 -0
  380. package/dist/website_crm/module.js +44 -0
  381. package/package.json +118 -1
@@ -0,0 +1,210 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Settings › App settings (RFC 0020): the apps' own settings, declared in their manifests. The apps you may see on
4
+ * the left; an app's sections as cards — each setting with its help, a mark when it differs from the default with
5
+ * *Reset*, and *Save* per section, sending only what changed. Company settings name the company they apply to. The
6
+ * server checks every value and who may change it, and audits each change. Each setting opens its history (who, when,
7
+ * old → new); administrators copy a company's settings to another, export them as a file and import one, every one
8
+ * of those previewed before anything is written.
9
+ */
10
+ import { Alert, ApiError, AppIcon, Badge, Button, Checkbox, DateField, Dialog, Input, Label, Select, Textarea, useKosh, } from '../../ui/index.js';
11
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
12
+ import { Copy, Download, History, RotateCcw, Upload } from 'lucide-react';
13
+ import { useEffect, useRef, useState } from 'react';
14
+ import { useTranslation } from 'react-i18next';
15
+ const shown = (value) => value === null || value === ''
16
+ ? '—'
17
+ : value === true
18
+ ? '✓'
19
+ : value === false
20
+ ? '✗'
21
+ : String(value);
22
+ const BASE = '/api/v1/settings/app-settings';
23
+ const message = (error) => (error instanceof ApiError ? error.message : String(error));
24
+ /** The app shown: `?app=` in the address, else the first. */
25
+ function useChosenApp(apps) {
26
+ const [chosen, setChosen] = useState(() => new URLSearchParams(window.location.search).get('app') ?? undefined);
27
+ const set = (app) => {
28
+ setChosen(app);
29
+ const url = new URL(window.location.href);
30
+ url.searchParams.set('app', app);
31
+ window.history.replaceState(window.history.state, '', url);
32
+ };
33
+ return [apps.some((a) => a.app === chosen) ? chosen : apps[0]?.app, set];
34
+ }
35
+ function Control({ setting, value, disabled, onChange, }) {
36
+ const id = `setting-${setting.key}`;
37
+ const { t } = useTranslation('settings');
38
+ switch (setting.type) {
39
+ case 'boolean':
40
+ return (_jsx(Checkbox, { id: id, checked: value === true, disabled: disabled, onCheckedChange: (on) => onChange(on === true) }));
41
+ case 'selection':
42
+ return (_jsx(Select, { id: id, label: setting.label, value: typeof value === 'string' ? value : 'none', disabled: disabled, options: [{ value: 'none', label: t('appSettings.notSet') }, ...(setting.options ?? [])], onChange: (v) => onChange(v === 'none' ? null : v) }));
43
+ case 'text':
44
+ return (_jsx(Textarea, { id: id, rows: 4, disabled: disabled, value: typeof value === 'string' ? value : '', onChange: (e) => onChange(e.target.value) }));
45
+ case 'date':
46
+ return disabled ? (_jsx(Input, { id: id, disabled: true, className: "w-48", value: typeof value === 'string' ? value : '', readOnly: true })) : (_jsx(DateField, { id: id, value: typeof value === 'string' ? value : null, onChange: onChange }));
47
+ default:
48
+ return (_jsx(Input, { id: id, className: setting.type === 'char' ? undefined : 'w-48', disabled: disabled, type: setting.type === 'integer' ? 'number' : 'text', inputMode: setting.type === 'decimal' ? 'decimal' : undefined, min: setting.min ?? undefined, max: setting.max ?? undefined, value: value === null ? '' : String(value), onChange: (e) => {
49
+ const raw = e.target.value;
50
+ onChange(setting.type === 'integer'
51
+ ? raw === ''
52
+ ? null
53
+ : Number.parseInt(raw, 10)
54
+ : raw === ''
55
+ ? null
56
+ : raw);
57
+ } }));
58
+ }
59
+ }
60
+ /** Who changed a setting, when, from what to what — the audit log of this section, newest first. */
61
+ function HistoryDialog({ api, app, section, setting, onClose, }) {
62
+ const { t } = useTranslation('settings');
63
+ const { format } = useKosh();
64
+ const history = useQuery({
65
+ queryKey: ['settings', 'app-settings', app, section.key, 'history', setting?.key],
66
+ queryFn: () => api.call('GET', `${BASE}/${app}/${section.key}/history?key=${encodeURIComponent(setting?.key ?? '')}`),
67
+ enabled: setting !== null,
68
+ });
69
+ return (_jsx(Dialog, { open: setting !== null, onOpenChange: (open) => {
70
+ if (!open)
71
+ onClose();
72
+ }, title: t('appSettings.historyOf', { setting: setting?.label ?? '' }), children: history.error !== null ? (_jsx(Alert, { tone: "danger", children: message(history.error) })) : history.data === undefined ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "\u2026" })) : history.data.events.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('appSettings.neverChanged') })) : (_jsx("ol", { className: "grid gap-2 text-sm", children: history.data.events.map((e) => (_jsxs("li", { className: "grid gap-0.5 rounded-md border p-2", children: [_jsxs("span", { className: "text-xs text-muted-foreground", children: [format.datetime(e.at), " \u00B7 ", e.actor?.name ?? t('appSettings.system'), " \u00B7", ' ', t(`appSettings.how.${e.how}`)] }), e.changes.map((c) => (_jsxs("span", { className: "tabular-nums", children: [_jsx("s", { className: "text-muted-foreground", children: shown(c.from) }), " \u2192 ", _jsx("b", { children: shown(c.to) })] }, c.key)))] }, e.at))) })) }));
73
+ }
74
+ /** The changes a copy or an import would make, section by section. */
75
+ function ChangesList({ preview }) {
76
+ const { t } = useTranslation('settings');
77
+ return (_jsxs("div", { className: "grid gap-3 text-sm", children: [preview.sections.length === 0 ? (_jsx("p", { className: "text-muted-foreground", children: t('appSettings.nothingToChange') })) : (preview.sections.map((s) => (_jsxs("div", { className: "grid gap-1", children: [_jsxs("span", { className: "font-medium", children: [s.appLabel, " \u203A ", s.label] }), _jsx("ul", { className: "grid gap-0.5 ps-4", children: s.changes.map((c) => (_jsxs("li", { className: "tabular-nums", children: [c.label, ": ", _jsx("s", { className: "text-muted-foreground", children: shown(c.from) }), " \u2192", ' ', _jsx("b", { children: shown(c.to) })] }, c.key))) })] }, `${s.app}.${s.section}`)))), (preview.skipped ?? []).length === 0 ? null : (_jsx("p", { className: "text-xs text-muted-foreground", children: t('appSettings.skipped', { what: (preview.skipped ?? []).join(', ') }) }))] }));
78
+ }
79
+ /** Administrators: copy this company's settings to another, export them, import a file — each previewed first. */
80
+ function Tools({ api, onDone }) {
81
+ const { t } = useTranslation('settings');
82
+ const { companyId } = useKosh();
83
+ const [copying, setCopying] = useState(false);
84
+ const [to, setTo] = useState('');
85
+ const [importing, setImporting] = useState(null);
86
+ const [error, setError] = useState(null);
87
+ const [done, setDone] = useState(null);
88
+ const file = useRef(null);
89
+ const companies = useQuery({
90
+ queryKey: ['settings', 'companies'],
91
+ queryFn: () => api.call('GET', '/api/v1/settings/companies'),
92
+ enabled: copying,
93
+ });
94
+ const targets = (companies.data?.companies ?? []).filter((c) => c.id !== companyId && c.member);
95
+ const copyPreview = useQuery({
96
+ queryKey: ['settings', 'app-settings', 'copy', companyId, to],
97
+ queryFn: () => api.call('POST', `${BASE}/copy`, { from: companyId, to: Number(to), preview: true }),
98
+ enabled: copying && to !== '',
99
+ });
100
+ const copy = useMutation({
101
+ mutationFn: () => api.call('POST', `${BASE}/copy`, { from: companyId, to: Number(to) }),
102
+ onSuccess: (r) => {
103
+ setCopying(false);
104
+ setDone(t('appSettings.copied', { count: r.sections.length }));
105
+ onDone();
106
+ },
107
+ onError: (e) => setError(message(e)),
108
+ });
109
+ const importPreview = useQuery({
110
+ queryKey: ['settings', 'app-settings', 'import', importing],
111
+ queryFn: () => api.call('POST', `${BASE}/import`, { file: importing, preview: true }),
112
+ enabled: importing !== null,
113
+ retry: false,
114
+ });
115
+ const doImport = useMutation({
116
+ mutationFn: () => api.call('POST', `${BASE}/import`, { file: importing }),
117
+ onSuccess: (r) => {
118
+ setImporting(null);
119
+ setDone(t('appSettings.imported', { count: r.sections.length }));
120
+ onDone();
121
+ },
122
+ onError: (e) => setError(message(e)),
123
+ });
124
+ const exportFile = async () => {
125
+ try {
126
+ const data = await api.call('GET', `${BASE}/export`);
127
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
128
+ const url = URL.createObjectURL(blob);
129
+ const a = document.createElement('a');
130
+ a.href = url;
131
+ a.download = `app-settings-${data.company.name.replaceAll(/[^\w-]+/g, '_')}.json`;
132
+ a.click();
133
+ URL.revokeObjectURL(url);
134
+ }
135
+ catch (e) {
136
+ setError(message(e));
137
+ }
138
+ };
139
+ const read = (f) => {
140
+ if (f === undefined)
141
+ return;
142
+ f.text().then((text) => {
143
+ try {
144
+ setImporting(JSON.parse(text));
145
+ setError(null);
146
+ }
147
+ catch {
148
+ setError(t('appSettings.notJson'));
149
+ }
150
+ }, (e) => setError(message(e)));
151
+ };
152
+ return (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Button, { size: "sm", variant: "secondary", onClick: () => setCopying(true), children: [_jsx(Copy, { "aria-hidden": true }), t('appSettings.copyTo')] }), _jsxs(Button, { size: "sm", variant: "secondary", onClick: () => void exportFile(), children: [_jsx(Download, { "aria-hidden": true }), t('appSettings.export')] }), _jsxs(Button, { size: "sm", variant: "secondary", onClick: () => file.current?.click(), children: [_jsx(Upload, { "aria-hidden": true }), t('appSettings.import')] }), _jsx("input", { ref: file, type: "file", accept: "application/json,.json", className: "sr-only", "aria-label": t('appSettings.importFile'), onChange: (e) => {
153
+ read(e.target.files?.[0]);
154
+ e.target.value = '';
155
+ } }), done === null ? null : _jsx("span", { className: "text-sm text-muted-foreground", children: done }), error === null ? null : _jsx(Alert, { tone: "danger", children: error }), _jsx(Dialog, { open: copying, onOpenChange: setCopying, title: t('appSettings.copyTitle'), description: t('appSettings.copyIntro'), footer: _jsx(Button, { variant: "primary", disabled: to === '' || (copyPreview.data?.sections.length ?? 0) === 0, busy: copy.isPending, onClick: () => copy.mutate(), children: t('appSettings.copyNow') }), children: _jsxs("div", { className: "grid gap-3", children: [_jsx(Select, { id: "copy-to", label: t('appSettings.toCompany'), value: to === '' ? 'none' : to, options: [
156
+ { value: 'none', label: t('appSettings.chooseCompany') },
157
+ ...targets.map((c) => ({ value: String(c.id), label: c.name })),
158
+ ], onChange: (v) => setTo(v === 'none' ? '' : v) }), copyPreview.error !== null ? (_jsx(Alert, { tone: "danger", children: message(copyPreview.error) })) : copyPreview.data === undefined ? null : (_jsx(ChangesList, { preview: copyPreview.data }))] }) }), _jsx(Dialog, { open: importing !== null, onOpenChange: (open) => {
159
+ if (!open)
160
+ setImporting(null);
161
+ }, title: t('appSettings.importTitle'), description: t('appSettings.importIntro'), footer: _jsx(Button, { variant: "primary", disabled: (importPreview.data?.sections.length ?? 0) === 0, busy: doImport.isPending, onClick: () => doImport.mutate(), children: t('appSettings.importNow') }), children: importPreview.error !== null ? (_jsx(Alert, { tone: "danger", children: message(importPreview.error) })) : importPreview.data === undefined ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "\u2026" })) : (_jsx(ChangesList, { preview: importPreview.data })) })] }));
162
+ }
163
+ function SectionCard({ api, app, section, }) {
164
+ const { t } = useTranslation('settings');
165
+ const queryClient = useQueryClient();
166
+ const [draft, setDraft] = useState({});
167
+ const [history, setHistory] = useState(null);
168
+ // biome-ignore lint/correctness/useExhaustiveDependencies: a reloaded section starts a clean draft
169
+ useEffect(() => setDraft({}), [section]);
170
+ const refresh = () => void queryClient.invalidateQueries({ queryKey: ['settings', 'app-settings', app] });
171
+ const save = useMutation({
172
+ mutationFn: () => api.call('PATCH', `${BASE}/${app}/${section.key}`, { values: draft }),
173
+ onSuccess: refresh,
174
+ });
175
+ const reset = useMutation({
176
+ mutationFn: (key) => api.call('POST', `${BASE}/${app}/${section.key}/reset`, { keys: [key] }),
177
+ onSuccess: refresh,
178
+ });
179
+ const currentOf = (s) => (s.key in draft ? draft[s.key] : s.value);
180
+ const dirty = Object.keys(draft).length > 0;
181
+ return (_jsxs("section", { id: `section-${section.key}`, "aria-labelledby": `section-${section.key}-title`, className: "grid gap-4 rounded-lg border bg-surface p-5", children: [_jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [_jsxs("div", { children: [_jsx("h2", { id: `section-${section.key}-title`, className: "font-semibold", children: section.label }), section.description === null ? null : (_jsx("p", { className: "text-sm text-muted-foreground", children: section.description }))] }), _jsx(Badge, { tone: "neutral", children: section.scope === 'company' ? t('appSettings.perCompany') : t('appSettings.everywhere') })] }), _jsx("div", { className: "grid gap-5", children: section.settings
182
+ .filter((s) => {
183
+ if (s.visibleWhen === null)
184
+ return true;
185
+ const other = section.settings.find((x) => x.key === s.visibleWhen?.key);
186
+ return other !== undefined && currentOf(other) === s.visibleWhen.equals;
187
+ })
188
+ .map((s) => (_jsxs("div", { className: "grid gap-1.5 sm:grid-cols-[16rem_1fr] sm:gap-6", children: [_jsxs("div", { className: "grid content-start gap-1", children: [_jsxs(Label, { htmlFor: `setting-${s.key}`, className: "flex flex-wrap items-center gap-2", children: [s.label, s.changed && !(s.key in draft) ? (_jsx(Badge, { tone: "info", children: t('appSettings.changed') })) : null] }), s.help === null ? null : _jsx("p", { className: "text-xs text-muted-foreground", children: s.help })] }), _jsxs("div", { className: "flex flex-wrap items-start gap-2", children: [_jsx("div", { className: "min-w-0 flex-1", children: _jsx(Control, { setting: s, value: currentOf(s), disabled: !section.editable, onChange: (v) => setDraft((d) => ({ ...d, [s.key]: v })) }) }), _jsx(Button, { size: "icon", variant: "ghost", "aria-label": t('appSettings.historyOf', { setting: s.label }), title: t('appSettings.history'), onClick: () => setHistory(s), children: _jsx(History, { "aria-hidden": true }) }), s.changed && section.editable && !(s.key in draft) ? (_jsxs(Button, { size: "sm", variant: "ghost", busy: reset.isPending && reset.variables === s.key, "aria-label": t('appSettings.resetOne', { setting: s.label }), title: t('appSettings.defaultIs', {
189
+ value: s.default === null || s.default === '' ? '—' : String(s.default),
190
+ }), onClick: () => reset.mutate(s.key), children: [_jsx(RotateCcw, { "aria-hidden": true }), t('appSettings.reset')] })) : null] })] }, s.key))) }), save.error === null && reset.error === null ? null : (_jsx(Alert, { tone: "danger", children: message(save.error ?? reset.error) })), _jsx(HistoryDialog, { api: api, app: app, section: section, setting: history, onClose: () => setHistory(null) }), section.editable ? (_jsxs("div", { className: "flex items-center justify-end gap-2", children: [save.isSuccess && !dirty ? (_jsx("span", { className: "text-sm text-muted-foreground", children: t('appSettings.saved') })) : null, dirty ? (_jsx(Button, { variant: "ghost", onClick: () => setDraft({}), children: t('appSettings.discard') })) : null, _jsx(Button, { variant: "primary", disabled: !dirty, busy: save.isPending, onClick: () => save.mutate(), children: t('appSettings.save') })] })) : (_jsx("p", { className: "text-xs text-muted-foreground", children: t('appSettings.readOnly') }))] }));
191
+ }
192
+ export function AppSettingsPage({ api }) {
193
+ const { t } = useTranslation('settings');
194
+ const { companyId } = useKosh();
195
+ const queryClient = useQueryClient();
196
+ const list = useQuery({
197
+ queryKey: ['settings', 'app-settings'],
198
+ queryFn: () => api.call('GET', BASE),
199
+ });
200
+ const apps = list.data?.apps ?? [];
201
+ const [app, choose] = useChosenApp(apps);
202
+ const settings = useQuery({
203
+ queryKey: ['settings', 'app-settings', app, companyId],
204
+ queryFn: () => api.call('GET', `${BASE}/${app}`),
205
+ enabled: app !== undefined,
206
+ });
207
+ if (list.error !== null)
208
+ return _jsx(Alert, { tone: "danger", children: message(list.error) });
209
+ return (_jsxs("div", { className: "mx-auto grid max-w-5xl gap-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: t('appSettings.title') }), _jsx("p", { className: "text-sm text-muted-foreground", children: t('appSettings.intro') })] }), list.data?.administrator === true ? (_jsx(Tools, { api: api, onDone: () => void queryClient.invalidateQueries({ queryKey: ['settings', 'app-settings'] }) })) : null] }), list.data !== undefined && apps.length === 0 ? (_jsx("p", { className: "rounded-lg border bg-surface p-6 text-center text-sm text-muted-foreground", children: t('appSettings.none') })) : (_jsxs("div", { className: "grid gap-6 md:grid-cols-[13rem_1fr]", children: [_jsx("nav", { "aria-label": t('appSettings.apps'), className: "md:sticky md:top-16 md:self-start", children: _jsx("ul", { className: "flex gap-1 overflow-x-auto md:grid", children: apps.map((a) => (_jsx("li", { children: _jsxs("button", { type: "button", "aria-current": a.app === app ? 'page' : undefined, onClick: () => choose(a.app), className: `flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm hover:bg-muted ${a.app === app ? 'bg-accent font-medium text-accent-foreground' : ''}`, children: [_jsx(AppIcon, { name: a.icon ?? a.app, className: "size-6 rounded-md [&_svg]:size-3.5" }), a.label] }) }, a.app))) }) }), _jsx("div", { className: "grid gap-4", children: settings.error !== null ? (_jsx(Alert, { tone: "danger", children: message(settings.error) })) : settings.data === undefined ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "\u2026" })) : (settings.data.sections.map((s) => (_jsx(SectionCard, { api: api, app: settings.data.app, section: s }, s.key)))) })] }))] }));
210
+ }
@@ -21,6 +21,7 @@ const SECTION_ICONS = {
21
21
  'settings.action_security': 'security',
22
22
  'settings.action_audit': 'audit',
23
23
  'settings.action_apps': 'apps',
24
+ 'settings.action_app_settings': 'settings',
24
25
  'assistant.action_settings': 'assistant',
25
26
  };
26
27
  export function OverviewPage({ api }) {
@@ -28,10 +29,22 @@ export function OverviewPage({ api }) {
28
29
  const id = useId();
29
30
  const [query, setQuery] = useState('');
30
31
  const menus = useQuery({ queryKey: ['menus', 'settings'], queryFn: () => api.menus('settings') });
31
- const about = (action) => ({
32
- description: t(`overview.about.${action}.description`, { defaultValue: '' }),
33
- keywords: t(`overview.about.${action}.keywords`, { defaultValue: '' }),
32
+ // The apps' own settings (RFC 0020): their sections' words make App settings findable by them.
33
+ const appSettings = useQuery({
34
+ queryKey: ['settings', 'app-settings'],
35
+ queryFn: () => api.call('GET', '/api/v1/settings/app-settings'),
34
36
  });
37
+ const about = (action) => {
38
+ const words = action === 'settings.action_app_settings'
39
+ ? (appSettings.data?.apps ?? [])
40
+ .flatMap((a) => [a.label, ...a.sections.flatMap((s) => [s.label, s.keywords])])
41
+ .join(' ')
42
+ : '';
43
+ return {
44
+ description: t(`overview.about.${action}.description`, { defaultValue: '' }),
45
+ keywords: `${t(`overview.about.${action}.keywords`, { defaultValue: '' })} ${words}`,
46
+ };
47
+ };
35
48
  const entry = (n) => n.action === undefined || n.action === 'settings.action_overview'
36
49
  ? null
37
50
  : { label: n.label, action: n.action, ...about(n.action) };
@@ -25,31 +25,45 @@ function InvitationLink({ url, emailed, to, }) {
25
25
  void navigator.clipboard?.writeText(url).then(() => setCopied(true));
26
26
  }, children: [copied ? _jsx(Check, { "aria-hidden": true }) : _jsx(Copy, { "aria-hidden": true }), copied ? t('people.copied') : t('people.copy')] })] })] }));
27
27
  }
28
+ /** Invite someone (they choose their password from a link), or add them directly with a password you pass on. */
28
29
  function InviteDialog({ api, catalogue, open, onOpenChange, }) {
29
30
  const { t } = useTranslation('settings');
30
31
  const id = useId();
31
32
  const queryClient = useQueryClient();
33
+ const [mode, setMode] = useState('invite');
32
34
  const [name, setName] = useState('');
33
35
  const [email, setEmail] = useState('');
36
+ const [login, setLogin] = useState('');
37
+ const [password, setPassword] = useState('');
38
+ const [repeat, setRepeat] = useState('');
34
39
  const [assignments, setAssignments] = useState([]);
35
40
  const invite = useMutation({
36
- mutationFn: () => api.call('POST', P, {
37
- name,
38
- email,
39
- assignments,
40
- }),
41
+ mutationFn: () => api.call('POST', P, mode === 'invite'
42
+ ? { name, email, assignments }
43
+ : { name, email, login: login.trim() === '' ? email : login, password, assignments }),
41
44
  onSuccess: () => void queryClient.invalidateQueries({ queryKey: ['settings', 'people'] }),
42
45
  });
43
46
  useEffect(() => {
44
47
  if (!open) {
48
+ setMode('invite');
45
49
  setName('');
46
50
  setEmail('');
51
+ setLogin('');
52
+ setPassword('');
53
+ setRepeat('');
47
54
  setAssignments([]);
48
55
  invite.reset();
49
56
  }
50
57
  }, [open, invite.reset]);
51
58
  const done = invite.data;
52
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, title: t('people.inviteTitle'), description: t('people.inviteHint'), wide: true, footer: done === undefined ? (_jsxs(_Fragment, { children: [_jsx(Button, { onClick: () => onOpenChange(false), children: t('profile.cancel') }), _jsx(Button, { variant: "primary", busy: invite.isPending, disabled: name.trim() === '' || !email.includes('@'), onClick: () => invite.mutate(), children: t('people.send') })] })) : (_jsx(Button, { variant: "primary", onClick: () => onOpenChange(false), children: t('people.done') })), children: done !== undefined ? (_jsx(InvitationLink, { url: done.activationUrl, emailed: done.emailed, to: done.login })) : (_jsxs("div", { className: "grid gap-4", children: [_jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: `${id}-name`, children: t('profile.name') }), _jsx(Input, { id: `${id}-name`, value: name, onChange: (e) => setName(e.target.value) })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: `${id}-email`, children: t('profile.email') }), _jsx(Input, { id: `${id}-email`, type: "email", value: email, onChange: (e) => setEmail(e.target.value) })] })] }), _jsxs("div", { className: "grid gap-2", children: [_jsx("h3", { className: "text-sm font-semibold", children: t('people.roles') }), _jsx(RolesEditor, { catalogue: catalogue, value: assignments, onChange: setAssignments })] }), invite.error === null ? null : _jsx(Alert, { tone: "danger", children: message(invite.error) })] })) }));
59
+ const mismatch = repeat !== '' && repeat !== password;
60
+ const ready = name.trim() !== '' &&
61
+ (mode === 'invite'
62
+ ? email.includes('@')
63
+ : (login.trim() !== '' || email.includes('@')) &&
64
+ password.length >= 12 &&
65
+ repeat === password);
66
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, title: mode === 'invite' ? t('people.inviteTitle') : t('people.addTitle'), description: mode === 'invite' ? t('people.inviteHint') : t('people.addHint'), wide: true, footer: done === undefined ? (_jsxs(_Fragment, { children: [_jsx(Button, { onClick: () => onOpenChange(false), children: t('profile.cancel') }), _jsx(Button, { variant: "primary", busy: invite.isPending, disabled: !ready, onClick: () => invite.mutate(), children: mode === 'invite' ? t('people.send') : t('people.add') })] })) : (_jsx(Button, { variant: "primary", onClick: () => onOpenChange(false), children: t('people.done') })), children: done !== undefined ? (done.activationUrl === null ? (_jsx(Alert, { tone: "info", children: t('people.added', { login: done.login }) })) : (_jsx(InvitationLink, { url: done.activationUrl, emailed: done.emailed, to: done.login }))) : (_jsxs("div", { className: "grid gap-4", children: [_jsxs("fieldset", { className: "grid gap-2 sm:grid-cols-2", children: [_jsx("legend", { className: "sr-only", children: t('people.how') }), ['invite', 'password'].map((m) => (_jsxs("label", { className: `flex cursor-pointer items-start gap-2 rounded-lg border p-3 text-sm ${mode === m ? 'border-primary bg-accent/40' : ''}`, children: [_jsx("input", { type: "radio", name: `${id}-mode`, className: "mt-1", checked: mode === m, onChange: () => setMode(m) }), _jsxs("span", { children: [_jsx("span", { className: "font-medium", children: t(`people.mode.${m}`) }), _jsx("span", { className: "block text-muted-foreground", children: t(`people.mode.${m}Hint`) })] })] }, m)))] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: `${id}-name`, children: t('profile.name') }), _jsx(Input, { id: `${id}-name`, value: name, onChange: (e) => setName(e.target.value) })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: `${id}-email`, children: mode === 'invite' ? t('profile.email') : t('people.emailOptional') }), _jsx(Input, { id: `${id}-email`, type: "email", value: email, onChange: (e) => setEmail(e.target.value) })] }), mode === 'password' ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: `${id}-login`, children: t('people.login') }), _jsx(Input, { id: `${id}-login`, autoComplete: "off", placeholder: email === '' ? 'rahim' : email, value: login, onChange: (e) => setLogin(e.target.value) })] }), _jsx("div", {}), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: `${id}-password`, children: t('people.password') }), _jsx(Input, { id: `${id}-password`, type: "password", autoComplete: "new-password", value: password, onChange: (e) => setPassword(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: t('people.passwordHint') })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: `${id}-repeat`, children: t('people.repeat') }), _jsx(Input, { id: `${id}-repeat`, type: "password", autoComplete: "new-password", "aria-invalid": mismatch ? true : undefined, value: repeat, onChange: (e) => setRepeat(e.target.value) }), mismatch ? _jsx("p", { className: "text-xs text-danger", children: t('people.mismatch') }) : null] })] })) : null] }), _jsxs("div", { className: "grid gap-2", children: [_jsx("h3", { className: "text-sm font-semibold", children: t('people.roles') }), _jsx(RolesEditor, { catalogue: catalogue, value: assignments, onChange: setAssignments })] }), invite.error === null ? null : _jsx(Alert, { tone: "danger", children: message(invite.error) })] })) }));
53
67
  }
54
68
  /** What a person can do, computed by the server as them (administrators only). */
55
69
  function Effective({ api, personId }) {
@@ -7,6 +7,7 @@ import { type Registry } from '../../ui/index.js';
7
7
  import type { i18n } from 'i18next';
8
8
  import { messages } from './messages.js';
9
9
  export { ApiKeysPage } from './ApiKeysPage.js';
10
+ export { AppSettingsPage } from './AppSettingsPage.js';
10
11
  export { AppsPage } from './AppsPage.js';
11
12
  export { AuditPage } from './AuditPage.js';
12
13
  export { CompaniesPage } from './CompaniesPage.js';
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { addMessages } from '../../ui/index.js';
7
7
  import { ApiKeysPage } from './ApiKeysPage.js';
8
+ import { AppSettingsPage } from './AppSettingsPage.js';
8
9
  import { AppsPage } from './AppsPage.js';
9
10
  import { AuditPage } from './AuditPage.js';
10
11
  import { CompaniesPage } from './CompaniesPage.js';
@@ -17,6 +18,7 @@ import { ProfilePage } from './ProfilePage.js';
17
18
  import { RolesPage } from './RolesPage.js';
18
19
  import { SecurityPage } from './SecurityPage.js';
19
20
  export { ApiKeysPage } from './ApiKeysPage.js';
21
+ export { AppSettingsPage } from './AppSettingsPage.js';
20
22
  export { AppsPage } from './AppsPage.js';
21
23
  export { AuditPage } from './AuditPage.js';
22
24
  export { CompaniesPage } from './CompaniesPage.js';
@@ -32,6 +34,7 @@ export { SecurityPage } from './SecurityPage.js';
32
34
  export function registerSettingsWeb(registry, i18n) {
33
35
  registry.registerPage('settings.Overview', OverviewPage);
34
36
  registry.registerPage('settings.Apps', AppsPage);
37
+ registry.registerPage('settings.AppSettings', AppSettingsPage);
35
38
  registry.registerPage('settings.People', PeoplePage);
36
39
  registry.registerPage('settings.Roles', RolesPage);
37
40
  registry.registerPage('settings.Company', CompanyPage);
@@ -1,6 +1,45 @@
1
1
  /** Messages of the Settings app (namespace `settings`). */
2
2
  export const messages = {
3
3
  en: {
4
+ appSettings: {
5
+ title: 'App settings',
6
+ intro: 'Each app’s own settings, in one place. Who may change them is set by the app; every change is in the audit log.',
7
+ apps: 'Apps',
8
+ none: 'No installed app you may open has settings yet.',
9
+ perCompany: 'For this company',
10
+ everywhere: 'For every company',
11
+ changed: 'Changed',
12
+ reset: 'Reset',
13
+ resetOne: 'Reset {{setting}} to its default',
14
+ defaultIs: 'Default: {{value}}',
15
+ save: 'Save',
16
+ discard: 'Discard',
17
+ saved: 'Saved.',
18
+ readOnly: 'You may see these settings; changing them needs another role.',
19
+ notSet: 'Not set',
20
+ history: 'History',
21
+ historyOf: 'History of {{setting}}',
22
+ neverChanged: 'Never changed: the default applies.',
23
+ system: 'System',
24
+ how: { changed: 'changed', copied: 'copied from another company', imported: 'imported' },
25
+ copyTo: 'Copy to another company…',
26
+ copyTitle: 'Copy settings to another company',
27
+ copyIntro: 'This company’s settings for each app, written to the company you choose. What would change is shown first; nothing is written until you confirm.',
28
+ toCompany: 'To company',
29
+ chooseCompany: 'Choose a company',
30
+ copyNow: 'Copy the settings',
31
+ copied: 'Settings copied ({{count}} sections changed).',
32
+ nothingToChange: 'Nothing would change: the settings are the same already.',
33
+ export: 'Export',
34
+ import: 'Import…',
35
+ importFile: 'Settings file to import',
36
+ importTitle: 'Import settings',
37
+ importIntro: 'A settings file exported from Shutro ERP, checked against the installed apps. What would change is shown first; unknown settings are skipped.',
38
+ importNow: 'Import the settings',
39
+ imported: 'Settings imported ({{count}} sections changed).',
40
+ skipped: 'Skipped (not in this installation): {{what}}',
41
+ notJson: 'That file is not a settings export.',
42
+ },
4
43
  connected: {
5
44
  title: 'Connected apps',
6
45
  intro: 'Apps you allowed to act for you. They can never do more than you may; remove one and its access ends at once.',
@@ -73,9 +112,26 @@ export const messages = {
73
112
  people: {
74
113
  title: 'People',
75
114
  intro: 'Who works here, what they can open, and how they sign in.',
76
- invite: 'Invite',
115
+ invite: 'Add or invite',
77
116
  inviteTitle: 'Invite someone',
78
117
  inviteHint: 'They get a link to choose their password; it works for 7 days.',
118
+ addTitle: 'Add someone',
119
+ addHint: 'You set their password and pass it on; they can change it in their profile.',
120
+ add: 'Add the person',
121
+ added: '{{login}} can sign in now with the password you set. Pass it on safely.',
122
+ how: 'How they get in',
123
+ mode: {
124
+ invite: 'Send an invitation',
125
+ inviteHint: 'An email with a link; they choose their own password.',
126
+ password: 'Add with a password',
127
+ passwordHint: 'They can sign in at once; no email needed.',
128
+ },
129
+ emailOptional: 'Email (optional)',
130
+ login: 'Login',
131
+ password: 'Password',
132
+ passwordHint: 'At least 12 characters.',
133
+ repeat: 'Repeat the password',
134
+ mismatch: 'The passwords differ.',
79
135
  send: 'Send the invitation',
80
136
  done: 'Done',
81
137
  emailed: 'The invitation is on its way to {{to}}. You can also pass on the link:',
@@ -140,6 +196,10 @@ export const messages = {
140
196
  description: 'Sign-ins, permission changes, exports and sensitive actions; filtered and exported.',
141
197
  keywords: 'audit log history events who did what logins export csv',
142
198
  },
199
+ 'settings.action_app_settings': {
200
+ description: 'Each app’s own settings: quotations, purchase orders, fiscal year and lock dates, and more.',
201
+ keywords: 'app settings configuration defaults',
202
+ },
143
203
  'settings.action_companies': {
144
204
  description: 'Every company, its currency and people; switch between them or add one.',
145
205
  keywords: 'companies multi-company branches new company switch',
@@ -384,6 +444,45 @@ export const messages = {
384
444
  version: 'Version {{version}}',
385
445
  },
386
446
  bn: {
447
+ appSettings: {
448
+ title: 'অ্যাপ সেটিংস',
449
+ intro: 'প্রতিটি অ্যাপের নিজস্ব সেটিংস, এক জায়গায়। কে বদলাতে পারবেন তা অ্যাপ ঠিক করে; প্রতিটি পরিবর্তন অডিট লগে থাকে।',
450
+ apps: 'অ্যাপস',
451
+ none: 'আপনি খুলতে পারেন এমন কোনো অ্যাপের এখনো সেটিংস নেই।',
452
+ perCompany: 'এই প্রতিষ্ঠানের জন্য',
453
+ everywhere: 'সব প্রতিষ্ঠানের জন্য',
454
+ changed: 'পরিবর্তিত',
455
+ reset: 'ডিফল্টে ফিরিয়ে দিন',
456
+ resetOne: '{{setting}} ডিফল্টে ফিরিয়ে দিন',
457
+ defaultIs: 'ডিফল্ট: {{value}}',
458
+ save: 'সংরক্ষণ করুন',
459
+ discard: 'বাদ দিন',
460
+ saved: 'সংরক্ষিত।',
461
+ readOnly: 'আপনি এই সেটিংস দেখতে পারেন; বদলাতে অন্য রোল লাগে।',
462
+ notSet: 'নির্ধারিত নয়',
463
+ history: 'ইতিহাস',
464
+ historyOf: '{{setting}}-এর ইতিহাস',
465
+ neverChanged: 'কখনো বদলানো হয়নি: ডিফল্ট প্রযোজ্য।',
466
+ system: 'সিস্টেম',
467
+ how: { changed: 'বদলানো হয়েছে', copied: 'অন্য প্রতিষ্ঠান থেকে কপি', imported: 'ইমপোর্ট' },
468
+ copyTo: 'অন্য প্রতিষ্ঠানে কপি করুন…',
469
+ copyTitle: 'সেটিংস অন্য প্রতিষ্ঠানে কপি করুন',
470
+ copyIntro: 'এই প্রতিষ্ঠানের প্রতিটি অ্যাপের সেটিংস আপনার বেছে নেওয়া প্রতিষ্ঠানে লেখা হবে। কী বদলাবে তা আগে দেখানো হয়; নিশ্চিত না করা পর্যন্ত কিছু লেখা হয় না।',
471
+ toCompany: 'যে প্রতিষ্ঠানে',
472
+ chooseCompany: 'প্রতিষ্ঠান বেছে নিন',
473
+ copyNow: 'সেটিংস কপি করুন',
474
+ copied: 'সেটিংস কপি হয়েছে ({{count}}টি অংশ বদলেছে)।',
475
+ nothingToChange: 'কিছু বদলাবে না: সেটিংস আগে থেকেই এক।',
476
+ export: 'এক্সপোর্ট',
477
+ import: 'ইমপোর্ট…',
478
+ importFile: 'ইমপোর্টের সেটিংস ফাইল',
479
+ importTitle: 'সেটিংস ইমপোর্ট',
480
+ importIntro: 'Shutro ERP থেকে এক্সপোর্ট করা সেটিংস ফাইল, ইনস্টল করা অ্যাপের সাথে মিলিয়ে দেখা। কী বদলাবে তা আগে দেখানো হয়; অজানা সেটিংস বাদ যায়।',
481
+ importNow: 'সেটিংস ইমপোর্ট করুন',
482
+ imported: 'সেটিংস ইমপোর্ট হয়েছে ({{count}}টি অংশ বদলেছে)।',
483
+ skipped: 'বাদ গেছে (এই ইনস্টলেশনে নেই): {{what}}',
484
+ notJson: 'ফাইলটি সেটিংস এক্সপোর্ট নয়।',
485
+ },
387
486
  connected: {
388
487
  title: 'সংযুক্ত অ্যাপস',
389
488
  intro: 'যে অ্যাপগুলোকে আপনি আপনার হয়ে কাজ করতে দিয়েছেন। সরিয়ে দিলে সাথে সাথে প্রবেশাধিকার শেষ।',
@@ -456,9 +555,26 @@ export const messages = {
456
555
  people: {
457
556
  title: 'মানুষ',
458
557
  intro: 'এখানে কারা কাজ করেন, কী খুলতে পারেন এবং কীভাবে সাইন ইন করেন।',
459
- invite: 'আমন্ত্রণ',
558
+ invite: 'যোগ বা আমন্ত্রণ',
460
559
  inviteTitle: 'কাউকে আমন্ত্রণ জানান',
461
560
  inviteHint: 'তাঁরা পাসওয়ার্ড বেছে নেওয়ার একটি লিংক পাবেন; এটি ৭ দিন কাজ করে।',
561
+ addTitle: 'কাউকে যোগ করুন',
562
+ addHint: 'আপনি পাসওয়ার্ড ঠিক করে দিন; তাঁরা প্রোফাইলে বদলাতে পারেন।',
563
+ add: 'যোগ করুন',
564
+ added: '{{login}} এখনই আপনার ঠিক করা পাসওয়ার্ড দিয়ে সাইন ইন করতে পারেন। নিরাপদে জানিয়ে দিন।',
565
+ how: 'কীভাবে প্রবেশ করবেন',
566
+ mode: {
567
+ invite: 'আমন্ত্রণ পাঠান',
568
+ inviteHint: 'একটি ইমেইলে লিংক; তাঁরা নিজের পাসওয়ার্ড বেছে নেন।',
569
+ password: 'পাসওয়ার্ড দিয়ে যোগ করুন',
570
+ passwordHint: 'সাথে সাথে সাইন ইন করতে পারেন; ইমেইল লাগে না।',
571
+ },
572
+ emailOptional: 'ইমেইল (ঐচ্ছিক)',
573
+ login: 'লগইন',
574
+ password: 'পাসওয়ার্ড',
575
+ passwordHint: 'অন্তত ১২ অক্ষর।',
576
+ repeat: 'পাসওয়ার্ড আবার দিন',
577
+ mismatch: 'পাসওয়ার্ড মিলছে না।',
462
578
  send: 'আমন্ত্রণ পাঠান',
463
579
  done: 'সম্পন্ন',
464
580
  emailed: 'আমন্ত্রণ {{to}}-এ যাচ্ছে। লিংকটি আপনিও পাঠাতে পারেন:',
@@ -0,0 +1,22 @@
1
+ export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
2
+ import { type DefinitionHelpers } from '../../server/orm.js';
3
+ import type { DefaultLoaded, Env as CoreEnv, RecordSet as CoreRecordSet, Shutro as CoreShutro } from '../../server/orm.js';
4
+ import type { SchemaView } from './schema.js';
5
+ export type { SchemaView };
6
+ /** Every model visible in this view. */
7
+ export type ModelName = keyof SchemaView & string;
8
+ /** `Env` typed to this view. */
9
+ export type Env = CoreEnv<SchemaView>;
10
+ /** `Shutro` typed to this view. */
11
+ export type Shutro = CoreShutro<SchemaView>;
12
+ /** A recordset of `M`, with the default loaded shape unless `L` is given. */
13
+ export type Records<M extends ModelName, L = DefaultLoaded<SchemaView, M>> = CoreRecordSet<SchemaView, M, L>;
14
+ export declare const compute: DefinitionHelpers<SchemaView>['compute'];
15
+ export declare const check: DefinitionHelpers<SchemaView>['check'];
16
+ export declare const onchange: DefinitionHelpers<SchemaView>['onchange'];
17
+ export declare const records: DefinitionHelpers<SchemaView>['records'];
18
+ export declare const method: DefinitionHelpers<SchemaView>['method'];
19
+ /** Module data (RFC 0017): records, overrides of dependencies' records, and references, typed like creates. */
20
+ export declare const record: DefinitionHelpers<SchemaView>['record'];
21
+ export declare const override: DefinitionHelpers<SchemaView>['override'];
22
+ export declare const ref: DefinitionHelpers<SchemaView>['ref'];
@@ -0,0 +1,15 @@
1
+ // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 8b53af7578c6a801
2
+ // Bindings of module `stock`: import the definition API from here to get types scoped to what it can see.
3
+ export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
4
+ import { definitions } from '../../server/orm.js';
5
+ /** Definition callbacks typed against this module's view: no casts in computes, checks, onchange, methods. */
6
+ const helpers = definitions();
7
+ export const compute = helpers.compute;
8
+ export const check = helpers.check;
9
+ export const onchange = helpers.onchange;
10
+ export const records = helpers.records;
11
+ export const method = helpers.method;
12
+ /** Module data (RFC 0017): records, overrides of dependencies' records, and references, typed like creates. */
13
+ export const record = helpers.record;
14
+ export const override = helpers.override;
15
+ export const ref = helpers.ref;