@saasicat/ui-vue 0.19.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/{catalog-JXdKdIqG.d.cts → catalog-DJcfm5N2.d.cts} +21 -7
  2. package/dist/{catalog-JXdKdIqG.d.ts → catalog-DJcfm5N2.d.ts} +21 -7
  3. package/dist/{chunk-DPTIOQXX.js → chunk-F7NRM6KI.js} +56 -28
  4. package/dist/{chunk-RVG6LQHL.js → chunk-LY52T4N4.js} +21 -2
  5. package/dist/{chunk-ORX3ZG47.js → chunk-XMMFN2WJ.js} +74 -3
  6. package/dist/client/index.cjs +134 -29
  7. package/dist/client/index.d.cts +53 -3
  8. package/dist/client/index.d.ts +53 -3
  9. package/dist/client/index.js +14 -2
  10. package/dist/index.cjs +321 -162
  11. package/dist/index.d.cts +30 -8
  12. package/dist/index.d.ts +30 -8
  13. package/dist/index.js +176 -133
  14. package/dist/quasar/index.cjs +78 -29
  15. package/dist/quasar/index.d.cts +2 -2
  16. package/dist/quasar/index.d.ts +2 -2
  17. package/dist/quasar/index.js +6 -2
  18. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  19. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  20. package/dist/{use-super-admin-i18n-BlqcD0N7.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
  21. package/dist/{use-super-admin-i18n-1nCOkvgi.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
  22. package/package.json +18 -8
  23. package/src/client/i18n/messages/bundles.ts +0 -2
  24. package/src/client/i18n/messages/common.ts +24 -0
  25. package/src/client/i18n/messages/discovery.ts +2 -2
  26. package/src/client/i18n/messages/marketing.ts +2 -4
  27. package/src/client/i18n/messages/plan-detail.ts +0 -4
  28. package/src/client/i18n/messages/plans.ts +20 -8
  29. package/src/client/i18n/messages/tenants.ts +0 -2
  30. package/src/client/index.ts +1 -0
  31. package/src/client/manifest-loader.ts +1 -1
  32. package/src/client/resolve-plans.ts +144 -0
  33. package/src/components/BundleVersionPublishDialog.vue +6 -6
  34. package/src/components/KvBlock.vue +4 -4
  35. package/src/components/MarketingPromotionsTab.vue +34 -48
  36. package/src/components/MfaPromptDialog.vue +8 -2
  37. package/src/components/TenantActionConfirmDialog.vue +4 -4
  38. package/src/components/VersionDiffPreview.vue +7 -7
  39. package/src/components/admin-page/AdminBody.vue +40 -0
  40. package/src/components/admin-page/AdminFilters.vue +12 -0
  41. package/src/components/admin-page/AdminHero.vue +49 -0
  42. package/src/components/admin-page/AdminKpi.vue +84 -0
  43. package/src/components/admin-page/AdminPage.vue +16 -0
  44. package/src/components/admin-page/AdminPaginator.vue +195 -0
  45. package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
  46. package/src/components/admin-page/AdminSection.vue +53 -0
  47. package/src/components/admin-page/AdminStatistics.vue +40 -0
  48. package/src/components/admin-page/AdminTable.vue +175 -0
  49. package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
  50. package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
  51. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
  52. package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
  53. package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
  54. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
  55. package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
  56. package/src/components/dialogs/PilotCreateDialog.vue +12 -12
  57. package/src/components/dialogs/PilotEditDialog.vue +7 -7
  58. package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
  59. package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
  60. package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
  61. package/src/components/plan/PlanGrid.vue +1 -1
  62. package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
  63. package/src/components/plan-detail/PlanAuditLog.vue +7 -8
  64. package/src/components/plan-detail/PlanDetail.vue +95 -292
  65. package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
  66. package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
  67. package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
  68. package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
  69. package/src/components/plan-list/PlanList.vue +206 -457
  70. package/src/components/plan-matrix/PlanMatrix.vue +66 -183
  71. package/src/components/plan-review/PlanReview.vue +191 -275
  72. package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
  73. package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
  74. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
  75. package/src/index.ts +1 -0
  76. package/src/pages-standard/AdminLayout.vue +38 -12
  77. package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
  78. package/src/pages-standard/AuditPage.vue +65 -109
  79. package/src/pages-standard/BundlesPage.vue +146 -179
  80. package/src/pages-standard/DashboardPage.vue +59 -154
  81. package/src/pages-standard/DiscoveryPage.vue +172 -227
  82. package/src/pages-standard/EmailHistoryPage.vue +118 -159
  83. package/src/pages-standard/MarketingCatalogPage.vue +399 -440
  84. package/src/pages-standard/PilotsPage.vue +69 -117
  85. package/src/pages-standard/PlansPage.vue +240 -112
  86. package/src/pages-standard/PlatformEmailPage.vue +56 -91
  87. package/src/pages-standard/PromoCodeDetailPage.vue +49 -74
  88. package/src/pages-standard/PromoCodesPage.vue +95 -128
  89. package/src/pages-standard/SubscriptionsPage.vue +19 -52
  90. package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
  91. package/src/pages-standard/SuperAdminSetupWizard.vue +22 -18
  92. package/src/pages-standard/TenantDetailPage.vue +100 -191
  93. package/src/pages-standard/TenantsPage.vue +190 -295
  94. package/src/pages-standard/UsersPage.vue +89 -157
  95. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
  96. package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
  97. package/src/pages-standard/bundles-page/BundlesToolbar.vue +63 -0
  98. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
  99. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
  100. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
  101. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
  102. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
  103. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +111 -73
  104. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +43 -62
  105. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
  106. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
  107. package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
  108. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
  109. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
  110. package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
  111. package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
  112. package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
  113. package/src/pages-standard/sa-theme.css +552 -86
  114. package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
  115. package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
  116. package/src/pages-standard/tenant-detail/TenantUsers.vue +15 -0
  117. package/src/pages-standard/tenant-detail/types.ts +38 -0
  118. package/src/pages-standard/tenants/StatusPill.vue +2 -2
  119. package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
  120. package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
  121. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  122. package/src/pages-tenant/TenantPlanSection.vue +12 -20
  123. package/src/quasar/create-super-admin-app.ts +4 -0
  124. package/src/vue/entitlement-provider.ts +0 -1
  125. package/src/vue/feature-router-guard.ts +0 -1
  126. package/src/vue/super-admin-context.ts +106 -1
  127. package/src/vue/use-api-list.ts +2 -1
  128. package/src/vue/use-mfa-prompt.ts +72 -0
  129. package/src/vue/use-pagination.ts +63 -0
  130. package/src/vue/use-plans.ts +1 -2
  131. package/src/vue/use-sign-out.ts +90 -0
  132. package/src/vue/use-tenant-billing.ts +1 -1
  133. package/src/vue/use-tenant-manifest.ts +0 -1
  134. package/src/components/KpiCard.vue +0 -53
  135. package/src/components/plan-cockpit/PlanCockpit.vue +0 -1012
  136. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
  137. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -100
  138. package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
  139. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
  140. package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
  141. package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -251
  142. package/src/components/plan-cockpit/types.ts +0 -72
  143. package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
  144. package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
  145. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +0 -34
@@ -1,45 +1,41 @@
1
1
  <template>
2
- <div class="sa-pemail">
3
- <header class="sa-page-head">
4
- <div>
5
- <h1 class="sa-page-head__title">{{ resolvedTitle }}</h1>
6
- <p class="sa-page-head__sub">{{ msg.provider.subtitle }}</p>
7
- </div>
8
- <div class="sa-page-head__actions">
9
- <q-btn
2
+ <AdminPage class="sa-pemail">
3
+ <AdminHero :title="resolvedTitle" :subtitle="msg.provider.subtitle">
4
+ <template #actions>
5
+ <button
10
6
  v-if="rows.length === 0"
11
- unelevated
12
- color="primary"
13
- icon="add"
14
- :label="msg.sender"
7
+ class="sa-btn sa-btn--primary"
8
+ type="button"
15
9
  @click="openCreate"
16
- />
17
- <q-btn flat icon="refresh" :label="common.reload" @click="reload" />
18
- </div>
19
- </header>
10
+ >
11
+ <q-icon name="add" size="16px" />
12
+ <span>{{ msg.sender }}</span>
13
+ </button>
14
+ <AdminRefreshBtn :loading="loading" @refresh="reload" />
15
+ </template>
16
+ </AdminHero>
20
17
 
21
- <div class="sa-pemail__card">
22
- <q-table
23
- flat
24
- :rows="rows"
25
- :columns="columns"
26
- row-key="id"
27
- :loading="loading"
28
- :pagination="{ rowsPerPage: 0 }"
29
- hide-pagination
30
- >
31
- <template #body-cell-active="{ row }">
32
- <q-td>
33
- <q-badge
34
- :color="row.active ? 'positive' : 'grey'"
35
- :label="
36
- row.active ? msg.provider.statusActive : msg.provider.statusInactive
37
- "
38
- />
39
- </q-td>
40
- </template>
41
- <template #body-cell-actions="{ row }">
42
- <q-td>
18
+ <AdminBody>
19
+ <AdminSection class="sa-pemail__card">
20
+ <AdminTable
21
+ :rows="rows"
22
+ :columns="columns"
23
+ :loading="loading"
24
+ storage-key="platform-email"
25
+ >
26
+ <template #body-cell-active="{ row }">
27
+ <q-td>
28
+ <q-badge
29
+ :color="row.active ? 'positive' : 'grey'"
30
+ :label="
31
+ row.active
32
+ ? msg.provider.statusActive
33
+ : msg.provider.statusInactive
34
+ "
35
+ />
36
+ </q-td>
37
+ </template>
38
+ <template #row-actions="{ row }">
43
39
  <q-btn
44
40
  flat
45
41
  dense
@@ -64,10 +60,10 @@
64
60
  :title="common.delete"
65
61
  @click="onDelete(row)"
66
62
  />
67
- </q-td>
68
- </template>
69
- </q-table>
70
- </div>
63
+ </template>
64
+ </AdminTable>
65
+ </AdminSection>
66
+ </AdminBody>
71
67
 
72
68
  <q-dialog v-model="showForm">
73
69
  <q-card class="sa-pemail__dialog">
@@ -185,19 +181,26 @@
185
181
 
186
182
  <MfaPromptDialog
187
183
  v-if="requireMfaForWrite"
188
- :model-value="showMfa"
189
- :description="mfaDescription"
190
- :error="mfaError"
184
+ :model-value="mfa.show.value"
185
+ :description="mfa.description.value"
186
+ :error="mfa.error.value"
191
187
  :setup-hint="mfaSetupHint"
192
- @update:model-value="onMfaDialogVisibility"
193
- @confirm="onMfaConfirm"
188
+ @update:model-value="mfa.onVisibility"
189
+ @confirm="mfa.onConfirm"
194
190
  />
195
- </div>
191
+ </AdminPage>
196
192
  </template>
197
193
 
198
194
  <script setup lang="ts">
195
+ import AdminTable from '../components/admin-page/AdminTable.vue';
199
196
  import { computed, reactive, ref } from 'vue';
197
+ import { useMfaPrompt } from '../vue/use-mfa-prompt.js';
200
198
  import { useQuasar } from 'quasar';
199
+ import AdminRefreshBtn from '../components/admin-page/AdminRefreshBtn.vue';
200
+ import AdminBody from '../components/admin-page/AdminBody.vue';
201
+ import AdminHero from '../components/admin-page/AdminHero.vue';
202
+ import AdminSection from '../components/admin-page/AdminSection.vue';
203
+ import AdminPage from '../components/admin-page/AdminPage.vue';
201
204
  import { useSuperAdminNotify } from '../quasar/notify.js';
202
205
  import MfaPromptDialog from '../components/MfaPromptDialog.vue';
203
206
  import { formatMessage } from '../client/i18n/format.js';
@@ -265,7 +268,6 @@ const columns = computed(() => [
265
268
  align: 'left' as const,
266
269
  },
267
270
  { name: 'active', label: common.value.status, field: 'active', align: 'left' as const },
268
- { name: 'actions', label: '', field: 'id' as never, align: 'right' as 'left' },
269
271
  ]);
270
272
 
271
273
  const showForm = ref(false);
@@ -279,10 +281,7 @@ const testResult = ref<PlatformEmailTestResult | null>(null);
279
281
  const testing = ref(false);
280
282
 
281
283
  // MFA loop analogous to UsersPage (promise-resolver pattern).
282
- const showMfa = ref(false);
283
- const mfaError = ref('');
284
- const mfaDescription = ref('');
285
- let pendingMfaResolve: ((code: string | null) => void) | null = null;
284
+ const mfa = useMfaPrompt();
286
285
 
287
286
  function emptyForm(): PlatformEmailWriteInput {
288
287
  return {
@@ -298,29 +297,6 @@ function emptyForm(): PlatformEmailWriteInput {
298
297
  };
299
298
  }
300
299
 
301
- function promptMfa(description: string): Promise<string | null> {
302
- return new Promise((resolve) => {
303
- mfaDescription.value = description;
304
- mfaError.value = '';
305
- showMfa.value = true;
306
- pendingMfaResolve = (code) => {
307
- pendingMfaResolve = null;
308
- resolve(code);
309
- };
310
- });
311
- }
312
-
313
- function onMfaConfirm(code: string): void {
314
- pendingMfaResolve?.(code);
315
- }
316
-
317
- function onMfaDialogVisibility(open: boolean): void {
318
- showMfa.value = open;
319
- if (!open && pendingMfaResolve) {
320
- pendingMfaResolve(null);
321
- }
322
- }
323
-
324
300
  function errMsg(err: unknown): string {
325
301
  return (
326
302
  (err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
@@ -381,19 +357,19 @@ async function runWrite(
381
357
  }
382
358
  }
383
359
  for (;;) {
384
- const code = await promptMfa(label);
360
+ const code = await mfa.prompt(label);
385
361
  if (code === null) return false;
386
362
  try {
387
363
  await invoke(code);
388
- showMfa.value = false;
364
+ mfa.show.value = false;
389
365
  return true;
390
366
  } catch (err) {
391
367
  const status = (err as { response?: { status?: number } })?.response?.status;
392
368
  if (status === 401) {
393
- mfaError.value = shell.value.mfa.invalidCode;
369
+ mfa.error.value = shell.value.mfa.invalidCode;
394
370
  continue;
395
371
  }
396
- showMfa.value = false;
372
+ mfa.show.value = false;
397
373
  notify('negative', errMsg(err));
398
374
  return false;
399
375
  }
@@ -484,17 +460,6 @@ async function onTest(): Promise<void> {
484
460
  </script>
485
461
 
486
462
  <style scoped>
487
- .sa-pemail {
488
- min-height: calc(100vh - 56px);
489
- background: var(--sa-bg-app, #f1f5f9);
490
- padding: 20px 28px 28px;
491
- }
492
- .sa-pemail__card {
493
- background: #fff;
494
- border: 1px solid var(--sa-border, #e2e8f0);
495
- border-radius: 12px;
496
- overflow: hidden;
497
- }
498
463
  .sa-pemail__dialog {
499
464
  min-width: 420px;
500
465
  }
@@ -1,7 +1,7 @@
1
1
  <template>
2
- <div class="sa-promo-detail">
3
- <header class="sa-page-head">
4
- <div>
2
+ <AdminPage class="sa-promo-detail">
3
+ <AdminHero :title="data?.promo.code ?? labels.promoCode">
4
+ <template #before-title>
5
5
  <q-btn
6
6
  flat
7
7
  dense
@@ -10,73 +10,65 @@
10
10
  :to="backRoute"
11
11
  class="sa-promo-detail__back"
12
12
  />
13
- <h1 class="sa-page-head__title">{{ data?.promo.code ?? labels.promoCode }}</h1>
14
- <p class="sa-page-head__sub">
15
- <template v-if="data">
16
- {{ data.promo.valueType }} · {{ data.promo.value }} ·
17
- <q-badge
18
- :color="resolveStatusColor(String(data.promo.status))"
19
- :label="String(data.promo.status)"
20
- />
21
- </template>
22
- <template v-else>—</template>
23
- </p>
24
- </div>
25
- <div class="sa-promo-detail__head-actions">
26
- <q-btn
13
+ </template>
14
+ <template #subtitle>
15
+ <template v-if="data">
16
+ {{ data.promo.valueType }} · {{ data.promo.value }} ·
17
+ <q-badge
18
+ :color="resolveStatusColor(String(data.promo.status))"
19
+ :label="String(data.promo.status)"
20
+ />
21
+ </template>
22
+ <template v-else>—</template>
23
+ </template>
24
+ <template #actions>
25
+ <button
27
26
  v-if="editSubmit && data"
28
- color="primary"
29
- unelevated
30
- icon="edit"
31
- :label="labels.edit"
27
+ class="sa-btn sa-btn--primary"
28
+ type="button"
32
29
  @click="openEdit"
33
- />
30
+ >
31
+ <q-icon name="edit" size="16px" />
32
+ <span>{{ labels.edit }}</span>
33
+ </button>
34
34
  <slot name="header-actions" />
35
- </div>
36
- </header>
37
-
38
- <div class="sa-promo-detail__body">
39
- <div v-if="loading" class="sa-promo-detail__state">
40
- <q-spinner size="32px" /> {{ msg.detail.loading }}
41
- </div>
42
- <div v-else-if="!data" class="sa-promo-detail__state">{{ labels.empty }}</div>
35
+ </template>
36
+ </AdminHero>
43
37
 
44
- <template v-else>
45
- <div class="sa-card sa-promo-detail__section">
46
- <div class="sa-promo-detail__section-head">{{ labels.config }}</div>
38
+ <AdminBody
39
+ :loading="loading"
40
+ :empty="!data"
41
+ :loading-text="msg.detail.loading"
42
+ :empty-text="labels.empty"
43
+ >
44
+ <template v-if="data">
45
+ <AdminSection :title="labels.config">
47
46
  <slot name="config" :promo="data.promo">
48
47
  <pre class="sa-promo-detail__kv">{{ resolveFormatPromo(data.promo) }}</pre>
49
48
  </slot>
50
- </div>
49
+ </AdminSection>
51
50
 
52
- <div class="sa-card sa-promo-detail__section">
53
- <div class="sa-promo-detail__section-head">{{ labels.stats }}</div>
51
+ <AdminSection :title="labels.stats">
54
52
  <slot name="stats" :stats="data.stats">
55
53
  <pre class="sa-promo-detail__kv">{{
56
54
  JSON.stringify(data.stats, null, 2)
57
55
  }}</pre>
58
56
  </slot>
59
- </div>
57
+ </AdminSection>
60
58
 
61
- <div class="sa-card sa-promo-detail__section">
62
- <div class="sa-promo-detail__section-head">
63
- {{ labels.redemptions }} ({{ data.redemptions.length }})
64
- </div>
59
+ <AdminSection :title="`${labels.redemptions} (${data.redemptions.length})`">
65
60
  <slot name="redemptions" :redemptions="data.redemptions">
66
- <q-table
67
- flat
68
- :rows="data.redemptions"
61
+ <AdminTable
62
+ :rows="redemptionRows"
69
63
  :columns="redemptionsColumns ?? defaultColumns"
70
- row-key="id"
71
- :pagination="{ rowsPerPage: 0 }"
72
- hide-pagination
64
+ storage-key="promo-code-redemptions"
73
65
  />
74
66
  </slot>
75
- </div>
67
+ </AdminSection>
76
68
 
77
69
  <slot name="extra-sections" :data="data" />
78
70
  </template>
79
- </div>
71
+ </AdminBody>
80
72
 
81
73
  <PromoCodeEditDialog
82
74
  v-if="editSubmit"
@@ -86,11 +78,16 @@
86
78
  :submit="editSubmit"
87
79
  @updated="onEditUpdated"
88
80
  />
89
- </div>
81
+ </AdminPage>
90
82
  </template>
91
83
 
92
84
  <script setup lang="ts">
85
+ import AdminTable from '../components/admin-page/AdminTable.vue';
93
86
  import { computed, onMounted, ref } from 'vue';
87
+ import AdminBody from '../components/admin-page/AdminBody.vue';
88
+ import AdminHero from '../components/admin-page/AdminHero.vue';
89
+ import AdminSection from '../components/admin-page/AdminSection.vue';
90
+ import AdminPage from '../components/admin-page/AdminPage.vue';
94
91
  import type { RouteLocationRaw } from 'vue-router';
95
92
  import type { QTableColumn } from 'quasar';
96
93
  import PromoCodeEditDialog, {
@@ -280,13 +277,11 @@ const defaultColumns = computed<QTableColumn[]>(() => [
280
277
  align: 'left',
281
278
  },
282
279
  ]);
280
+
281
+ const redemptionRows = computed(() => data.value?.redemptions ?? []);
283
282
  </script>
284
283
 
285
284
  <style scoped>
286
- .sa-promo-detail {
287
- min-height: calc(100vh - 56px);
288
- background: var(--sa-bg-app);
289
- }
290
285
  .sa-promo-detail__back {
291
286
  margin-bottom: 6px;
292
287
  }
@@ -297,23 +292,10 @@ const defaultColumns = computed<QTableColumn[]>(() => [
297
292
  margin-left: auto;
298
293
  }
299
294
  .sa-promo-detail__body {
300
- padding: 12px 28px 28px;
301
295
  display: flex;
302
296
  flex-direction: column;
303
297
  gap: 14px;
304
298
  }
305
- .sa-promo-detail__section {
306
- padding: 16px 18px;
307
- }
308
- .sa-promo-detail__section-head {
309
- font-size: 11px;
310
- font-weight: 700;
311
- letter-spacing: 0.08em;
312
- color: var(--sa-muted);
313
- text-transform: uppercase;
314
- margin-bottom: 10px;
315
- font-family: var(--sa-font-head);
316
- }
317
299
  .sa-promo-detail__kv {
318
300
  background: #fafbfc;
319
301
  border: 1px solid var(--sa-border);
@@ -324,11 +306,4 @@ const defaultColumns = computed<QTableColumn[]>(() => [
324
306
  overflow-x: auto;
325
307
  font-family: var(--sa-font-mono);
326
308
  }
327
- .sa-promo-detail__state {
328
- padding: 24px;
329
- display: flex;
330
- align-items: center;
331
- gap: 12px;
332
- color: var(--sa-muted);
333
- }
334
309
  </style>