@ketvietlab/ketsuite 0.1.3 → 0.1.6

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 (359) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/deployment.d.ts.map +1 -1
  3. package/dist/deployment.js +3 -0
  4. package/dist/deployment.js.map +1 -1
  5. package/dist/index.d.ts +8 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +7 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/modules/account_staff_channel/channel-routes.d.ts.map +1 -1
  10. package/dist/modules/account_staff_channel/channel-routes.js +1 -12
  11. package/dist/modules/account_staff_channel/channel-routes.js.map +1 -1
  12. package/dist/modules/attendance/channel-routes.d.ts.map +1 -1
  13. package/dist/modules/attendance/channel-routes.js +1 -12
  14. package/dist/modules/attendance/channel-routes.js.map +1 -1
  15. package/dist/modules/backend/design/HANDOFF.md +2 -2
  16. package/dist/modules/backend/design/auth.css +12 -0
  17. package/dist/modules/backend/design/client/table-selection.d.mts +3 -3
  18. package/dist/modules/backend/design/client/table-selection.d.mts.map +1 -1
  19. package/dist/modules/backend/design/client/table-selection.mjs +1 -1
  20. package/dist/modules/backend/design/client/table-selection.mjs.map +4 -4
  21. package/dist/modules/backend/design/content.css +43 -0
  22. package/dist/modules/backend/design/design-system.css +1 -1
  23. package/dist/modules/backend/design/forms.css +84 -1
  24. package/dist/modules/backend/design/lists.css +26 -18
  25. package/dist/modules/backend/design/record.css +5 -2
  26. package/dist/modules/backend/design/responsive.css +5 -6
  27. package/dist/modules/backend/design/user-workflow.css +257 -0
  28. package/dist/modules/backend/forms.d.ts +38 -1
  29. package/dist/modules/backend/forms.d.ts.map +1 -1
  30. package/dist/modules/backend/forms.js +29 -0
  31. package/dist/modules/backend/forms.js.map +1 -1
  32. package/dist/modules/backend/index.d.ts +3 -2
  33. package/dist/modules/backend/index.d.ts.map +1 -1
  34. package/dist/modules/backend/index.js +3 -2
  35. package/dist/modules/backend/index.js.map +1 -1
  36. package/dist/modules/backend/messages.d.ts.map +1 -1
  37. package/dist/modules/backend/messages.js +42 -2
  38. package/dist/modules/backend/messages.js.map +1 -1
  39. package/dist/modules/channel_api/core.d.ts +30 -0
  40. package/dist/modules/channel_api/core.d.ts.map +1 -1
  41. package/dist/modules/channel_api/core.js +28 -0
  42. package/dist/modules/channel_api/core.js.map +1 -1
  43. package/dist/modules/crm/functions.d.ts +5 -0
  44. package/dist/modules/crm/functions.d.ts.map +1 -1
  45. package/dist/modules/crm/functions.js +105 -10
  46. package/dist/modules/crm/functions.js.map +1 -1
  47. package/dist/modules/crm/jobs.d.ts.map +1 -1
  48. package/dist/modules/crm/jobs.js +1 -0
  49. package/dist/modules/crm/jobs.js.map +1 -1
  50. package/dist/modules/crm/operations.d.ts.map +1 -1
  51. package/dist/modules/crm/operations.js +2 -1
  52. package/dist/modules/crm/operations.js.map +1 -1
  53. package/dist/modules/crm_backend/index.d.ts.map +1 -1
  54. package/dist/modules/crm_backend/index.js +5 -2
  55. package/dist/modules/crm_backend/index.js.map +1 -1
  56. package/dist/modules/crm_backend/messages.d.ts +116 -0
  57. package/dist/modules/crm_backend/messages.d.ts.map +1 -1
  58. package/dist/modules/crm_backend/messages.js +122 -6
  59. package/dist/modules/crm_backend/messages.js.map +1 -1
  60. package/dist/modules/crm_backend/routes.d.ts.map +1 -1
  61. package/dist/modules/crm_backend/routes.js +434 -174
  62. package/dist/modules/crm_backend/routes.js.map +1 -1
  63. package/dist/modules/crm_backend/screens/case-create.d.ts +7 -0
  64. package/dist/modules/crm_backend/screens/case-create.d.ts.map +1 -1
  65. package/dist/modules/crm_backend/screens/case-create.js +13 -4
  66. package/dist/modules/crm_backend/screens/case-create.js.map +1 -1
  67. package/dist/modules/crm_backend/screens/case-detail.d.ts +6 -0
  68. package/dist/modules/crm_backend/screens/case-detail.d.ts.map +1 -1
  69. package/dist/modules/crm_backend/screens/case-detail.js +73 -23
  70. package/dist/modules/crm_backend/screens/case-detail.js.map +1 -1
  71. package/dist/modules/crm_backend/screens/configuration.d.ts +21 -3
  72. package/dist/modules/crm_backend/screens/configuration.d.ts.map +1 -1
  73. package/dist/modules/crm_backend/screens/configuration.js +260 -55
  74. package/dist/modules/crm_backend/screens/configuration.js.map +1 -1
  75. package/dist/modules/crm_backend/screens/index.d.ts +3 -3
  76. package/dist/modules/crm_backend/screens/index.d.ts.map +1 -1
  77. package/dist/modules/crm_backend/screens/index.js +2 -2
  78. package/dist/modules/crm_backend/screens/index.js.map +1 -1
  79. package/dist/modules/crm_staff_channel/channel-routes.d.ts.map +1 -1
  80. package/dist/modules/crm_staff_channel/channel-routes.js +1 -12
  81. package/dist/modules/crm_staff_channel/channel-routes.js.map +1 -1
  82. package/dist/modules/flow/membership.d.ts.map +1 -1
  83. package/dist/modules/flow/membership.js +7 -1
  84. package/dist/modules/flow/membership.js.map +1 -1
  85. package/dist/modules/flow/operations.d.ts +10 -4
  86. package/dist/modules/flow/operations.d.ts.map +1 -1
  87. package/dist/modules/flow/operations.js +10 -4
  88. package/dist/modules/flow/operations.js.map +1 -1
  89. package/dist/modules/flow_staff_channel/channel-routes.d.ts.map +1 -1
  90. package/dist/modules/flow_staff_channel/channel-routes.js +1 -20
  91. package/dist/modules/flow_staff_channel/channel-routes.js.map +1 -1
  92. package/dist/modules/hospitality_core/messages.d.ts.map +1 -1
  93. package/dist/modules/hospitality_core/messages.js +44 -0
  94. package/dist/modules/hospitality_core/messages.js.map +1 -1
  95. package/dist/modules/hospitality_core/night-audit.d.ts.map +1 -1
  96. package/dist/modules/hospitality_core/night-audit.js +7 -0
  97. package/dist/modules/hospitality_core/night-audit.js.map +1 -1
  98. package/dist/modules/hospitality_core/operations.d.ts.map +1 -1
  99. package/dist/modules/hospitality_core/operations.js +150 -40
  100. package/dist/modules/hospitality_core/operations.js.map +1 -1
  101. package/dist/modules/hospitality_core/routes.d.ts.map +1 -1
  102. package/dist/modules/hospitality_core/routes.js +24 -2
  103. package/dist/modules/hospitality_core/routes.js.map +1 -1
  104. package/dist/modules/hospitality_core/screens/check-out-prep.d.ts +46 -0
  105. package/dist/modules/hospitality_core/screens/check-out-prep.d.ts.map +1 -0
  106. package/dist/modules/hospitality_core/screens/check-out-prep.js +133 -0
  107. package/dist/modules/hospitality_core/screens/check-out-prep.js.map +1 -0
  108. package/dist/modules/hospitality_core/screens/folio-detail.d.ts.map +1 -1
  109. package/dist/modules/hospitality_core/screens/folio-detail.js +2 -4
  110. package/dist/modules/hospitality_core/screens/folio-detail.js.map +1 -1
  111. package/dist/modules/hospitality_core/screens/index.d.ts +2 -0
  112. package/dist/modules/hospitality_core/screens/index.d.ts.map +1 -1
  113. package/dist/modules/hospitality_core/screens/index.js +1 -0
  114. package/dist/modules/hospitality_core/screens/index.js.map +1 -1
  115. package/dist/modules/hospitality_core/screens/reservation-detail.d.ts.map +1 -1
  116. package/dist/modules/hospitality_core/screens/reservation-detail.js +8 -1
  117. package/dist/modules/hospitality_core/screens/reservation-detail.js.map +1 -1
  118. package/dist/modules/hospitality_core/screens/shared.d.ts +12 -0
  119. package/dist/modules/hospitality_core/screens/shared.d.ts.map +1 -1
  120. package/dist/modules/hospitality_core/screens/shared.js +25 -4
  121. package/dist/modules/hospitality_core/screens/shared.js.map +1 -1
  122. package/dist/modules/hospitality_core/services.d.ts +48 -0
  123. package/dist/modules/hospitality_core/services.d.ts.map +1 -1
  124. package/dist/modules/hospitality_core/services.js +70 -0
  125. package/dist/modules/hospitality_core/services.js.map +1 -1
  126. package/dist/modules/hospitality_staff_channel/channel-routes.d.ts.map +1 -1
  127. package/dist/modules/hospitality_staff_channel/channel-routes.js +1 -12
  128. package/dist/modules/hospitality_staff_channel/channel-routes.js.map +1 -1
  129. package/dist/modules/inventory_staff_channel/channel-routes.d.ts.map +1 -1
  130. package/dist/modules/inventory_staff_channel/channel-routes.js +1 -12
  131. package/dist/modules/inventory_staff_channel/channel-routes.js.map +1 -1
  132. package/dist/modules/loyalty/admin-functions.d.ts +2 -0
  133. package/dist/modules/loyalty/admin-functions.d.ts.map +1 -1
  134. package/dist/modules/loyalty/admin-functions.js +96 -20
  135. package/dist/modules/loyalty/admin-functions.js.map +1 -1
  136. package/dist/modules/loyalty/engine.d.ts +4 -1
  137. package/dist/modules/loyalty/engine.d.ts.map +1 -1
  138. package/dist/modules/loyalty/engine.js +125 -18
  139. package/dist/modules/loyalty/engine.js.map +1 -1
  140. package/dist/modules/loyalty/index.d.ts.map +1 -1
  141. package/dist/modules/loyalty/index.js +4 -3
  142. package/dist/modules/loyalty/index.js.map +1 -1
  143. package/dist/modules/loyalty/jobs.d.ts.map +1 -1
  144. package/dist/modules/loyalty/jobs.js +0 -1
  145. package/dist/modules/loyalty/jobs.js.map +1 -1
  146. package/dist/modules/loyalty/membership-functions.d.ts +5 -0
  147. package/dist/modules/loyalty/membership-functions.d.ts.map +1 -1
  148. package/dist/modules/loyalty/membership-functions.js +37 -23
  149. package/dist/modules/loyalty/membership-functions.js.map +1 -1
  150. package/dist/modules/loyalty/messages.d.ts +14 -0
  151. package/dist/modules/loyalty/messages.d.ts.map +1 -1
  152. package/dist/modules/loyalty/messages.js +14 -0
  153. package/dist/modules/loyalty/messages.js.map +1 -1
  154. package/dist/modules/loyalty/models.d.ts.map +1 -1
  155. package/dist/modules/loyalty/models.js +17 -0
  156. package/dist/modules/loyalty/models.js.map +1 -1
  157. package/dist/modules/loyalty/order-functions.d.ts.map +1 -1
  158. package/dist/modules/loyalty/order-functions.js +70 -17
  159. package/dist/modules/loyalty/order-functions.js.map +1 -1
  160. package/dist/modules/loyalty/program-design.d.ts +11 -0
  161. package/dist/modules/loyalty/program-design.d.ts.map +1 -0
  162. package/dist/modules/loyalty/program-design.js +356 -0
  163. package/dist/modules/loyalty/program-design.js.map +1 -0
  164. package/dist/modules/loyalty/stats.d.ts +0 -13
  165. package/dist/modules/loyalty/stats.d.ts.map +1 -1
  166. package/dist/modules/loyalty/stats.js +11 -6
  167. package/dist/modules/loyalty/stats.js.map +1 -1
  168. package/dist/modules/loyalty/stored-value.d.ts.map +1 -1
  169. package/dist/modules/loyalty/stored-value.js +7 -2
  170. package/dist/modules/loyalty/stored-value.js.map +1 -1
  171. package/dist/modules/loyalty/types.d.ts +1 -1
  172. package/dist/modules/loyalty/types.d.ts.map +1 -1
  173. package/dist/modules/loyalty_backend/design-messages.d.ts +221 -0
  174. package/dist/modules/loyalty_backend/design-messages.d.ts.map +1 -0
  175. package/dist/modules/loyalty_backend/design-messages.js +221 -0
  176. package/dist/modules/loyalty_backend/design-messages.js.map +1 -0
  177. package/dist/modules/loyalty_backend/index.js +224 -412
  178. package/dist/modules/loyalty_backend/index.js.map +1 -1
  179. package/dist/modules/loyalty_backend/messages.d.ts +642 -214
  180. package/dist/modules/loyalty_backend/messages.d.ts.map +1 -1
  181. package/dist/modules/loyalty_backend/messages.js +19 -13
  182. package/dist/modules/loyalty_backend/messages.js.map +1 -1
  183. package/dist/modules/loyalty_backend/program-workspace.d.ts +4 -0
  184. package/dist/modules/loyalty_backend/program-workspace.d.ts.map +1 -0
  185. package/dist/modules/loyalty_backend/program-workspace.js +600 -0
  186. package/dist/modules/loyalty_backend/program-workspace.js.map +1 -0
  187. package/dist/modules/loyalty_backend/screens/index.d.ts +12 -36
  188. package/dist/modules/loyalty_backend/screens/index.d.ts.map +1 -1
  189. package/dist/modules/loyalty_backend/screens/index.js +148 -292
  190. package/dist/modules/loyalty_backend/screens/index.js.map +1 -1
  191. package/dist/modules/loyalty_backend/screens/page-frame.d.ts.map +1 -1
  192. package/dist/modules/loyalty_backend/screens/page-frame.js +2 -2
  193. package/dist/modules/loyalty_backend/screens/page-frame.js.map +1 -1
  194. package/dist/modules/loyalty_pos/functions.d.ts.map +1 -1
  195. package/dist/modules/loyalty_pos/functions.js +1 -0
  196. package/dist/modules/loyalty_pos/functions.js.map +1 -1
  197. package/dist/modules/loyalty_pos/index.js +1 -1
  198. package/dist/modules/loyalty_pos/index.js.map +1 -1
  199. package/dist/modules/loyalty_sale/functions.d.ts +1 -0
  200. package/dist/modules/loyalty_sale/functions.d.ts.map +1 -1
  201. package/dist/modules/loyalty_sale/functions.js +1 -0
  202. package/dist/modules/loyalty_sale/functions.js.map +1 -1
  203. package/dist/modules/partner_backend/client/partner.css +3 -1
  204. package/dist/modules/partner_backend/index.d.ts.map +1 -1
  205. package/dist/modules/partner_backend/index.js +4 -0
  206. package/dist/modules/partner_backend/index.js.map +1 -1
  207. package/dist/modules/partner_backend/routes.d.ts.map +1 -1
  208. package/dist/modules/partner_backend/routes.js +6 -1
  209. package/dist/modules/partner_backend/routes.js.map +1 -1
  210. package/dist/modules/partner_backend/screens/form.d.ts +1 -0
  211. package/dist/modules/partner_backend/screens/form.d.ts.map +1 -1
  212. package/dist/modules/partner_backend/screens/form.js +1 -0
  213. package/dist/modules/partner_backend/screens/form.js.map +1 -1
  214. package/dist/modules/purchase_staff_channel/order-routes.d.ts.map +1 -1
  215. package/dist/modules/purchase_staff_channel/order-routes.js +1 -12
  216. package/dist/modules/purchase_staff_channel/order-routes.js.map +1 -1
  217. package/dist/modules/quality_staff_channel/channel-routes.d.ts.map +1 -1
  218. package/dist/modules/quality_staff_channel/channel-routes.js +1 -12
  219. package/dist/modules/quality_staff_channel/channel-routes.js.map +1 -1
  220. package/dist/modules/sale/functions.d.ts.map +1 -1
  221. package/dist/modules/sale/functions.js +27 -17
  222. package/dist/modules/sale/functions.js.map +1 -1
  223. package/dist/modules/sale_staff_channel/channel-routes.d.ts.map +1 -1
  224. package/dist/modules/sale_staff_channel/channel-routes.js +1 -12
  225. package/dist/modules/sale_staff_channel/channel-routes.js.map +1 -1
  226. package/dist/modules/sale_staff_channel/order-routes.d.ts.map +1 -1
  227. package/dist/modules/sale_staff_channel/order-routes.js +1 -12
  228. package/dist/modules/sale_staff_channel/order-routes.js.map +1 -1
  229. package/dist/modules/stock_staff_channel/operation-routes.d.ts.map +1 -1
  230. package/dist/modules/stock_staff_channel/operation-routes.js +1 -12
  231. package/dist/modules/stock_staff_channel/operation-routes.js.map +1 -1
  232. package/dist/modules/user/authorization.d.ts +64 -1
  233. package/dist/modules/user/authorization.d.ts.map +1 -1
  234. package/dist/modules/user/authorization.js +445 -53
  235. package/dist/modules/user/authorization.js.map +1 -1
  236. package/dist/modules/user/functions.d.ts.map +1 -1
  237. package/dist/modules/user/functions.js +45 -23
  238. package/dist/modules/user/functions.js.map +1 -1
  239. package/dist/modules/user/index.d.ts +2 -1
  240. package/dist/modules/user/index.d.ts.map +1 -1
  241. package/dist/modules/user/index.js +1 -1
  242. package/dist/modules/user/index.js.map +1 -1
  243. package/dist/modules/user/models.d.ts.map +1 -1
  244. package/dist/modules/user/models.js +5 -0
  245. package/dist/modules/user/models.js.map +1 -1
  246. package/dist/modules/user/roles.d.ts.map +1 -1
  247. package/dist/modules/user/roles.js +2 -1
  248. package/dist/modules/user/roles.js.map +1 -1
  249. package/dist/modules/user_backend/access-routes.d.ts +31 -0
  250. package/dist/modules/user_backend/access-routes.d.ts.map +1 -0
  251. package/dist/modules/user_backend/access-routes.js +447 -0
  252. package/dist/modules/user_backend/access-routes.js.map +1 -0
  253. package/dist/modules/user_backend/index.d.ts +1 -0
  254. package/dist/modules/user_backend/index.d.ts.map +1 -1
  255. package/dist/modules/user_backend/index.js +1 -0
  256. package/dist/modules/user_backend/index.js.map +1 -1
  257. package/dist/modules/user_backend/messages.d.ts +4 -0
  258. package/dist/modules/user_backend/messages.d.ts.map +1 -1
  259. package/dist/modules/user_backend/messages.js +4 -0
  260. package/dist/modules/user_backend/messages.js.map +1 -1
  261. package/dist/modules/user_backend/routes.d.ts.map +1 -1
  262. package/dist/modules/user_backend/routes.js +38 -3
  263. package/dist/modules/user_backend/routes.js.map +1 -1
  264. package/dist/modules/user_backend/screens/user-form.d.ts +1 -0
  265. package/dist/modules/user_backend/screens/user-form.d.ts.map +1 -1
  266. package/dist/modules/user_backend/screens/user-form.js +28 -0
  267. package/dist/modules/user_backend/screens/user-form.js.map +1 -1
  268. package/dist/modules/website/cms.d.ts.map +1 -1
  269. package/dist/modules/website/cms.js +283 -6
  270. package/dist/modules/website/cms.js.map +1 -1
  271. package/dist/modules/website/index.d.ts.map +1 -1
  272. package/dist/modules/website/index.js +12 -0
  273. package/dist/modules/website/index.js.map +1 -1
  274. package/dist/modules/website/media-usage.d.ts +38 -0
  275. package/dist/modules/website/media-usage.d.ts.map +1 -0
  276. package/dist/modules/website/media-usage.js +87 -0
  277. package/dist/modules/website/media-usage.js.map +1 -0
  278. package/dist/modules/website/paths.js +1 -1
  279. package/dist/modules/website/paths.js.map +1 -1
  280. package/dist/modules/website/sections.d.ts.map +1 -1
  281. package/dist/modules/website/sections.js +12 -1
  282. package/dist/modules/website/sections.js.map +1 -1
  283. package/dist/modules/website_backend/index.js +138 -0
  284. package/dist/modules/website_backend/index.js.map +1 -1
  285. package/dist/modules/website_backend/menus.d.ts.map +1 -1
  286. package/dist/modules/website_backend/menus.js +11 -0
  287. package/dist/modules/website_backend/menus.js.map +1 -1
  288. package/dist/modules/website_backend/routes.d.ts.map +1 -1
  289. package/dist/modules/website_backend/routes.js +414 -31
  290. package/dist/modules/website_backend/routes.js.map +1 -1
  291. package/dist/modules/website_backend/screens/index.d.ts +98 -6
  292. package/dist/modules/website_backend/screens/index.d.ts.map +1 -1
  293. package/dist/modules/website_backend/screens/index.js +600 -154
  294. package/dist/modules/website_backend/screens/index.js.map +1 -1
  295. package/dist/modules/website_backend/screens/page-frame.d.ts +3 -1
  296. package/dist/modules/website_backend/screens/page-frame.d.ts.map +1 -1
  297. package/dist/modules/website_backend/screens/page-frame.js +1 -1
  298. package/dist/modules/website_backend/screens/page-frame.js.map +1 -1
  299. package/dist/modules/website_menu/functions.d.ts.map +1 -1
  300. package/dist/modules/website_menu/functions.js +51 -0
  301. package/dist/modules/website_menu/functions.js.map +1 -1
  302. package/dist/modules/website_menu/index.js +2 -0
  303. package/dist/modules/website_menu/index.js.map +1 -1
  304. package/dist/modules/website_search/functions.d.ts.map +1 -1
  305. package/dist/modules/website_search/functions.js +10 -120
  306. package/dist/modules/website_search/functions.js.map +1 -1
  307. package/dist/modules/website_search/index.js +2 -0
  308. package/dist/modules/website_search/index.js.map +1 -1
  309. package/dist/modules/website_search/jobs.d.ts +3 -0
  310. package/dist/modules/website_search/jobs.d.ts.map +1 -0
  311. package/dist/modules/website_search/jobs.js +101 -0
  312. package/dist/modules/website_search/jobs.js.map +1 -0
  313. package/dist/modules/website_search/rebuild.d.ts +33 -0
  314. package/dist/modules/website_search/rebuild.d.ts.map +1 -0
  315. package/dist/modules/website_search/rebuild.js +125 -0
  316. package/dist/modules/website_search/rebuild.js.map +1 -0
  317. package/dist/permission-catalogue.d.ts.map +1 -1
  318. package/dist/permission-catalogue.js +21 -1
  319. package/dist/permission-catalogue.js.map +1 -1
  320. package/dist/scaffold/index.js +1 -1
  321. package/dist/ui/chrome.d.ts +8 -0
  322. package/dist/ui/chrome.d.ts.map +1 -1
  323. package/dist/ui/chrome.js +9 -1
  324. package/dist/ui/chrome.js.map +1 -1
  325. package/dist/ui/client/table-selection-view.d.ts.map +1 -1
  326. package/dist/ui/client/table-selection-view.js +161 -5
  327. package/dist/ui/client/table-selection-view.js.map +1 -1
  328. package/dist/ui/client/user-workflow.d.ts +2 -0
  329. package/dist/ui/client/user-workflow.d.ts.map +1 -0
  330. package/dist/ui/client/user-workflow.js +141 -0
  331. package/dist/ui/client/user-workflow.js.map +1 -0
  332. package/dist/ui/form.d.ts +9 -0
  333. package/dist/ui/form.d.ts.map +1 -1
  334. package/dist/ui/form.js +29 -15
  335. package/dist/ui/form.js.map +1 -1
  336. package/dist/ui/icons.d.ts.map +1 -1
  337. package/dist/ui/icons.js +2 -0
  338. package/dist/ui/icons.js.map +1 -1
  339. package/dist/ui/index.d.ts +7 -3
  340. package/dist/ui/index.d.ts.map +1 -1
  341. package/dist/ui/index.js +6 -3
  342. package/dist/ui/index.js.map +1 -1
  343. package/dist/ui/layout.d.ts.map +1 -1
  344. package/dist/ui/layout.js +14 -4
  345. package/dist/ui/layout.js.map +1 -1
  346. package/dist/ui/nav.d.ts.map +1 -1
  347. package/dist/ui/nav.js +2 -1
  348. package/dist/ui/nav.js.map +1 -1
  349. package/dist/ui/state.d.ts +20 -1
  350. package/dist/ui/state.d.ts.map +1 -1
  351. package/dist/ui/state.js +17 -0
  352. package/dist/ui/state.js.map +1 -1
  353. package/dist/ui/table.js +1 -1
  354. package/dist/ui/table.js.map +1 -1
  355. package/dist/ui/user-workflow.d.ts +72 -0
  356. package/dist/ui/user-workflow.d.ts.map +1 -0
  357. package/dist/ui/user-workflow.js +144 -0
  358. package/dist/ui/user-workflow.js.map +1 -0
  359. package/package.json +8 -4
@@ -1,10 +1,12 @@
1
+ import { newProgramRoute, programWorkspaceRoute } from './program-workspace.js';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { defineModule, text } from '@ketvietlab/ketjs';
3
- import { readForm, seeOther } from '../backend/forms.js';
4
+ import { formRefusal, readForm, seeOther } from '../backend/forms.js';
4
5
  import { PAGE_SIZE, pageOf, pager, searchOf, withParam } from '../backend/paging.js';
5
- import { DISCOUNT_APPLICABILITY, DISCOUNT_MODES, POINT_MODES, PROGRAM_APPLIES_ON, PROGRAM_TRIGGERS, LEDGER_OPERATIONS, PROGRAM_TYPES, REWARD_TYPES, TAX_MODES, } from '../loyalty/types.js';
6
+ import { LEDGER_OPERATIONS, PROGRAM_TYPES } from '../loyalty/types.js';
7
+ import { tierFormSchema } from '../loyalty/admin-functions.js';
6
8
  import { messages } from './messages.js';
7
- import { dashboardScreen, ledgerScreen, membershipsScreen, orderLoyaltyScreen, portalScreen, programDetailScreen, programsScreen, walletDetailScreen, walletsScreen, } from './screens/index.js';
9
+ import { dashboardScreen, ledgerScreen, tiersScreen, orderLoyaltyScreen, portalScreen, programsScreen, walletDetailScreen, walletsScreen, } from './screens/index.js';
8
10
  import { adminPage, choices, inLocale, optional } from '../backend/screen.js';
9
11
  /**
10
12
  * One filter dimension, as the chrome's dropdown wants it.
@@ -26,7 +28,10 @@ const filterMenu = (url, id, label, param, current, options) => ({
26
28
  /** The chips above the table saying which filters are on, each one removable. */
27
29
  const filterFacets = (url, active) => active
28
30
  .filter((entry) => Boolean(entry.label))
29
- .map((entry) => ({ label: entry.label, without: withParam(url, entry.param, null) }));
31
+ .map((entry) => ({
32
+ label: entry.label,
33
+ without: withParam(url, entry.param, null),
34
+ }));
30
35
  /**
31
36
  * A window over a ledger, chosen by name rather than by date.
32
37
  *
@@ -40,14 +45,20 @@ const periodWindow = (period) => {
40
45
  const at = new Date();
41
46
  const year = at.getUTCFullYear();
42
47
  if (period === 'year')
43
- return { from: new Date(Date.UTC(year, 0, 1)).toISOString(), to: at.toISOString() };
48
+ return {
49
+ from: new Date(Date.UTC(year, 0, 1)).toISOString(),
50
+ to: at.toISOString(),
51
+ };
44
52
  if (period === 'quarter')
45
53
  return {
46
54
  from: new Date(Date.UTC(year, Math.floor(at.getUTCMonth() / 3) * 3, 1)).toISOString(),
47
55
  to: at.toISOString(),
48
56
  };
49
57
  if (period === 'month')
50
- return { from: new Date(Date.UTC(year, at.getUTCMonth(), 1)).toISOString(), to: at.toISOString() };
58
+ return {
59
+ from: new Date(Date.UTC(year, at.getUTCMonth(), 1)).toISOString(),
60
+ to: at.toISOString(),
61
+ };
51
62
  return {};
52
63
  };
53
64
  const crossSite = (req) => {
@@ -61,9 +72,11 @@ const crossSite = (req) => {
61
72
  return true;
62
73
  }
63
74
  };
64
- const bool = (value) => value === '1' || value === 'true' || value === 'on';
65
75
  const resultErrors = (result, _) => (result?.errors ?? []).map((error) => error.code ? _(error.code, error.params) : String(error.message ?? _('loyalty_backend.error.invalid')));
66
- const options = (_, values, group) => values.map((value) => ({ value, label: _(`loyalty_backend.${group}.${value}`) }));
76
+ const options = (_, values, group) => values.map((value) => ({
77
+ value,
78
+ label: _(`loyalty_backend.${group}.${value}`),
79
+ }));
67
80
  const dataFor = async (ctx, url, req) => {
68
81
  const [programs, partners, templates] = await Promise.all([
69
82
  ctx.call('loyalty.program.list', { includeArchived: true }, url, req),
@@ -86,188 +99,6 @@ const createProgramFields = (_) => [
86
99
  required: true,
87
100
  },
88
101
  ];
89
- const programFields = (_, row) => [
90
- { name: 'name', label: _('loyalty_backend.field.name'), value: String(row.name ?? ''), required: true },
91
- {
92
- name: 'programType',
93
- label: _('loyalty_backend.field.programType'),
94
- type: 'select',
95
- value: String(row.programType ?? ''),
96
- options: options(_, PROGRAM_TYPES, 'programType'),
97
- required: true,
98
- },
99
- {
100
- name: 'sequence',
101
- label: _('loyalty_backend.field.sequence'),
102
- type: 'number',
103
- value: Number(row.sequence ?? 10),
104
- },
105
- {
106
- name: 'currency',
107
- label: _('loyalty_backend.field.currency'),
108
- value: String(row.currency ?? ''),
109
- required: true,
110
- },
111
- {
112
- name: 'dateFrom',
113
- label: _('loyalty_backend.field.dateFrom'),
114
- type: 'datetime-local',
115
- value: row.dateFrom ? String(row.dateFrom).slice(0, 16) : '',
116
- },
117
- {
118
- name: 'dateTo',
119
- label: _('loyalty_backend.field.dateTo'),
120
- type: 'datetime-local',
121
- value: row.dateTo ? String(row.dateTo).slice(0, 16) : '',
122
- },
123
- {
124
- name: 'limitUsage',
125
- label: _('loyalty_backend.field.limitUsage'),
126
- type: 'checkbox',
127
- value: Boolean(row.limitUsage),
128
- },
129
- {
130
- name: 'maxUsage',
131
- label: _('loyalty_backend.field.maxUsage'),
132
- type: 'number',
133
- value: row.maxUsage == null ? '' : Number(row.maxUsage),
134
- },
135
- {
136
- name: 'appliesOn',
137
- label: _('loyalty_backend.field.appliesOn'),
138
- type: 'select',
139
- value: String(row.appliesOn ?? ''),
140
- options: options(_, PROGRAM_APPLIES_ON, 'appliesOn'),
141
- },
142
- {
143
- name: 'trigger',
144
- label: _('loyalty_backend.field.trigger'),
145
- type: 'select',
146
- value: String(row.trigger ?? ''),
147
- options: options(_, PROGRAM_TRIGGERS, 'trigger'),
148
- },
149
- { name: 'pointName', label: _('loyalty_backend.field.pointName'), value: String(row.pointName ?? '') },
150
- {
151
- name: 'portalVisible',
152
- label: _('loyalty_backend.field.portalVisible'),
153
- type: 'checkbox',
154
- value: Boolean(row.portalVisible),
155
- },
156
- {
157
- name: 'availableSale',
158
- label: _('loyalty_backend.field.availableSale'),
159
- type: 'checkbox',
160
- value: Boolean(row.availableSale),
161
- },
162
- {
163
- name: 'availablePos',
164
- label: _('loyalty_backend.field.availablePos'),
165
- type: 'checkbox',
166
- value: Boolean(row.availablePos),
167
- },
168
- ];
169
- const programInput = (id, form) => ({
170
- id,
171
- name: form.name ?? '',
172
- programType: form.programType ?? '',
173
- sequence: Number(form.sequence || 10),
174
- ...optional(form, 'currency'),
175
- ...optional(form, 'dateFrom'),
176
- ...optional(form, 'dateTo'),
177
- limitUsage: bool(form.limitUsage),
178
- ...(form.maxUsage ? { maxUsage: Number(form.maxUsage) } : {}),
179
- ...optional(form, 'appliesOn'),
180
- ...optional(form, 'trigger'),
181
- ...optional(form, 'pointName'),
182
- portalVisible: bool(form.portalVisible),
183
- availableSale: bool(form.availableSale),
184
- availablePos: bool(form.availablePos),
185
- });
186
- const ruleFields = (_, products) => [
187
- { name: 'priority', label: _('loyalty_backend.field.priority'), type: 'number', value: 10 },
188
- {
189
- name: 'productId',
190
- label: _('loyalty_backend.field.rewardProduct'),
191
- type: 'select',
192
- options: choices(products, true),
193
- },
194
- {
195
- name: 'pointAmount',
196
- label: _('loyalty_backend.field.pointAmount'),
197
- type: 'decimal',
198
- value: 1,
199
- required: true,
200
- },
201
- {
202
- name: 'pointMode',
203
- label: _('loyalty_backend.field.pointMode'),
204
- type: 'select',
205
- options: options(_, POINT_MODES, 'pointMode'),
206
- required: true,
207
- },
208
- { name: 'pointSplit', label: _('loyalty_backend.field.pointSplit'), type: 'checkbox' },
209
- { name: 'minimumQuantity', label: _('loyalty_backend.field.minimumQuantity'), type: 'decimal', value: 1 },
210
- { name: 'minimumAmount', label: _('loyalty_backend.field.minimumAmount'), type: 'decimal', value: 0 },
211
- {
212
- name: 'taxMode',
213
- label: _('loyalty_backend.field.taxMode'),
214
- type: 'select',
215
- options: options(_, TAX_MODES, 'taxMode'),
216
- required: true,
217
- },
218
- {
219
- name: 'mode',
220
- label: _('loyalty_backend.field.mode'),
221
- type: 'select',
222
- options: options(_, PROGRAM_TRIGGERS, 'trigger'),
223
- required: true,
224
- },
225
- { name: 'code', label: _('loyalty_backend.field.code') },
226
- ];
227
- const rewardFields = (_, products) => [
228
- { name: 'description', label: _('loyalty_backend.field.description'), required: true },
229
- {
230
- name: 'rewardType',
231
- label: _('loyalty_backend.field.rewardType'),
232
- type: 'select',
233
- options: options(_, REWARD_TYPES, 'rewardType'),
234
- required: true,
235
- },
236
- { name: 'discount', label: _('loyalty_backend.field.discount'), type: 'decimal', value: 10 },
237
- {
238
- name: 'discountMode',
239
- label: _('loyalty_backend.field.discountMode'),
240
- type: 'select',
241
- options: options(_, DISCOUNT_MODES, 'discountMode'),
242
- },
243
- {
244
- name: 'discountApplicability',
245
- label: _('loyalty_backend.field.discountApplicability'),
246
- type: 'select',
247
- options: options(_, DISCOUNT_APPLICABILITY, 'discountApplicability'),
248
- },
249
- { name: 'discountMaximum', label: _('loyalty_backend.field.discountMaximum'), type: 'decimal' },
250
- {
251
- name: 'rewardProductId',
252
- label: _('loyalty_backend.field.rewardProduct'),
253
- type: 'select',
254
- options: choices(products, true),
255
- },
256
- {
257
- name: 'rewardProductQuantity',
258
- label: _('loyalty_backend.field.rewardProductQuantity'),
259
- type: 'decimal',
260
- value: 1,
261
- },
262
- {
263
- name: 'requiredPoints',
264
- label: _('loyalty_backend.field.requiredPoints'),
265
- type: 'decimal',
266
- value: 1,
267
- required: true,
268
- },
269
- { name: 'clearWallet', label: _('loyalty_backend.field.clearWallet'), type: 'checkbox' },
270
- ];
271
102
  const routes = {
272
103
  '/admin/loyalty': (ctx) => async (url, req) => {
273
104
  if (req.method !== 'GET')
@@ -289,23 +120,8 @@ const routes = {
289
120
  });
290
121
  },
291
122
  '/admin/loyalty/programs': (ctx) => async (url, req) => {
292
- if (req.method === 'POST') {
293
- if (crossSite(req))
294
- return text('Forbidden', { status: 403 });
295
- const form = await readForm(req);
296
- const result = await ctx.call('loyalty.program.save', { ...programInput(randomUUID(), form), availableSale: true, availablePos: true }, url, req);
297
- if (result.ok)
298
- return seeOther(inLocale(url, `/admin/loyalty/programs/${String(result.id)}`));
299
- const _ = ctx.translate(ctx.localeOf(url, req));
300
- const [rows, totals] = await Promise.all([
301
- ctx.call('loyalty.program.list', { includeArchived: true, limit: PAGE_SIZE }, url, req),
302
- ctx.call('loyalty.program.stats', {}, url, req),
303
- ]);
304
- return adminPage(ctx, url, req, {
305
- title: 'loyalty_backend.programs.title',
306
- body: (_, frame) => programsScreen(_, frame, rows, totals, createProgramFields(_), resultErrors(result, _)),
307
- });
308
- }
123
+ if (req.method === 'POST')
124
+ return seeOther(inLocale(url, '/admin/loyalty/programs/new'));
309
125
  if (req.method !== 'GET')
310
126
  return text('GET or POST', { status: 405 });
311
127
  const page = pageOf(url);
@@ -338,7 +154,10 @@ const routes = {
338
154
  ...(programType ? { type: programType } : {}),
339
155
  },
340
156
  facets: filterFacets(url, [
341
- { param: 'state', label: state ? _(`loyalty_backend.state.${state}`) : null },
157
+ {
158
+ param: 'state',
159
+ label: state ? _(`loyalty_backend.state.${state}`) : null,
160
+ },
342
161
  {
343
162
  param: 'type',
344
163
  label: programType ? _(`loyalty_backend.programType.${programType}`) : null,
@@ -346,10 +165,26 @@ const routes = {
346
165
  ]),
347
166
  menus: [
348
167
  filterMenu(url, 'state', _('loyalty_backend.field.state'), 'state', state, [
349
- { value: 'running', label: _('loyalty_backend.state.running') },
350
- { value: 'upcoming', label: _('loyalty_backend.state.upcoming') },
351
- { value: 'archived', label: _('loyalty_backend.state.archived') },
352
- { value: 'ended', label: _('loyalty_backend.state.ended') },
168
+ {
169
+ value: 'draft',
170
+ label: _('loyalty_backend.state.draft'),
171
+ },
172
+ {
173
+ value: 'running',
174
+ label: _('loyalty_backend.state.running'),
175
+ },
176
+ {
177
+ value: 'upcoming',
178
+ label: _('loyalty_backend.state.upcoming'),
179
+ },
180
+ {
181
+ value: 'archived',
182
+ label: _('loyalty_backend.state.archived'),
183
+ },
184
+ {
185
+ value: 'ended',
186
+ label: _('loyalty_backend.state.ended'),
187
+ },
353
188
  ]),
354
189
  filterMenu(url, 'type', _('loyalty_backend.field.programType'), 'type', programType, options(_, PROGRAM_TYPES, 'programType')),
355
190
  ],
@@ -359,72 +194,8 @@ const routes = {
359
194
  }, rows, totals, createProgramFields(_)),
360
195
  });
361
196
  },
362
- '/admin/loyalty/programs/{id}': (ctx) => async (url, req, params) => {
363
- let errors = [];
364
- if (req.method === 'POST') {
365
- if (crossSite(req))
366
- return text('Forbidden', { status: 403 });
367
- const form = await readForm(req);
368
- let result;
369
- if (form.action === 'save-program')
370
- result = await ctx.call('loyalty.program.save', programInput(params.id, form), url, req);
371
- else if (form.action === 'archive' || form.action === 'restore')
372
- result = await ctx.call('loyalty.program.archive', { id: params.id, active: form.action === 'restore' }, url, req);
373
- else if (form.action === 'add-rule')
374
- result = await ctx.call('loyalty.rule.save', {
375
- id: randomUUID(),
376
- programId: params.id,
377
- priority: Number(form.priority || 10),
378
- ...optional(form, 'productId'),
379
- pointAmount: form.pointAmount || '1',
380
- pointMode: form.pointMode || 'order',
381
- pointSplit: bool(form.pointSplit),
382
- minimumQuantity: form.minimumQuantity || '1',
383
- minimumAmount: form.minimumAmount || '0',
384
- taxMode: form.taxMode || 'excl',
385
- mode: form.mode || 'auto',
386
- ...optional(form, 'code'),
387
- }, url, req);
388
- else if (form.action === 'add-reward')
389
- result = await ctx.call('loyalty.reward.save', {
390
- id: randomUUID(),
391
- programId: params.id,
392
- description: form.description || '',
393
- rewardType: form.rewardType || 'discount',
394
- discount: form.discount || '0',
395
- discountMode: form.discountMode || 'percent',
396
- discountApplicability: form.discountApplicability || 'order',
397
- ...optional(form, 'discountMaximum'),
398
- ...optional(form, 'rewardProductId'),
399
- rewardProductQuantity: form.rewardProductQuantity || '1',
400
- requiredPoints: form.requiredPoints || '1',
401
- clearWallet: bool(form.clearWallet),
402
- }, url, req);
403
- else
404
- return text('unknown action', { status: 400 });
405
- if (result.ok)
406
- return seeOther(inLocale(url, url.pathname));
407
- errors = resultErrors(result, ctx.translate(ctx.localeOf(url, req)));
408
- }
409
- else if (req.method !== 'GET')
410
- return text('GET or POST', { status: 405 });
411
- const [program, data] = await Promise.all([
412
- ctx.call('loyalty.program.get', { id: params.id }, url, req),
413
- dataFor(ctx, url, req),
414
- ]);
415
- if (!program)
416
- return text(ctx.translate(ctx.localeOf(url, req))('loyalty_backend.error.notFound'), { status: 404 });
417
- return adminPage(ctx, url, req, {
418
- title: 'loyalty_backend.program.detail',
419
- body: (_, frame) => programDetailScreen(_, frame, program, {
420
- programFields: programFields(_, program),
421
- ruleFields: ruleFields(_, data.products),
422
- rewardFields: rewardFields(_, data.products),
423
- tab: url.searchParams.get('tab') ?? 'overview',
424
- errors,
425
- }),
426
- });
427
- },
197
+ '/admin/loyalty/programs/new': newProgramRoute,
198
+ '/admin/loyalty/programs/{id}': programWorkspaceRoute,
428
199
  '/admin/loyalty/wallets': (ctx) => async (url, req) => {
429
200
  const data = await dataFor(ctx, url, req);
430
201
  let errors = [];
@@ -479,14 +250,29 @@ const routes = {
479
250
  ...(programId ? { program: programId } : {}),
480
251
  },
481
252
  facets: filterFacets(url, [
482
- { param: 'state', label: state ? _(`loyalty_backend.state.${state}`) : null },
483
- { param: 'program', label: programId ? (programNames.get(programId) ?? null) : null },
253
+ {
254
+ param: 'state',
255
+ label: state ? _(`loyalty_backend.state.${state}`) : null,
256
+ },
257
+ {
258
+ param: 'program',
259
+ label: programId ? (programNames.get(programId) ?? null) : null,
260
+ },
484
261
  ]),
485
262
  menus: [
486
263
  filterMenu(url, 'state', _('loyalty_backend.field.state'), 'state', state, [
487
- { value: 'active', label: _('loyalty_backend.state.running') },
488
- { value: 'locked', label: _('loyalty_backend.state.locked') },
489
- { value: 'expired', label: _('loyalty_backend.state.expired') },
264
+ {
265
+ value: 'active',
266
+ label: _('loyalty_backend.state.running'),
267
+ },
268
+ {
269
+ value: 'locked',
270
+ label: _('loyalty_backend.state.locked'),
271
+ },
272
+ {
273
+ value: 'expired',
274
+ label: _('loyalty_backend.state.expired'),
275
+ },
490
276
  ]),
491
277
  filterMenu(url, 'program', _('loyalty_backend.field.program'), 'program', programId, data.programs.map((program) => ({
492
278
  value: String(program.id),
@@ -521,7 +307,11 @@ const routes = {
521
307
  type: 'decimal',
522
308
  value: 0,
523
309
  },
524
- { name: 'expiresAt', label: _('loyalty_backend.field.expiresAt'), type: 'datetime-local' },
310
+ {
311
+ name: 'expiresAt',
312
+ label: _('loyalty_backend.field.expiresAt'),
313
+ type: 'datetime-local',
314
+ },
525
315
  ], errors),
526
316
  });
527
317
  },
@@ -563,14 +353,24 @@ const routes = {
563
353
  return adminPage(ctx, url, req, {
564
354
  title: 'loyalty_backend.wallets.title',
565
355
  body: (_, frame) => walletDetailScreen(_, frame, wallet, [
566
- { name: 'amount', label: _('loyalty_backend.field.amount'), type: 'decimal', required: true },
356
+ {
357
+ name: 'amount',
358
+ label: _('loyalty_backend.field.amount'),
359
+ type: 'decimal',
360
+ required: true,
361
+ },
567
362
  {
568
363
  name: 'sourceId',
569
364
  label: _('loyalty_backend.field.sourceId'),
570
365
  value: randomUUID(),
571
366
  required: true,
572
367
  },
573
- { name: 'note', label: _('loyalty_backend.field.note'), type: 'textarea', span: 'full' },
368
+ {
369
+ name: 'note',
370
+ label: _('loyalty_backend.field.note'),
371
+ type: 'textarea',
372
+ span: 'full',
373
+ },
574
374
  ], url.searchParams.get('tab') ?? 'overview', errors),
575
375
  });
576
376
  },
@@ -616,13 +416,22 @@ const routes = {
616
416
  ...(walletId ? { wallet: walletId } : {}),
617
417
  },
618
418
  facets: filterFacets(url, [
619
- { param: 'period', label: _(`loyalty_backend.period.${period}`) },
419
+ {
420
+ param: 'period',
421
+ label: _(`loyalty_backend.period.${period}`),
422
+ },
620
423
  {
621
424
  param: 'operation',
622
425
  label: operation ? _(`loyalty_backend.operation.${operation}`) : null,
623
426
  },
624
- { param: 'program', label: programId ? (programNames.get(programId) ?? null) : null },
625
- { param: 'wallet', label: walletId ? (codes.get(walletId) ?? null) : null },
427
+ {
428
+ param: 'program',
429
+ label: programId ? (programNames.get(programId) ?? null) : null,
430
+ },
431
+ {
432
+ param: 'wallet',
433
+ label: walletId ? (codes.get(walletId) ?? null) : null,
434
+ },
626
435
  ]),
627
436
  menus: [
628
437
  filterMenu(url, 'period', _('loyalty_backend.field.period'), 'period', period, PERIODS.map((value) => ({
@@ -638,148 +447,145 @@ const routes = {
638
447
  },
639
448
  pager: pager(url, page, rows.length, Number(totals.entries ?? 0)),
640
449
  },
641
- }, rows.map((row) => ({ ...row, walletCode: codes.get(String(row.walletId)) })), totals),
450
+ }, rows.map((row) => ({
451
+ ...row,
452
+ walletCode: codes.get(String(row.walletId)),
453
+ })), totals),
642
454
  });
643
455
  },
644
- '/admin/loyalty/memberships': (ctx) => async (url, req) => {
645
- const data = await dataFor(ctx, url, req);
646
- const loyaltyPrograms = data.programs.filter((program) => program.programType === 'loyalty' && program.active);
647
- let errors = [];
456
+ '/admin/loyalty/memberships': (_ctx) => async (url, req) => {
457
+ if (req.method !== 'GET')
458
+ return text('GET', { status: 405 });
459
+ return seeOther(inLocale(url, '/admin/loyalty/tiers'));
460
+ },
461
+ '/admin/loyalty/tiers': (ctx) => async (url, req) => {
462
+ if (!['GET', 'POST'].includes(req.method ?? ''))
463
+ return text('GET or POST', { status: 405 });
464
+ if (req.method === 'POST' && crossSite(req))
465
+ return text('Forbidden', { status: 403 });
466
+ const _ = ctx.translate(ctx.localeOf(url, req));
467
+ const refusal = formRefusal(_);
468
+ let submitted = {};
469
+ let submittedAction = '';
470
+ let tiers = (await ctx.call('loyalty.tier.list', { includeArchived: true }, url, req));
471
+ const baseHref = () => {
472
+ const target = new URL(inLocale(url, '/admin/loyalty/tiers'), url.origin);
473
+ return target.pathname + target.search;
474
+ };
648
475
  if (req.method === 'POST') {
649
- if (crossSite(req))
650
- return text('Forbidden', { status: 403 });
651
476
  const form = await readForm(req);
652
- const result = form.action === 'tier'
653
- ? await ctx.call('loyalty.tier.save', {
654
- id: randomUUID(),
655
- name: form.name ?? '',
656
- code: form.code ?? '',
657
- sequence: Number(form.sequence || 10),
658
- minimumSpend: form.minimumSpend || '0',
659
- redeemPercent: form.redeemPercent || '0',
660
- active: true,
661
- }, url, req)
662
- : await ctx.call('loyalty.membership.config.save', {
663
- id: form.id || 'membership-config',
664
- programId: form.programId ?? '',
665
- windowMonths: Number(form.windowMonths || 12),
666
- pointValue: form.pointValue || '1',
667
- minimumRedeemStep: form.minimumRedeemStep || '1',
668
- fallbackCurrencyPerPoint: form.fallbackCurrencyPerPoint || '1',
669
- fallbackEnabled: bool(form.fallbackEnabled),
670
- }, url, req);
671
- if (result.ok)
672
- return seeOther(inLocale(url, url.pathname));
673
- errors = resultErrors(result, ctx.translate(ctx.localeOf(url, req)));
477
+ submitted = form;
478
+ submittedAction = form.action ?? '';
479
+ let result = { ok: false };
480
+ if (submittedAction === 'tier') {
481
+ const checked = refusal.check(tierFormSchema, form);
482
+ if (checked) {
483
+ const existingTier = tiers.find((row) => String(row.id) === form.id);
484
+ result = (await ctx.call('loyalty.tier.save', {
485
+ id: form.id || randomUUID(),
486
+ name: checked.name,
487
+ code: checked.code,
488
+ sequence: checked.sequence ?? 10,
489
+ minimumSpend: checked.minimumSpend,
490
+ windowMonths: checked.windowMonths,
491
+ redeemPercent: String(existingTier?.redeemPercent ?? 100),
492
+ active: true,
493
+ }, url, req));
494
+ }
495
+ if (result.ok)
496
+ return seeOther(baseHref());
497
+ refusal.add(resultErrors(result, _));
498
+ }
499
+ else if (submittedAction === 'toggle') {
500
+ const tier = tiers.find((row) => String(row.id) === form.id);
501
+ if (!tier)
502
+ return text(_('loyalty_backend.error.notFound'), { status: 404 });
503
+ result = (await ctx.call('loyalty.tier.save', {
504
+ id: String(tier.id),
505
+ name: String(tier.name),
506
+ code: String(tier.code),
507
+ sequence: Number(tier.sequence ?? 10),
508
+ minimumSpend: String(tier.minimumSpend),
509
+ windowMonths: Number(tier.windowMonths ?? 12),
510
+ redeemPercent: String(tier.redeemPercent),
511
+ active: !tier.active,
512
+ }, url, req));
513
+ if (result.ok)
514
+ return seeOther(baseHref());
515
+ refusal.add(resultErrors(result, _));
516
+ }
517
+ else
518
+ return text('unknown action', { status: 400 });
519
+ tiers = (await ctx.call('loyalty.tier.list', { includeArchived: true }, url, req));
674
520
  }
675
- else if (req.method !== 'GET')
676
- return text('GET or POST', { status: 405 });
677
- const page = pageOf(url);
678
- const state = url.searchParams.get('state') ?? undefined;
679
- const tierId = url.searchParams.get('tier') ?? undefined;
680
- const [tiers, memberships, totals, config] = await Promise.all([
681
- ctx.call('loyalty.tier.list', { includeArchived: true }, url, req),
682
- ctx.call('loyalty.membership.list', {
683
- ...(state ? { state } : {}),
684
- ...(tierId ? { tierId } : {}),
685
- limit: PAGE_SIZE,
686
- offset: (page - 1) * PAGE_SIZE,
687
- }, url, req),
688
- ctx.call('loyalty.membership.stats', { ...(tierId ? { tierId } : {}) }, url, req),
689
- ctx.call('loyalty.membership.config.get', {}, url, req),
690
- ]);
691
- const names = new Map(data.partners.map((partner) => [String(partner.id), String(partner.name)]));
692
- const tierNames = new Map(tiers.map((tier) => [String(tier.id), String(tier.name)]));
693
- return adminPage(ctx, url, req, {
694
- title: 'loyalty_backend.memberships.title',
695
- body: (_, frame) => membershipsScreen(_, {
696
- ...frame,
697
- chrome: {
698
- section: _('loyalty_backend.menu.app'),
699
- search: {
700
- name: 'q',
701
- value: '',
702
- placeholder: _('loyalty_backend.chrome.searchMembers'),
703
- keep: { ...(state ? { state } : {}), ...(tierId ? { tier: tierId } : {}) },
704
- facets: filterFacets(url, [
705
- { param: 'state', label: state ? _(`loyalty_backend.state.${state}`) : null },
706
- { param: 'tier', label: tierId ? (tierNames.get(tierId) ?? null) : null },
707
- ]),
708
- menus: [
709
- filterMenu(url, 'state', _('loyalty_backend.field.state'), 'state', state, [
710
- { value: 'active', label: _('loyalty_backend.state.active') },
711
- { value: 'dormant', label: _('loyalty_backend.state.dormant') },
712
- ]),
713
- filterMenu(url, 'tier', _('loyalty_backend.field.tier'), 'tier', tierId, tiers.map((tier) => ({ value: String(tier.id), label: String(tier.name) }))),
714
- ],
715
- },
716
- pager: pager(url, page, memberships.length, Number(totals.total ?? 0)),
717
- },
718
- }, memberships.map((membership) => ({
719
- ...membership,
720
- partnerName: names.get(String(membership.partnerId)),
721
- })), totals, tiers, [
722
- { name: 'name', label: _('loyalty_backend.field.name'), required: true },
723
- { name: 'code', label: _('loyalty_backend.field.code'), required: true },
724
- { name: 'sequence', label: _('loyalty_backend.field.sequence'), type: 'number', value: 10 },
521
+ const tierId = url.searchParams.get('tier') ?? (submittedAction === 'tier' ? submitted.id : undefined);
522
+ const tier = tierId ? (tiers.find((row) => String(row.id) === tierId) ?? null) : null;
523
+ if (tierId && !tier && submittedAction !== 'tier')
524
+ return text(_('loyalty_backend.error.notFound'), { status: 404 });
525
+ const modal = url.searchParams.get('modal') === 'tier' || submittedAction === 'tier';
526
+ const held = (name, fallback, action) => submittedAction === action && Object.hasOwn(submitted, name) ? submitted[name] : fallback;
527
+ const tierFields = modal
528
+ ? [
725
529
  {
726
- name: 'minimumSpend',
727
- label: _('loyalty_backend.field.minimumSpend'),
728
- type: 'decimal',
729
- value: 0,
530
+ name: 'name',
531
+ label: _('loyalty_backend.field.name'),
532
+ value: String(held('name', tier?.name ?? '', 'tier')),
730
533
  required: true,
534
+ error: submittedAction === 'tier' ? refusal.error('name') : null,
731
535
  },
732
536
  {
733
- name: 'redeemPercent',
734
- label: _('loyalty_backend.field.redeemPercent'),
735
- type: 'decimal',
736
- value: 100,
537
+ name: 'code',
538
+ label: _('loyalty_backend.field.code'),
539
+ value: String(held('code', tier?.code ?? '', 'tier')),
737
540
  required: true,
541
+ error: submittedAction === 'tier' ? refusal.error('code') : null,
738
542
  },
739
- ], [
740
543
  {
741
- name: 'programId',
742
- label: _('loyalty_backend.field.program'),
743
- type: 'select',
744
- value: String(config?.programId ?? loyaltyPrograms[0]?.id ?? ''),
745
- options: choices(loyaltyPrograms),
544
+ name: 'minimumSpend',
545
+ label: _('loyalty_backend.field.minimumSpend'),
546
+ type: 'decimal',
547
+ value: String(held('minimumSpend', tier?.minimumSpend ?? 0, 'tier')),
746
548
  required: true,
549
+ error: submittedAction === 'tier' ? refusal.error('minimumSpend') : null,
747
550
  },
748
551
  {
749
552
  name: 'windowMonths',
750
553
  label: _('loyalty_backend.field.windowMonths'),
751
554
  type: 'number',
752
- value: Number(config?.windowMonths ?? 12),
555
+ value: String(held('windowMonths', tier?.windowMonths ?? 12, 'tier')),
753
556
  required: true,
557
+ help: _('loyalty_backend.memberships.windowExamples'),
558
+ error: submittedAction === 'tier' ? refusal.error('windowMonths') : null,
754
559
  },
755
560
  {
756
- name: 'pointValue',
757
- label: _('loyalty_backend.field.pointValue'),
758
- type: 'decimal',
759
- value: String(config?.pointValue ?? 1),
760
- required: true,
761
- },
762
- {
763
- name: 'minimumRedeemStep',
764
- label: _('loyalty_backend.field.minimumRedeemStep'),
765
- type: 'decimal',
766
- value: String(config?.minimumRedeemStep ?? 1),
767
- required: true,
768
- },
769
- {
770
- name: 'fallbackCurrencyPerPoint',
771
- label: _('loyalty_backend.field.fallbackCurrencyPerPoint'),
772
- type: 'decimal',
773
- value: String(config?.fallbackCurrencyPerPoint ?? 1),
774
- required: true,
561
+ name: 'sequence',
562
+ label: _('loyalty_backend.field.sequence'),
563
+ type: 'number',
564
+ value: String(held('sequence', tier?.sequence ?? 10, 'tier')),
565
+ error: submittedAction === 'tier' ? refusal.error('sequence') : null,
775
566
  },
776
- {
777
- name: 'fallbackEnabled',
778
- label: _('loyalty_backend.field.fallbackEnabled'),
779
- type: 'checkbox',
780
- value: config ? Boolean(config.fallbackEnabled) : true,
567
+ ]
568
+ : undefined;
569
+ const create = new URL(baseHref(), url.origin);
570
+ create.searchParams.set('modal', 'tier');
571
+ const closeHref = baseHref();
572
+ return adminPage(ctx, url, req, {
573
+ title: 'loyalty_backend.memberships.title',
574
+ body: (_, frame) => tiersScreen(_, frame, tiers, {
575
+ action: url.pathname + url.search,
576
+ closeHref,
577
+ createHref: create.pathname + create.search,
578
+ tierHref: (row) => {
579
+ const target = new URL(closeHref, url.origin);
580
+ target.searchParams.set('modal', 'tier');
581
+ target.searchParams.set('tier', String(row.id));
582
+ return target.pathname + target.search;
781
583
  },
782
- ], errors),
584
+ tierFields,
585
+ tierErrors: submittedAction === 'tier' ? refusal.sentences() : [],
586
+ tier,
587
+ modal,
588
+ }),
783
589
  });
784
590
  },
785
591
  '/admin/loyalty/orders/{channel}/{id}': (ctx) => async (url, req, params) => {
@@ -795,7 +601,11 @@ const routes = {
795
601
  if (form.action === 'code')
796
602
  result = await ctx.call(`${prefix}.applyCode`, { orderId: params.id, code: form.code ?? '' }, url, req);
797
603
  else if (form.action === 'reward')
798
- result = await ctx.call(`${prefix}.applyReward`, { orderId: params.id, programId: form.programId ?? '', rewardId: form.rewardId ?? '' }, url, req);
604
+ result = await ctx.call(`${prefix}.applyReward`, {
605
+ orderId: params.id,
606
+ programId: form.programId ?? '',
607
+ rewardId: form.rewardId ?? '',
608
+ }, url, req);
799
609
  else if (form.action === 'remove')
800
610
  result = await ctx.call(`${prefix}.removeReward`, { orderId: params.id, programId: form.programId ?? '' }, url, req);
801
611
  else
@@ -841,7 +651,9 @@ const routes = {
841
651
  const user = (await ctx.call('user.getUser', { id: session.userId }, url, req));
842
652
  const _ = ctx.translate(ctx.localeOf(url, req));
843
653
  if (!user?.partnerId)
844
- return text(_('loyalty_backend.error.partnerRequired'), { status: 403 });
654
+ return text(_('loyalty_backend.error.partnerRequired'), {
655
+ status: 403,
656
+ });
845
657
  const summary = (await ctx.call('loyalty.portal.summary', { partnerId: user.partnerId }, url, req));
846
658
  if (summary.ok !== true)
847
659
  return text(resultErrors(summary, _).join('\n'), { status: 400 });
@@ -885,9 +697,9 @@ export default defineModule({
885
697
  'loyalty.memberships': {
886
698
  parent: 'loyalty',
887
699
  label: 'menu.memberships',
888
- path: '/admin/loyalty/memberships',
700
+ path: '/admin/loyalty/tiers',
889
701
  sequence: 30,
890
- needs: 'loyalty.membership.list',
702
+ needs: 'loyalty.tier.list',
891
703
  },
892
704
  'loyalty.ledger': {
893
705
  parent: 'loyalty',