@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,114 +1,81 @@
1
1
  <template>
2
- <div class="sa-tenant-detail">
3
- <header class="sa-page-head">
4
- <div>
5
- <q-btn
6
- flat
7
- dense
8
- icon="arrow_back"
9
- :label="labels.back"
10
- :to="backRoute"
11
- class="sa-tenant-detail__back"
12
- />
13
- <h1 class="sa-page-head__title">{{ data?.name ?? labels.title }}</h1>
14
- <p v-if="data" class="sa-page-head__sub">
15
- {{ labels.slug }}: <code>{{ data.slug }}</code>
16
- </p>
17
- </div>
18
- <slot name="header-actions" :data="data" :reload="load" />
19
- </header>
20
-
21
- <div class="sa-tenant-detail__body">
22
- <div v-if="loading" class="sa-tenant-detail__state">
23
- <q-spinner size="32px" /> {{ common.loading }}
24
- </div>
25
-
26
- <template v-else-if="data">
2
+ <AdminPage class="sa-tenant-detail">
3
+ <AdminHero :title="data?.name ?? labels.title">
4
+ <template v-if="data" #subtitle>
5
+ {{ labels.slug }}: <code>{{ data.slug }}</code>
6
+ </template>
7
+ <!--
8
+ Back first, then the tenant-level actions, then whatever the app
9
+ adds — the same order every other hero uses. Both are plain
10
+ `.sa-btn` buttons: a hero is where a page's own actions live, and
11
+ a detail page's "suspend" is exactly that. It used to sit on the
12
+ master-data section instead, which read as an action on that one
13
+ card rather than on the tenant.
14
+ -->
15
+ <template #actions>
16
+ <button class="sa-btn" type="button" @click="goBack">
17
+ <q-icon name="arrow_back" size="16px" />
18
+ <span>{{ labels.back }}</span>
19
+ </button>
20
+ <button
21
+ v-for="action in manifestActions"
22
+ :key="action.def.id"
23
+ :class="['sa-btn', toneClass(action.def.actionKey)]"
24
+ type="button"
25
+ @click="action.onClick"
26
+ >
27
+ <q-icon :name="iconForActionKey(action.def.actionKey)" size="16px" />
28
+ <span>{{ action.def.label }}</span>
29
+ </button>
30
+ <slot name="header-actions" :data="data" :reload="load" />
31
+ </template>
32
+ </AdminHero>
33
+
34
+ <AdminBody :loading="loading" :empty="!data">
35
+ <template v-if="data">
27
36
  <!-- Master data -->
28
- <div class="sa-card q-mb-md">
29
- <header class="sa-tenant-detail__card-head">
30
- <div>
31
- <h2 class="sa-card__title">{{ labels.masterData }}</h2>
32
- <p v-if="stammdatenSub" class="sa-tenant-detail__card-sub">
33
- {{ stammdatenSub }}
34
- </p>
35
- </div>
36
- <div class="sa-tenant-detail__card-actions">
37
- <!-- Manifest-driven default actions (Suspend/Reactivate) -->
38
- <q-btn
39
- v-for="action in manifestActions"
40
- :key="action.def.id"
41
- outline
42
- :color="toneColor(action.def.actionKey)"
43
- :icon="iconForActionKey(action.def.actionKey)"
44
- :label="action.def.label"
45
- @click="action.onClick"
46
- />
47
- <slot name="card-actions" :data="data" :reload="load" />
48
- </div>
49
- </header>
37
+ <AdminSection :title="labels.masterData" :subtitle="stammdatenSub" class="q-mb-md">
38
+ <template #actions>
39
+ <slot name="card-actions" :data="data" :reload="load" />
40
+ </template>
50
41
  <slot name="stammdaten" :data="data">
51
- <div class="sa-tenant-detail__grid">
52
- <KvBlock :label="labels.plan" :value="data.subscription?.plan ?? '—'" />
53
- <KvBlock
54
- :label="labels.status"
55
- :value="data.subscription?.status ?? '—'"
56
- />
57
- <KvBlock
58
- :label="labels.pilot"
59
- :value="data.subscription?.isPilot ? common.yes : common.no"
60
- />
61
- <KvBlock
62
- :label="labels.trialEnd"
63
- :value="formatDate(data.subscription?.trialEndsAt)"
64
- />
65
- <KvBlock
66
- :label="labels.pilotEnd"
67
- :value="formatDate(data.subscription?.pilotEndsAt)"
68
- />
69
- <KvBlock v-if="data.vatId" :label="labels.vatId" :value="data.vatId" />
42
+ <TenantMasterData
43
+ :data="data"
44
+ :labels="labels"
45
+ :format-date="formatDateResolved"
46
+ :yes="common.yes"
47
+ :no="common.no"
48
+ >
70
49
  <slot name="extra-stammdaten" :data="data" />
71
- </div>
50
+ </TenantMasterData>
72
51
  </slot>
73
- </div>
52
+ </AdminSection>
74
53
 
75
54
  <!-- Usage -->
76
- <div v-if="verbrauchFields.length > 0" class="sa-card q-mb-md">
77
- <h3 class="sa-card__title">{{ labels.usage }}</h3>
78
- <div class="sa-tenant-detail__grid">
79
- <KvBlock
80
- v-for="(field, i) in verbrauchFields"
81
- :key="field.label + i"
82
- :label="field.label"
83
- :value="resolveVerbrauch(field)"
84
- />
85
- </div>
86
- </div>
55
+ <AdminSection
56
+ v-if="verbrauchFields.length > 0"
57
+ :title="labels.usage"
58
+ class="q-mb-md"
59
+ >
60
+ <TenantUsage :data="data" :fields="verbrauchFields" />
61
+ </AdminSection>
87
62
 
88
63
  <!-- Users -->
89
- <div v-if="showUsers && data.users" class="sa-card q-mb-md">
90
- <h3 class="sa-card__title">{{ labels.users }}</h3>
91
- <q-table
92
- flat
93
- :rows="data.users"
94
- :columns="userColumns ?? defaultUserColumns"
95
- row-key="id"
96
- :pagination="{ rowsPerPage: 0 }"
97
- hide-pagination
98
- />
99
- </div>
64
+ <AdminSection v-if="showUsers && data.users" :title="labels.users" class="q-mb-md">
65
+ <TenantUsers :users="data.users" :columns="userColumns ?? defaultUserColumns" />
66
+ </AdminSection>
100
67
 
101
68
  <slot name="extra-cards" :data="data" :reload="load" />
102
69
  </template>
103
- </div>
70
+ </AdminBody>
104
71
 
105
72
  <!-- Manifest-driven action flow dialogs -->
106
73
  <MfaPromptDialog
107
- v-model="mfaState.show"
108
- :description="mfaState.description"
109
- :error="mfaState.error"
110
- @update:model-value="onMfaDialogVisibility"
111
- @confirm="onMfaConfirm"
74
+ :model-value="mfa.show.value"
75
+ :description="mfa.description.value"
76
+ :error="mfa.error.value"
77
+ @update:model-value="mfa.onVisibility"
78
+ @confirm="mfa.onConfirm"
112
79
  />
113
80
  <TenantActionConfirmDialog
114
81
  v-model="confirmState.show"
@@ -118,47 +85,31 @@
118
85
  @submit="onConfirmSubmit"
119
86
  @cancel="onConfirmCancel"
120
87
  />
121
- </div>
88
+ </AdminPage>
122
89
  </template>
123
90
 
124
91
  <script setup lang="ts">
125
92
  import { computed, onMounted, ref, toRef } from 'vue';
126
- import type { RouteLocationRaw } from 'vue-router';
93
+ import AdminBody from '../components/admin-page/AdminBody.vue';
94
+ import { useMfaPrompt } from '../vue/use-mfa-prompt.js';
95
+ import AdminHero from '../components/admin-page/AdminHero.vue';
96
+ import AdminSection from '../components/admin-page/AdminSection.vue';
97
+ import AdminPage from '../components/admin-page/AdminPage.vue';
98
+ import { useRouter, type RouteLocationRaw } from 'vue-router';
99
+ import type { TenantDetailData, VerbrauchField } from './tenant-detail/types.js';
127
100
  import type { QTableColumn } from 'quasar';
128
101
  import { useSuperAdminNotify } from '../quasar/notify.js';
129
102
  import type { AdminManifest, TenantActionDef, TenantDto } from '@saasicat/types';
130
103
  import { formatMessage } from '../client/i18n/format.js';
131
- import KvBlock from '../components/KvBlock.vue';
104
+ import TenantMasterData from './tenant-detail/TenantMasterData.vue';
105
+ import TenantUsage from './tenant-detail/TenantUsage.vue';
106
+ import TenantUsers from './tenant-detail/TenantUsers.vue';
132
107
  import MfaPromptDialog from '../components/MfaPromptDialog.vue';
133
108
  import TenantActionConfirmDialog from '../components/TenantActionConfirmDialog.vue';
134
109
  import { useSaMessages } from '../vue/use-super-admin-i18n.js';
135
110
  import { useTenantActionFlow } from '../vue/use-tenant-action-flow.js';
136
111
 
137
- export interface TenantDetailData {
138
- id: string;
139
- slug: string;
140
- name: string;
141
- isActive: boolean;
142
- vatId?: string | null;
143
- subscription?: {
144
- plan?: string | null;
145
- status?: string | null;
146
- isPilot?: boolean | null;
147
- trialEndsAt?: string | null;
148
- pilotEndsAt?: string | null;
149
- } | null;
150
- users?: Array<Record<string, unknown> & { id: string }>;
151
- /** Freely selectable usage numbers — the page renders them via `verbrauchFields`. */
152
- counts?: Record<string, number | string>;
153
- }
154
-
155
- export interface VerbrauchField {
156
- label: string;
157
- /** Lookup key in `data.counts`. */
158
- key?: string;
159
- /** Alternative: custom getter. Takes precedence over `key`. */
160
- getter?: (data: TenantDetailData) => string | number;
161
- }
112
+ export type { TenantDetailData, VerbrauchField } from './tenant-detail/types.js';
162
113
 
163
114
  const props = withDefaults(
164
115
  defineProps<{
@@ -232,55 +183,22 @@ function defaultFormatDate(value: string | null | undefined): string {
232
183
  return String(value).slice(0, 10);
233
184
  }
234
185
 
235
- function formatDate(value: string | null | undefined): string {
186
+ function formatDateResolved(value: string | null | undefined): string {
236
187
  return props.formatDate ? props.formatDate(value) : defaultFormatDate(value);
237
188
  }
238
189
 
239
- function resolveVerbrauch(field: VerbrauchField): string {
240
- if (!data.value) return '—';
241
- if (field.getter) return String(field.getter(data.value));
242
- if (field.key) {
243
- const v = data.value.counts?.[field.key];
244
- return v === undefined || v === null ? '0' : String(v);
245
- }
246
- return '—';
247
- }
248
-
249
190
  // ── Manifest-driven Action Flow (Suspend/Reactivate, Default) ──────────
250
191
  const manifestRef = toRef(() => props.manifest);
251
192
 
252
- const mfaState = ref<{ show: boolean; description: string; error: string }>({
253
- show: false,
254
- description: '',
255
- error: '',
256
- });
257
- let pendingMfaResolve: ((code: string | null) => void) | null = null;
193
+ const mfa = useMfaPrompt();
258
194
 
259
195
  function showMfaDialog(def: TenantActionDef, ctx: { row: TenantDto }): Promise<string | null> {
260
- return new Promise((resolve) => {
261
- mfaState.value = {
262
- show: true,
263
- description: formatMessage(msg.value.actions.mfaDescription, {
264
- action: def.label,
265
- tenant: ctx.row.name,
266
- }),
267
- error: '',
268
- };
269
- pendingMfaResolve = (code) => {
270
- pendingMfaResolve = null;
271
- resolve(code);
272
- };
273
- });
274
- }
275
-
276
- function onMfaConfirm(code: string): void {
277
- pendingMfaResolve?.(code);
278
- mfaState.value.show = false;
279
- }
280
-
281
- function onMfaDialogVisibility(open: boolean): void {
282
- mfaState.value.show = open;
283
- if (!open && pendingMfaResolve) pendingMfaResolve(null);
196
+ return mfa.prompt(
197
+ formatMessage(msg.value.actions.mfaDescription, {
198
+ action: def.label,
199
+ tenant: ctx.row.name,
200
+ }),
201
+ );
284
202
  }
285
203
 
286
204
  const confirmState = ref<{
@@ -348,16 +266,29 @@ const manifestActions = computed(() => {
348
266
  .map((a) => ({ def: a.def, onClick: () => void a.invoke(row) }));
349
267
  });
350
268
 
269
+ const router = useRouter();
270
+
271
+ /**
272
+ * The hero back button navigates rather than rendering a link.
273
+ *
274
+ * `backRoute` is a `RouteLocationRaw`, not an href, so there is no anchor to
275
+ * build without duplicating the router's resolution — and every other hero in
276
+ * the package uses a button for the same step.
277
+ */
278
+ function goBack(): void {
279
+ void router.push(props.backRoute);
280
+ }
281
+
351
282
  function iconForActionKey(actionKey: string): string {
352
283
  if (actionKey.endsWith('.suspend')) return 'block';
353
284
  if (actionKey.endsWith('.reactivate')) return 'play_arrow';
354
285
  return 'bolt';
355
286
  }
356
287
 
357
- function toneColor(actionKey: string): string {
358
- if (actionKey.endsWith('.suspend')) return 'negative';
359
- if (actionKey.endsWith('.reactivate')) return 'positive';
360
- return 'primary';
288
+ function toneClass(actionKey: string): string {
289
+ if (actionKey.endsWith('.suspend')) return 'sa-btn--danger';
290
+ if (actionKey.endsWith('.reactivate')) return 'sa-btn--positive';
291
+ return 'sa-btn--primary';
361
292
  }
362
293
 
363
294
  const defaultUserColumns = computed<QTableColumn[]>(() => [
@@ -392,16 +323,6 @@ const defaultUserColumns = computed<QTableColumn[]>(() => [
392
323
  </script>
393
324
 
394
325
  <style scoped>
395
- .sa-tenant-detail {
396
- min-height: calc(100vh - 56px);
397
- background: var(--sa-bg-app);
398
- }
399
- .sa-tenant-detail__back {
400
- margin-bottom: 6px;
401
- }
402
- .sa-tenant-detail__body {
403
- padding: 12px 28px 28px;
404
- }
405
326
  .sa-tenant-detail__card-head {
406
327
  display: flex;
407
328
  justify-content: space-between;
@@ -421,18 +342,6 @@ const defaultUserColumns = computed<QTableColumn[]>(() => [
421
342
  gap: 8px;
422
343
  align-items: center;
423
344
  }
424
- .sa-tenant-detail__grid {
425
- display: grid;
426
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
427
- gap: 12px;
428
- }
429
- .sa-tenant-detail__state {
430
- padding: 32px 0;
431
- color: var(--sa-muted);
432
- display: flex;
433
- align-items: center;
434
- gap: 12px;
435
- }
436
345
  .sa-tenant-detail__empty {
437
346
  color: var(--sa-muted);
438
347
  font-size: 13px;