@ketvietlab/ketsuite 0.1.5 → 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 (204) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/modules/backend/design/HANDOFF.md +2 -2
  7. package/dist/modules/backend/design/auth.css +12 -0
  8. package/dist/modules/backend/design/client/table-selection.d.mts +3 -3
  9. package/dist/modules/backend/design/client/table-selection.d.mts.map +1 -1
  10. package/dist/modules/backend/design/client/table-selection.mjs +1 -1
  11. package/dist/modules/backend/design/client/table-selection.mjs.map +4 -4
  12. package/dist/modules/backend/design/design-system.css +1 -1
  13. package/dist/modules/backend/design/forms.css +62 -1
  14. package/dist/modules/backend/design/lists.css +26 -18
  15. package/dist/modules/backend/design/record.css +5 -2
  16. package/dist/modules/backend/design/responsive.css +5 -1
  17. package/dist/modules/backend/design/user-workflow.css +257 -0
  18. package/dist/modules/backend/index.d.ts.map +1 -1
  19. package/dist/modules/backend/index.js +1 -0
  20. package/dist/modules/backend/index.js.map +1 -1
  21. package/dist/modules/backend/messages.d.ts.map +1 -1
  22. package/dist/modules/backend/messages.js +2 -0
  23. package/dist/modules/backend/messages.js.map +1 -1
  24. package/dist/modules/crm/functions.d.ts +5 -0
  25. package/dist/modules/crm/functions.d.ts.map +1 -1
  26. package/dist/modules/crm/functions.js +105 -10
  27. package/dist/modules/crm/functions.js.map +1 -1
  28. package/dist/modules/crm/jobs.d.ts.map +1 -1
  29. package/dist/modules/crm/jobs.js +1 -0
  30. package/dist/modules/crm/jobs.js.map +1 -1
  31. package/dist/modules/crm/operations.d.ts.map +1 -1
  32. package/dist/modules/crm/operations.js +2 -1
  33. package/dist/modules/crm/operations.js.map +1 -1
  34. package/dist/modules/crm_backend/index.d.ts.map +1 -1
  35. package/dist/modules/crm_backend/index.js +5 -2
  36. package/dist/modules/crm_backend/index.js.map +1 -1
  37. package/dist/modules/crm_backend/messages.d.ts +116 -0
  38. package/dist/modules/crm_backend/messages.d.ts.map +1 -1
  39. package/dist/modules/crm_backend/messages.js +122 -6
  40. package/dist/modules/crm_backend/messages.js.map +1 -1
  41. package/dist/modules/crm_backend/routes.d.ts.map +1 -1
  42. package/dist/modules/crm_backend/routes.js +434 -174
  43. package/dist/modules/crm_backend/routes.js.map +1 -1
  44. package/dist/modules/crm_backend/screens/case-create.d.ts +7 -0
  45. package/dist/modules/crm_backend/screens/case-create.d.ts.map +1 -1
  46. package/dist/modules/crm_backend/screens/case-create.js +13 -4
  47. package/dist/modules/crm_backend/screens/case-create.js.map +1 -1
  48. package/dist/modules/crm_backend/screens/case-detail.d.ts +6 -0
  49. package/dist/modules/crm_backend/screens/case-detail.d.ts.map +1 -1
  50. package/dist/modules/crm_backend/screens/case-detail.js +73 -23
  51. package/dist/modules/crm_backend/screens/case-detail.js.map +1 -1
  52. package/dist/modules/crm_backend/screens/configuration.d.ts +21 -3
  53. package/dist/modules/crm_backend/screens/configuration.d.ts.map +1 -1
  54. package/dist/modules/crm_backend/screens/configuration.js +260 -55
  55. package/dist/modules/crm_backend/screens/configuration.js.map +1 -1
  56. package/dist/modules/crm_backend/screens/index.d.ts +3 -3
  57. package/dist/modules/crm_backend/screens/index.d.ts.map +1 -1
  58. package/dist/modules/crm_backend/screens/index.js +2 -2
  59. package/dist/modules/crm_backend/screens/index.js.map +1 -1
  60. package/dist/modules/loyalty/admin-functions.d.ts +2 -0
  61. package/dist/modules/loyalty/admin-functions.d.ts.map +1 -1
  62. package/dist/modules/loyalty/admin-functions.js +96 -20
  63. package/dist/modules/loyalty/admin-functions.js.map +1 -1
  64. package/dist/modules/loyalty/engine.d.ts +4 -1
  65. package/dist/modules/loyalty/engine.d.ts.map +1 -1
  66. package/dist/modules/loyalty/engine.js +125 -18
  67. package/dist/modules/loyalty/engine.js.map +1 -1
  68. package/dist/modules/loyalty/index.d.ts.map +1 -1
  69. package/dist/modules/loyalty/index.js +4 -3
  70. package/dist/modules/loyalty/index.js.map +1 -1
  71. package/dist/modules/loyalty/jobs.d.ts.map +1 -1
  72. package/dist/modules/loyalty/jobs.js +0 -1
  73. package/dist/modules/loyalty/jobs.js.map +1 -1
  74. package/dist/modules/loyalty/membership-functions.d.ts +5 -0
  75. package/dist/modules/loyalty/membership-functions.d.ts.map +1 -1
  76. package/dist/modules/loyalty/membership-functions.js +37 -23
  77. package/dist/modules/loyalty/membership-functions.js.map +1 -1
  78. package/dist/modules/loyalty/messages.d.ts +14 -0
  79. package/dist/modules/loyalty/messages.d.ts.map +1 -1
  80. package/dist/modules/loyalty/messages.js +14 -0
  81. package/dist/modules/loyalty/messages.js.map +1 -1
  82. package/dist/modules/loyalty/models.d.ts.map +1 -1
  83. package/dist/modules/loyalty/models.js +17 -0
  84. package/dist/modules/loyalty/models.js.map +1 -1
  85. package/dist/modules/loyalty/order-functions.d.ts.map +1 -1
  86. package/dist/modules/loyalty/order-functions.js +70 -17
  87. package/dist/modules/loyalty/order-functions.js.map +1 -1
  88. package/dist/modules/loyalty/program-design.d.ts +11 -0
  89. package/dist/modules/loyalty/program-design.d.ts.map +1 -0
  90. package/dist/modules/loyalty/program-design.js +356 -0
  91. package/dist/modules/loyalty/program-design.js.map +1 -0
  92. package/dist/modules/loyalty/stats.d.ts +0 -13
  93. package/dist/modules/loyalty/stats.d.ts.map +1 -1
  94. package/dist/modules/loyalty/stats.js +11 -6
  95. package/dist/modules/loyalty/stats.js.map +1 -1
  96. package/dist/modules/loyalty/stored-value.d.ts.map +1 -1
  97. package/dist/modules/loyalty/stored-value.js +7 -2
  98. package/dist/modules/loyalty/stored-value.js.map +1 -1
  99. package/dist/modules/loyalty/types.d.ts +1 -1
  100. package/dist/modules/loyalty/types.d.ts.map +1 -1
  101. package/dist/modules/loyalty_backend/design-messages.d.ts +221 -0
  102. package/dist/modules/loyalty_backend/design-messages.d.ts.map +1 -0
  103. package/dist/modules/loyalty_backend/design-messages.js +221 -0
  104. package/dist/modules/loyalty_backend/design-messages.js.map +1 -0
  105. package/dist/modules/loyalty_backend/index.js +224 -412
  106. package/dist/modules/loyalty_backend/index.js.map +1 -1
  107. package/dist/modules/loyalty_backend/messages.d.ts +642 -214
  108. package/dist/modules/loyalty_backend/messages.d.ts.map +1 -1
  109. package/dist/modules/loyalty_backend/messages.js +19 -13
  110. package/dist/modules/loyalty_backend/messages.js.map +1 -1
  111. package/dist/modules/loyalty_backend/program-workspace.d.ts +4 -0
  112. package/dist/modules/loyalty_backend/program-workspace.d.ts.map +1 -0
  113. package/dist/modules/loyalty_backend/program-workspace.js +600 -0
  114. package/dist/modules/loyalty_backend/program-workspace.js.map +1 -0
  115. package/dist/modules/loyalty_backend/screens/index.d.ts +12 -36
  116. package/dist/modules/loyalty_backend/screens/index.d.ts.map +1 -1
  117. package/dist/modules/loyalty_backend/screens/index.js +148 -292
  118. package/dist/modules/loyalty_backend/screens/index.js.map +1 -1
  119. package/dist/modules/loyalty_backend/screens/page-frame.d.ts.map +1 -1
  120. package/dist/modules/loyalty_backend/screens/page-frame.js +2 -2
  121. package/dist/modules/loyalty_backend/screens/page-frame.js.map +1 -1
  122. package/dist/modules/loyalty_pos/functions.d.ts.map +1 -1
  123. package/dist/modules/loyalty_pos/functions.js +1 -0
  124. package/dist/modules/loyalty_pos/functions.js.map +1 -1
  125. package/dist/modules/loyalty_pos/index.js +1 -1
  126. package/dist/modules/loyalty_pos/index.js.map +1 -1
  127. package/dist/modules/loyalty_sale/functions.d.ts +1 -0
  128. package/dist/modules/loyalty_sale/functions.d.ts.map +1 -1
  129. package/dist/modules/loyalty_sale/functions.js +1 -0
  130. package/dist/modules/loyalty_sale/functions.js.map +1 -1
  131. package/dist/modules/partner_backend/client/partner.css +3 -1
  132. package/dist/modules/partner_backend/index.d.ts.map +1 -1
  133. package/dist/modules/partner_backend/index.js +4 -0
  134. package/dist/modules/partner_backend/index.js.map +1 -1
  135. package/dist/modules/partner_backend/routes.d.ts.map +1 -1
  136. package/dist/modules/partner_backend/routes.js +6 -1
  137. package/dist/modules/partner_backend/routes.js.map +1 -1
  138. package/dist/modules/partner_backend/screens/form.d.ts +1 -0
  139. package/dist/modules/partner_backend/screens/form.d.ts.map +1 -1
  140. package/dist/modules/partner_backend/screens/form.js +1 -0
  141. package/dist/modules/partner_backend/screens/form.js.map +1 -1
  142. package/dist/modules/user/authorization.d.ts +64 -1
  143. package/dist/modules/user/authorization.d.ts.map +1 -1
  144. package/dist/modules/user/authorization.js +445 -53
  145. package/dist/modules/user/authorization.js.map +1 -1
  146. package/dist/modules/user/functions.d.ts.map +1 -1
  147. package/dist/modules/user/functions.js +45 -23
  148. package/dist/modules/user/functions.js.map +1 -1
  149. package/dist/modules/user/index.d.ts +2 -1
  150. package/dist/modules/user/index.d.ts.map +1 -1
  151. package/dist/modules/user/index.js +1 -1
  152. package/dist/modules/user/index.js.map +1 -1
  153. package/dist/modules/user/models.d.ts.map +1 -1
  154. package/dist/modules/user/models.js +5 -0
  155. package/dist/modules/user/models.js.map +1 -1
  156. package/dist/modules/user/roles.d.ts.map +1 -1
  157. package/dist/modules/user/roles.js +2 -1
  158. package/dist/modules/user/roles.js.map +1 -1
  159. package/dist/modules/user_backend/access-routes.d.ts +31 -0
  160. package/dist/modules/user_backend/access-routes.d.ts.map +1 -0
  161. package/dist/modules/user_backend/access-routes.js +447 -0
  162. package/dist/modules/user_backend/access-routes.js.map +1 -0
  163. package/dist/modules/user_backend/index.d.ts +1 -0
  164. package/dist/modules/user_backend/index.d.ts.map +1 -1
  165. package/dist/modules/user_backend/index.js +1 -0
  166. package/dist/modules/user_backend/index.js.map +1 -1
  167. package/dist/modules/user_backend/routes.d.ts.map +1 -1
  168. package/dist/modules/user_backend/routes.js +10 -2
  169. package/dist/modules/user_backend/routes.js.map +1 -1
  170. package/dist/permission-catalogue.d.ts.map +1 -1
  171. package/dist/permission-catalogue.js +7 -0
  172. package/dist/permission-catalogue.js.map +1 -1
  173. package/dist/scaffold/index.js +1 -1
  174. package/dist/ui/client/table-selection-view.d.ts.map +1 -1
  175. package/dist/ui/client/table-selection-view.js +95 -1
  176. package/dist/ui/client/table-selection-view.js.map +1 -1
  177. package/dist/ui/client/user-workflow.d.ts +2 -0
  178. package/dist/ui/client/user-workflow.d.ts.map +1 -0
  179. package/dist/ui/client/user-workflow.js +141 -0
  180. package/dist/ui/client/user-workflow.js.map +1 -0
  181. package/dist/ui/form.d.ts +7 -0
  182. package/dist/ui/form.d.ts.map +1 -1
  183. package/dist/ui/form.js +28 -15
  184. package/dist/ui/form.js.map +1 -1
  185. package/dist/ui/icons.d.ts.map +1 -1
  186. package/dist/ui/icons.js +2 -0
  187. package/dist/ui/icons.js.map +1 -1
  188. package/dist/ui/index.d.ts +5 -1
  189. package/dist/ui/index.d.ts.map +1 -1
  190. package/dist/ui/index.js +4 -1
  191. package/dist/ui/index.js.map +1 -1
  192. package/dist/ui/layout.d.ts.map +1 -1
  193. package/dist/ui/layout.js +14 -4
  194. package/dist/ui/layout.js.map +1 -1
  195. package/dist/ui/nav.d.ts.map +1 -1
  196. package/dist/ui/nav.js +2 -1
  197. package/dist/ui/nav.js.map +1 -1
  198. package/dist/ui/table.js +1 -1
  199. package/dist/ui/table.js.map +1 -1
  200. package/dist/ui/user-workflow.d.ts +72 -0
  201. package/dist/ui/user-workflow.d.ts.map +1 -0
  202. package/dist/ui/user-workflow.js +144 -0
  203. package/dist/ui/user-workflow.js.map +1 -0
  204. package/package.json +8 -4
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "@ketvietlab/ketjs-view/jsx-runtime";
2
- import { badge, CardGrid, code, actionGroup, ContentCard, dataTable, DashboardPage, DefinitionList, Delta, emptyState, ListScreen, icon, linkButton, Metric, Notice, RecordActions, RecordForm, RecordWorkspace, RecordScreen, Section, shell, stack, Surface, Tabs, WorkspaceScreen, } from '../../../ui/index.js';
2
+ import { badge, CardGrid, code, actionGroup, ContentCard, dataTable, DashboardPage, DefinitionList, Delta, emptyState, icon, linkButton, Metric, ModalSheet, modalWorkspace, Notice, RecordForm, RecordWorkspace, RecordScreen, Section, shell, stack, Surface, Tabs, WorkspaceScreen, } from '../../../ui/index.js';
3
3
  import { selectionLabel } from '../../backend/screen.js';
4
4
  import { FormScreenFrame, ListScreenFrame } from './page-frame.js';
5
5
  const empty = (_) => emptyState(_('loyalty_backend.empty.title'), _('loyalty_backend.empty.hint'));
@@ -63,6 +63,10 @@ const walletStateBadge = (_, wallet) => {
63
63
  };
64
64
  /** Where a program stands in its own calendar. */
65
65
  const programStateBadge = (_, program) => {
66
+ if (program.phase === 'draft')
67
+ return badge(_('loyalty_backend.state.draft'), 'neutral');
68
+ if (program.state)
69
+ return badge(_(`loyalty_backend.state.${String(program.state)}`), program.state === 'running' ? 'positive' : 'neutral');
66
70
  // is what this module's own archive action writes, so that is
67
71
  // what the badge says. A program the operator paused and one they retired are
68
72
  // the same row here, and inventing a second word for it would claim otherwise.
@@ -78,8 +82,8 @@ const programStateBadge = (_, program) => {
78
82
  /** The window a program runs in, or that it never closes. */
79
83
  const periodOf = (_, program) => {
80
84
  const day = (value) => (value ? String(value).slice(0, 10) : null);
81
- const start = day(program.dateFrom);
82
- const end = day(program.dateTo);
85
+ const start = day(program.startDate ?? program.dateFrom);
86
+ const end = day(program.endDate ?? program.dateTo);
83
87
  if (!start && !end)
84
88
  return _('loyalty_backend.period.always');
85
89
  if (start && !end)
@@ -114,7 +118,7 @@ export const dashboardScreen = (_, frame, stats) => shell(_, _('loyalty_backend.
114
118
  id: 'members',
115
119
  title: _('loyalty_backend.menu.memberships'),
116
120
  value: stats.members,
117
- href: '/admin/loyalty/memberships',
121
+ href: '/admin/loyalty/tiers',
118
122
  },
119
123
  {
120
124
  id: 'ledger',
@@ -133,9 +137,28 @@ export const dashboardScreen = (_, frame, stats) => shell(_, _('loyalty_backend.
133
137
  * figures above count them apart and the badge in the row says which is which
134
138
  * without anyone opening it.
135
139
  */
136
- export const programsScreen = (_, frame, programs, totals, createFields, errors = []) => (_jsx(ListScreenFrame, { translator: _, title: _('loyalty_backend.programs.title'), subtitle: _('loyalty_backend.programs.hint'), frame: frame, body: stack([
140
+ export const programsScreen = (_, frame, programs, totals, _createFields, _errors = []) => (_jsx(ListScreenFrame, { translator: _, title: _('loyalty_backend.programs.title'), subtitle: _('loyalty_backend.programs.hint'), frame: {
141
+ ...frame,
142
+ extras: {
143
+ ...frame.extras,
144
+ 'topbar.end': linkButton({
145
+ label: _('loyalty_backend.action.createProgram'),
146
+ href: '/admin/loyalty/programs/new',
147
+ variant: 'primary',
148
+ }),
149
+ },
150
+ }, body: stack([
137
151
  statRow([
138
- { id: 'total', label: _('loyalty_backend.stat.programs'), value: figure(totals.total) },
152
+ {
153
+ id: 'total',
154
+ label: _('loyalty_backend.stat.programs'),
155
+ value: figure(totals.total),
156
+ },
157
+ {
158
+ id: 'draft',
159
+ label: _('loyalty_backend.state.draft'),
160
+ value: figure(totals.draft),
161
+ },
139
162
  {
140
163
  id: 'running',
141
164
  label: _('loyalty_backend.stat.running'),
@@ -154,21 +177,23 @@ export const programsScreen = (_, frame, programs, totals, createFields, errors
154
177
  value: figure(totals.archived),
155
178
  detail: _('loyalty_backend.stat.archivedHint'),
156
179
  },
157
- { id: 'ended', label: _('loyalty_backend.stat.ended'), value: figure(totals.ended) },
180
+ {
181
+ id: 'ended',
182
+ label: _('loyalty_backend.stat.ended'),
183
+ value: figure(totals.ended),
184
+ },
158
185
  ]),
159
186
  programs.length
160
187
  ? dataTable(_, {
161
188
  rows: programs,
189
+ rowHref: (row) => `/admin/loyalty/programs/${encodeURIComponent(String(row.id))}`,
190
+ rowLink: false,
162
191
  id: (row) => String(row.id),
163
192
  columns: [
164
193
  {
165
194
  key: 'name',
166
195
  label: _('loyalty_backend.field.name'),
167
- cell: (row) => linkButton({
168
- label: String(row.name),
169
- href: `/admin/loyalty/programs/${String(row.id)}`,
170
- variant: 'tertiary',
171
- }),
196
+ cell: (row) => String(row.name),
172
197
  priority: 'primary',
173
198
  },
174
199
  {
@@ -201,160 +226,19 @@ export const programsScreen = (_, frame, programs, totals, createFields, errors
201
226
  ],
202
227
  })
203
228
  : empty(_),
204
- _jsx(Section, { title: _('loyalty_backend.action.createProgram'), body: _jsx(Surface, { body: _jsx(RecordForm, { action: "/admin/loyalty/programs", fields: createFields, errors: errors, submit: _('loyalty_backend.action.createProgram'), submitVariant: "primary" }) }) }),
205
229
  ]) }));
206
- /**
207
- * One program, in the three questions it is set up by.
208
- *
209
- * What it is, how points are earned, and what they buy — three tabs rather than
210
- * three sections down one page, because they are edited on separate occasions
211
- * and each carries a form. Stacked, adding a reward meant scrolling past every
212
- * rule to reach the form for it.
213
- *
214
- * The settings form stays on the first tab with the identity strip above it, so
215
- * the state of the program is visible while it is being changed.
216
- */
217
- export const programDetailScreen = (_, frame, program, options) => {
218
- const rules = program.rules ?? [];
219
- const rewards = program.rewards ?? [];
220
- const here = `/admin/loyalty/programs/${String(program.id)}`;
221
- const tab = options.tab === 'rules' || options.tab === 'rewards' ? options.tab : 'overview';
222
- const rulesPane = (_jsx(Section, { title: _('loyalty_backend.rules.title'), description: _('loyalty_backend.rules.hint'), body: stack([
223
- ...(rules.length
224
- ? [
225
- dataTable(_, {
226
- rows: rules,
227
- id: (row) => String(row.id),
228
- columns: [
229
- {
230
- key: 'priority',
231
- label: _('loyalty_backend.field.priority'),
232
- cell: (row) => String(row.priority),
233
- },
234
- {
235
- key: 'mode',
236
- label: _('loyalty_backend.field.mode'),
237
- cell: (row) => labelOf(_, 'trigger', row.mode),
238
- },
239
- {
240
- key: 'points',
241
- label: _('loyalty_backend.field.pointAmount'),
242
- cell: (row) => String(row.pointAmount),
243
- },
244
- {
245
- key: 'basis',
246
- label: _('loyalty_backend.field.pointMode'),
247
- cell: (row) => labelOf(_, 'pointMode', row.pointMode),
248
- },
249
- {
250
- key: 'code',
251
- label: _('loyalty_backend.field.code'),
252
- cell: (row) => code(row.code ? String(row.code) : '—'),
253
- },
254
- {
255
- key: 'state',
256
- label: _('loyalty_backend.field.state'),
257
- cell: (row) => activeBadge(_, row.active),
258
- },
259
- ],
260
- }),
261
- ]
262
- : []),
263
- _jsx(Surface, { body: _jsx(RecordForm, { action: here, hidden: { action: 'add-rule' }, fields: options.ruleFields, submit: _('loyalty_backend.action.addRule'), submitVariant: "secondary" }) }),
264
- ]) }));
265
- const rewardsPane = (_jsx(Section, { title: _('loyalty_backend.rewards.title'), description: _('loyalty_backend.rewards.hint'), body: stack([
266
- ...(rewards.length
267
- ? [
268
- dataTable(_, {
269
- rows: rewards,
270
- id: (row) => String(row.id),
271
- columns: [
272
- {
273
- key: 'description',
274
- label: _('loyalty_backend.field.description'),
275
- cell: (row) => String(row.description),
276
- priority: 'primary',
277
- },
278
- {
279
- key: 'type',
280
- label: _('loyalty_backend.field.rewardType'),
281
- cell: (row) => labelOf(_, 'rewardType', row.rewardType),
282
- },
283
- {
284
- key: 'points',
285
- label: _('loyalty_backend.field.requiredPoints'),
286
- cell: (row) => String(row.requiredPoints),
287
- },
288
- {
289
- key: 'discount',
290
- label: _('loyalty_backend.field.discount'),
291
- cell: (row) => String(row.discount),
292
- },
293
- {
294
- key: 'state',
295
- label: _('loyalty_backend.field.state'),
296
- cell: (row) => activeBadge(_, row.active),
297
- },
298
- ],
299
- }),
300
- ]
301
- : []),
302
- _jsx(Surface, { body: _jsx(RecordForm, { action: here, hidden: { action: 'add-reward' }, fields: options.rewardFields, submit: _('loyalty_backend.action.addReward'), submitVariant: "secondary" }) }),
303
- ]) }));
304
- return (_jsx(FormScreenFrame, { translator: _, title: String(program.name), frame: frame, body: _jsx(RecordWorkspace, { kicker: _('loyalty_backend.menu.programs'), title: String(program.name), subtitle: periodOf(_, program), imageFallback: icon('gift'), status: programStateBadge(_, program), badges: [
305
- badge(labelOf(_, 'programType', program.programType), 'info', String(program.programType)),
306
- badge(labelOf(_, 'trigger', program.trigger), 'neutral', String(program.trigger)),
307
- ], summary: [
308
- {
309
- id: 'points',
310
- label: _('loyalty_backend.field.pointName'),
311
- value: String(program.pointName ?? '—'),
312
- },
313
- { id: 'rules', label: _('loyalty_backend.rules.title'), value: rules.length },
314
- { id: 'rewards', label: _('loyalty_backend.rewards.title'), value: rewards.length },
315
- ], navigation: _jsx(Tabs, { label: _('loyalty_backend.program.detail'), items: [
316
- {
317
- id: 'overview',
318
- label: _('loyalty_backend.tab.overview'),
319
- href: here,
320
- active: tab === 'overview',
321
- },
322
- {
323
- id: 'rules',
324
- label: _('loyalty_backend.tab.rules'),
325
- href: `${here}?tab=rules`,
326
- active: tab === 'rules',
327
- count: rules.length,
328
- },
329
- {
330
- id: 'rewards',
331
- label: _('loyalty_backend.tab.rewards'),
332
- href: `${here}?tab=rewards`,
333
- active: tab === 'rewards',
334
- count: rewards.length,
335
- },
336
- ] }), controller: _jsx(RecordActions, { action: here, actions: [
337
- {
338
- value: program.active ? 'archive' : 'restore',
339
- label: program.active
340
- ? _('loyalty_backend.action.archive')
341
- : _('loyalty_backend.action.restore'),
342
- variant: program.active ? 'destructive' : 'secondary',
343
- },
344
- ] }), body: tab === 'rules' ? (rulesPane) : tab === 'rewards' ? (rewardsPane) : (_jsx(Section, { title: _('loyalty_backend.program.detail'), body: _jsx(Surface, { body: _jsx(RecordForm, { action: here, hidden: { action: 'save-program' }, fields: options.programFields, errors: options.errors, submit: _('loyalty_backend.action.save'), submitVariant: "primary" }) }) })) }) }));
345
- };
346
- /**
347
- * Every wallet, and how much is sitting in them.
348
- *
349
- * The balance total is the number a finance team asks for first — it is a
350
- * liability, not a statistic — so it sits beside the count rather than under it.
351
- * Locked and expired are counted apart because an operator undoes them
352
- * differently.
353
- */
354
230
  export const walletsScreen = (_, frame, wallets, totals, createFields, errors = []) => (_jsx(ListScreenFrame, { translator: _, title: _('loyalty_backend.wallets.title'), subtitle: _('loyalty_backend.wallets.hint'), frame: frame, body: stack([
355
231
  statRow([
356
- { id: 'total', label: _('loyalty_backend.stat.wallets'), value: figure(totals.total) },
357
- { id: 'balance', label: _('loyalty_backend.stat.balance'), value: figure(totals.balance) },
232
+ {
233
+ id: 'total',
234
+ label: _('loyalty_backend.stat.wallets'),
235
+ value: figure(totals.total),
236
+ },
237
+ {
238
+ id: 'balance',
239
+ label: _('loyalty_backend.stat.balance'),
240
+ value: figure(totals.balance),
241
+ },
358
242
  {
359
243
  id: 'active',
360
244
  label: _('loyalty_backend.stat.walletsActive'),
@@ -470,13 +354,21 @@ export const walletDetailScreen = (_, frame, wallet, adjustFields, tab, errors =
470
354
  badge(labelOf(_, 'walletUnit', wallet.unit), 'neutral', String(wallet.unit)),
471
355
  ...(wallet.programName ? [badge(String(wallet.programName), 'info')] : []),
472
356
  ], summary: [
473
- { id: 'balance', label: _('loyalty_backend.field.balance'), value: figure(wallet.balance) },
357
+ {
358
+ id: 'balance',
359
+ label: _('loyalty_backend.field.balance'),
360
+ value: figure(wallet.balance),
361
+ },
474
362
  {
475
363
  id: 'available',
476
364
  label: _('loyalty_backend.field.available'),
477
365
  value: figure(wallet.available),
478
366
  },
479
- { id: 'reserved', label: _('loyalty_backend.field.reserved'), value: figure(wallet.reserved) },
367
+ {
368
+ id: 'reserved',
369
+ label: _('loyalty_backend.field.reserved'),
370
+ value: figure(wallet.reserved),
371
+ },
480
372
  ], navigation: _jsx(Tabs, { label: _('loyalty_backend.wallets.title'), items: [
481
373
  {
482
374
  id: 'overview',
@@ -668,130 +560,79 @@ export const ledgerScreen = (_, frame, rows, totals) => (_jsx(ListScreenFrame, {
668
560
  ]),
669
561
  rows.length ? ledgerTable(_, rows) : empty(_),
670
562
  ]) }));
671
- /**
672
- * The membership base, led by what it is worth.
673
- *
674
- * Dormant is counted next to active on purpose: a member whose rolling window
675
- * has emptied is a customer who stopped coming, and that is the number a
676
- * marketing team acts on. The tiers and the window settings stay on this page
677
- * below the list, because they are what the numbers above are computed from.
678
- */
679
- export const membershipsScreen = (_, frame, memberships, totals, tiers, tierFields, configFields, errors = []) => (_jsx(ListScreen, { translator: _, title: _('loyalty_backend.memberships.title'), subtitle: _('loyalty_backend.memberships.hint'), frame: frame, body: stack([
680
- statRow([
681
- { id: 'total', label: _('loyalty_backend.stat.members'), value: figure(totals.total) },
682
- {
683
- id: 'active',
684
- label: _('loyalty_backend.stat.membersActive'),
685
- value: figure(totals.active),
686
- detail: percent(totals.active, totals.total),
687
- tone: 'positive',
688
- },
689
- {
690
- id: 'dormant',
691
- label: _('loyalty_backend.stat.membersDormant'),
692
- value: figure(totals.dormant),
693
- detail: percent(totals.dormant, totals.total),
694
- tone: 'warning',
563
+ /** Company-wide tiers with a rolling spend window on each threshold. */
564
+ export const tiersScreen = (_, frame, tiers, options) => {
565
+ const page = (_jsx(ListScreenFrame, { translator: _, title: _('loyalty_backend.memberships.title'), subtitle: _('loyalty_backend.memberships.hint'), frame: {
566
+ ...frame,
567
+ extras: {
568
+ ...frame.extras,
569
+ 'topbar.end': linkButton({
570
+ label: _('loyalty_backend.action.addTier'),
571
+ href: options.createHref,
572
+ variant: 'primary',
573
+ }),
695
574
  },
696
- { id: 'points', label: _('loyalty_backend.stat.points'), value: figure(totals.points) },
697
- { id: 'spend', label: _('loyalty_backend.stat.spend'), value: figure(totals.spend) },
698
- ]),
699
- memberships.length ? membersTable(_, memberships) : empty(_),
700
- _jsx(Section, { title: _('loyalty_backend.memberships.config'), description: _('loyalty_backend.memberships.windowHint'), body: _jsx(Surface, { body: _jsx(RecordForm, { action: "/admin/loyalty/memberships", hidden: { action: 'config' }, fields: configFields, errors: errors, submit: _('loyalty_backend.action.saveConfig'), submitVariant: "primary" }) }) }),
701
- _jsx(Section, { title: _('loyalty_backend.tiers.title'), body: stack([
702
- ...(tiers.length
703
- ? [
704
- dataTable(_, {
705
- rows: tiers,
706
- id: (row) => String(row.id),
707
- columns: [
708
- {
709
- key: 'name',
710
- label: _('loyalty_backend.field.name'),
711
- cell: (row) => String(row.name),
712
- priority: 'primary',
713
- },
714
- {
715
- key: 'code',
716
- label: _('loyalty_backend.field.code'),
717
- cell: (row) => code(String(row.code)),
718
- },
719
- {
720
- key: 'minimum',
721
- label: _('loyalty_backend.field.minimumSpend'),
722
- cell: (row) => String(row.minimumSpend),
723
- align: 'end',
724
- },
725
- {
726
- key: 'cap',
727
- label: _('loyalty_backend.field.redeemPercent'),
728
- cell: (row) => `${String(row.redeemPercent)}%`,
729
- align: 'end',
730
- },
731
- {
732
- key: 'state',
733
- label: _('loyalty_backend.field.state'),
734
- cell: (row) => activeBadge(_, row.active),
735
- },
736
- ],
737
- }),
738
- ]
739
- : []),
740
- _jsx(Surface, { body: _jsx(RecordForm, { action: "/admin/loyalty/memberships", hidden: { action: 'tier' }, fields: tierFields, submit: _('loyalty_backend.action.addTier'), submitVariant: "secondary" }) }),
741
- ]) }),
742
- ]) }));
743
- /**
744
- * One member per row, ranked by what they have spent in the window.
745
- *
746
- * Points and spend are both shown because they answer different questions: spend
747
- * is what earned the tier, points are what the member can still redeem, and a
748
- * screen that shows one is regularly asked for the other.
749
- */
750
- const membersTable = (_, memberships) => dataTable(_, {
751
- rows: memberships,
752
- id: (row) => String(row.id),
753
- columns: [
754
- {
755
- key: 'partner',
756
- label: _('loyalty_backend.field.partner'),
757
- cell: (row) => String(row.partnerName ?? row.partnerId),
758
- priority: 'primary',
759
- kind: 'person',
760
- },
761
- {
762
- key: 'tier',
763
- label: _('loyalty_backend.field.tier'),
764
- cell: (row) => row.tierName ? badge(String(row.tierName), 'info', String(row.tierCode ?? row.tierId ?? '')) : '—',
765
- kind: 'status',
766
- },
767
- {
768
- key: 'points',
769
- label: _('loyalty_backend.field.points'),
770
- cell: (row) => figure(row.points),
771
- align: 'end',
772
- kind: 'number',
773
- },
774
- {
775
- key: 'spend',
776
- label: _('loyalty_backend.field.rollingSpend'),
777
- cell: (row) => figure(row.rollingSpend),
778
- align: 'end',
779
- kind: 'number',
780
- },
781
- {
782
- key: 'window',
783
- label: _('loyalty_backend.field.windowMonths'),
784
- cell: (row) => _('loyalty_backend.value.months', { count: Number(row.windowMonths ?? 0) }),
785
- priority: 'tertiary',
786
- },
787
- {
788
- key: 'refreshed',
789
- label: _('loyalty_backend.field.refreshedAt'),
790
- cell: (row) => String(row.refreshedAt),
791
- kind: 'date',
792
- },
793
- ],
794
- });
575
+ }, body: stack([
576
+ _jsx(Section, { title: _('loyalty_backend.tiers.title'), description: _('loyalty_backend.tiers.hint'), body: tiers.length
577
+ ? dataTable(_, {
578
+ rows: tiers,
579
+ id: (row) => String(row.id),
580
+ rowHref: options.tierHref,
581
+ rowLink: false,
582
+ responsive: 'stack',
583
+ columns: [
584
+ {
585
+ key: 'name',
586
+ label: _('loyalty_backend.field.name'),
587
+ cell: (row) => String(row.name),
588
+ priority: 'primary',
589
+ },
590
+ {
591
+ key: 'code',
592
+ label: _('loyalty_backend.field.code'),
593
+ cell: (row) => code(String(row.code)),
594
+ },
595
+ {
596
+ key: 'minimum',
597
+ label: _('loyalty_backend.field.minimumSpend'),
598
+ cell: (row) => figure(row.minimumSpend),
599
+ align: 'end',
600
+ },
601
+ {
602
+ key: 'window',
603
+ label: _('loyalty_backend.field.windowMonths'),
604
+ cell: (row) => figure(row.windowMonths ?? 12),
605
+ align: 'end',
606
+ },
607
+ {
608
+ key: 'sequence',
609
+ label: _('loyalty_backend.field.sequence'),
610
+ cell: (row) => figure(row.sequence),
611
+ align: 'end',
612
+ },
613
+ {
614
+ key: 'state',
615
+ label: _('loyalty_backend.field.state'),
616
+ cell: (row) => activeBadge(_, row.active),
617
+ },
618
+ ],
619
+ })
620
+ : empty(_) }),
621
+ ]) }));
622
+ if (!options.modal || !options.tierFields)
623
+ return page;
624
+ return modalWorkspace(page, _jsx(ModalSheet, { title: _(options.tier ? 'loyalty_backend.action.editTier' : 'loyalty_backend.action.addTier'), closeHref: options.closeHref, closeLabel: _('loyalty_backend.action.cancel'), presentation: "dialog", body: stack([
625
+ _jsx(RecordForm, { action: options.action, hidden: {
626
+ action: 'tier',
627
+ ...(options.tier ? { id: String(options.tier.id) } : {}),
628
+ }, fields: options.tierFields, errors: options.tierErrors, submit: _('loyalty_backend.action.save'), submitVariant: "primary", cancelHref: options.closeHref, cancelLabel: _('loyalty_backend.action.cancel') }),
629
+ ...(options.tier
630
+ ? [
631
+ _jsx(RecordForm, { action: options.action, hidden: { action: 'toggle', id: String(options.tier.id) }, fields: [], submit: _(options.tier.active ? 'loyalty_backend.action.archive' : 'loyalty_backend.action.restore'), submitVariant: "secondary" }),
632
+ ]
633
+ : []),
634
+ ]) }));
635
+ };
795
636
  export const orderLoyaltyScreen = (_, frame, options) => {
796
637
  const programs = options.result.programs ?? [];
797
638
  const action = `/admin/loyalty/orders/${options.channel}/${options.orderId}`;
@@ -806,11 +647,19 @@ export const orderLoyaltyScreen = (_, frame, options) => {
806
647
  _jsx(Notice, { title: _('loyalty_backend.validation.title'), message: options.errors.join(' · '), tone: "danger" }),
807
648
  ]
808
649
  : []),
809
- _jsx(Section, { title: _('loyalty_backend.order.code'), body: _jsx(Surface, { body: _jsx(RecordForm, { action: action, hidden: { action: 'code' }, fields: [{ name: 'code', label: _('loyalty_backend.field.code'), required: true }], submit: _('loyalty_backend.action.applyCode'), submitVariant: "primary" }) }) }),
650
+ _jsx(Section, { title: _('loyalty_backend.order.code'), body: _jsx(Surface, { body: _jsx(RecordForm, { action: action, hidden: { action: 'code' }, fields: [
651
+ {
652
+ name: 'code',
653
+ label: _('loyalty_backend.field.code'),
654
+ required: true,
655
+ },
656
+ ], submit: _('loyalty_backend.action.applyCode'), submitVariant: "primary" }) }) }),
810
657
  ...(programs.length
811
658
  ? programs.map((program) => {
812
659
  const rewards = program.rewards ?? [];
813
- return (_jsx(Section, { title: String(program.programName), description: _('loyalty_backend.order.pointsEarned', { points: String(program.points) }), body: stack([
660
+ return (_jsx(Section, { title: String(program.programName), description: _('loyalty_backend.order.pointsEarned', {
661
+ points: String(program.points),
662
+ }), body: stack([
814
663
  ...(program.ineligibleReasons && program.ineligibleReasons.length
815
664
  ? [
816
665
  _jsx(Notice, { title: _('loyalty_backend.order.ineligible'), message: program.ineligibleReasons.map(String).join(' · '), tone: "warning" }),
@@ -836,7 +685,10 @@ export const orderLoyaltyScreen = (_, frame, options) => {
836
685
  }, fields: [], submit: _('loyalty_backend.action.applyReward'), submitVariant: "primary" }) })) }),
837
686
  ]
838
687
  : [empty(_)]),
839
- _jsx(RecordForm, { action: action, hidden: { action: 'remove', programId: String(program.programId) }, fields: [], submit: _('loyalty_backend.action.removeReward'), submitVariant: "destructive" }),
688
+ _jsx(RecordForm, { action: action, hidden: {
689
+ action: 'remove',
690
+ programId: String(program.programId),
691
+ }, fields: [], submit: _('loyalty_backend.action.removeReward'), submitVariant: "destructive" }),
840
692
  ]) }));
841
693
  })
842
694
  : [empty(_)]),
@@ -908,5 +760,9 @@ export const portalScreen = (_, frame, summary) => {
908
760
  _jsx(Section, { title: _('loyalty_backend.portal.history'), body: ledger.length ? ledgerTable(_, ledger, { admin: false }) : empty(_) }),
909
761
  ]) }));
910
762
  };
911
- export const extensionLink = (_, href) => (_jsx(Surface, { tone: "subtle", body: linkButton({ label: _('loyalty_backend.action.openOrderLoyalty'), href, variant: 'secondary' }) }));
763
+ export const extensionLink = (_, href) => (_jsx(Surface, { tone: "subtle", body: linkButton({
764
+ label: _('loyalty_backend.action.openOrderLoyalty'),
765
+ href,
766
+ variant: 'secondary',
767
+ }) }));
912
768
  //# sourceMappingURL=index.js.map