@saasicat/ui-vue 0.5.0 → 0.7.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 (186) hide show
  1. package/README.md +17 -0
  2. package/dist/{chunk-EH7TOD4H.js → chunk-A3W2N26K.js} +73 -77
  3. package/dist/{chunk-3YP2ZAOD.js → chunk-QZOSDCNP.js} +36 -1
  4. package/dist/chunk-X5SQ5HMB.js +3231 -0
  5. package/dist/client/index.cjs +3279 -76
  6. package/dist/client/index.d.cts +31 -5
  7. package/dist/client/index.d.ts +31 -5
  8. package/dist/client/index.js +23 -5
  9. package/dist/index.cjs +3709 -465
  10. package/dist/index.d.cts +37 -54
  11. package/dist/index.d.ts +37 -54
  12. package/dist/index.js +347 -311
  13. package/dist/messages-7b0P8W75.d.cts +1652 -0
  14. package/dist/messages-7b0P8W75.d.ts +1652 -0
  15. package/dist/quasar/index.cjs +3185 -4
  16. package/dist/quasar/index.d.cts +11 -2
  17. package/dist/quasar/index.d.ts +11 -2
  18. package/dist/quasar/index.js +8 -3
  19. package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
  20. package/dist/testing-e2e/admin-pages-suite.js +14 -11
  21. package/dist/{ui-notify-COUzXEQ4.d.ts → use-super-admin-i18n-B3v3-SWZ.d.ts} +35 -3
  22. package/dist/{ui-notify-D_eAAwRh.d.cts → use-super-admin-i18n-DMktRVEt.d.cts} +35 -3
  23. package/package.json +2 -2
  24. package/src/client/action-registry.ts +4 -4
  25. package/src/client/batch-column-fetcher.ts +4 -4
  26. package/src/client/boot-loader.ts +3 -3
  27. package/src/client/i18n/currency.ts +42 -0
  28. package/src/client/i18n/define.ts +59 -0
  29. package/src/client/i18n/format.ts +12 -0
  30. package/src/client/i18n/index.ts +14 -0
  31. package/src/client/i18n/locale.ts +15 -0
  32. package/src/client/i18n/messages/audit.ts +42 -0
  33. package/src/client/i18n/messages/bundles.ts +459 -0
  34. package/src/client/i18n/messages/common.ts +147 -0
  35. package/src/client/i18n/messages/dashboard.ts +16 -0
  36. package/src/client/i18n/messages/discovery.ts +229 -0
  37. package/src/client/i18n/messages/email.ts +125 -0
  38. package/src/client/i18n/messages/marketing.ts +270 -0
  39. package/src/client/i18n/messages/nav.ts +72 -0
  40. package/src/client/i18n/messages/pilots.ts +192 -0
  41. package/src/client/i18n/messages/plan-detail.ts +266 -0
  42. package/src/client/i18n/messages/plan-editor.ts +177 -0
  43. package/src/client/i18n/messages/plan-versions.ts +251 -0
  44. package/src/client/i18n/messages/plans.ts +503 -0
  45. package/src/client/i18n/messages/promos.ts +202 -0
  46. package/src/client/i18n/messages/shell.ts +145 -0
  47. package/src/client/i18n/messages/tenants.ts +104 -0
  48. package/src/client/i18n/messages/users.ts +78 -0
  49. package/src/client/i18n/messages.ts +84 -0
  50. package/src/client/index.ts +2 -0
  51. package/src/client/manifest-loader.ts +4 -4
  52. package/src/client/nav-builder.ts +44 -51
  53. package/src/client/plan-versions-catalog.ts +42 -30
  54. package/src/components/BundleVersionPublishDialog.vue +35 -24
  55. package/src/components/MarketingPromotionsTab.vue +112 -59
  56. package/src/components/MfaPromptDialog.vue +11 -11
  57. package/src/components/TenantActionConfirmDialog.vue +19 -17
  58. package/src/components/VersionDiffPreview.vue +72 -43
  59. package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
  60. package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
  61. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
  62. package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
  63. package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
  64. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
  65. package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
  66. package/src/components/bundle-editor/bundle-version-status.ts +29 -29
  67. package/src/components/dialogs/PilotCreateDialog.vue +58 -63
  68. package/src/components/dialogs/PilotEditDialog.vue +40 -34
  69. package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
  70. package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
  71. package/src/components/dialogs/types.ts +3 -13
  72. package/src/components/plan/PlanCycleToggle.vue +20 -9
  73. package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
  74. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
  75. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
  76. package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
  77. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
  78. package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
  79. package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
  80. package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
  81. package/src/components/plan-detail/PlanAuditLog.vue +11 -6
  82. package/src/components/plan-detail/PlanDetail.vue +21 -23
  83. package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
  84. package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
  85. package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
  86. package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
  87. package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
  88. package/src/components/plan-list/PlanList.vue +99 -57
  89. package/src/components/plan-matrix/PlanMatrix.vue +85 -38
  90. package/src/components/plan-review/PlanReview.vue +105 -80
  91. package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
  92. package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
  93. package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
  94. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
  95. package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
  96. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
  97. package/src/index.ts +1 -1
  98. package/src/pages-standard/AdminLayout.vue +42 -17
  99. package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
  100. package/src/pages-standard/AuditPage.vue +58 -19
  101. package/src/pages-standard/BundlesPage.vue +29 -18
  102. package/src/pages-standard/DashboardPage.vue +29 -15
  103. package/src/pages-standard/DiscoveryPage.vue +40 -28
  104. package/src/pages-standard/EmailHistoryPage.vue +92 -59
  105. package/src/pages-standard/MarketingCatalogPage.vue +35 -16
  106. package/src/pages-standard/PilotsPage.vue +57 -41
  107. package/src/pages-standard/PlanVersionsPage.vue +7 -3
  108. package/src/pages-standard/PlansPage.vue +89 -57
  109. package/src/pages-standard/PlatformEmailPage.vue +115 -50
  110. package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
  111. package/src/pages-standard/PromoCodesPage.vue +46 -35
  112. package/src/pages-standard/SubscriptionsPage.vue +14 -9
  113. package/src/pages-standard/SuperAdminLoginPage.vue +16 -13
  114. package/src/pages-standard/SuperAdminSetupWizard.vue +51 -47
  115. package/src/pages-standard/TenantDetailPage.vue +50 -36
  116. package/src/pages-standard/TenantsPage.vue +54 -41
  117. package/src/pages-standard/UsersPage.vue +66 -42
  118. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
  119. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
  120. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
  121. package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
  122. package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
  123. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
  124. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
  125. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
  126. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
  127. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
  128. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
  129. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
  130. package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
  131. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
  132. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
  133. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
  134. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
  135. package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
  136. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +41 -25
  137. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +45 -26
  138. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +7 -5
  139. package/src/pages-standard/plan-versions/PlanVersionsList.vue +39 -16
  140. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +24 -12
  141. package/src/pages-standard/plan-versions/format.ts +33 -17
  142. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
  143. package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
  144. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
  145. package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
  146. package/src/pages-standard/tenants/format.ts +3 -2
  147. package/src/pages-tenant/MySubscriptionBundlesPage.vue +72 -37
  148. package/src/pages-tenant/PlanChangeWizard.vue +8 -4
  149. package/src/pages-tenant/TenantPlanSection.vue +17 -13
  150. package/src/pages-tenant/default-i18n.ts +217 -1
  151. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +8 -6
  152. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +9 -10
  153. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
  154. package/src/quasar/create-super-admin-app.ts +19 -0
  155. package/src/testing-e2e/admin-pages-suite.ts +14 -11
  156. package/src/vue/project-page-host.ts +5 -3
  157. package/src/vue/use-audit-entries.ts +3 -3
  158. package/src/vue/use-bulk-publish.ts +6 -4
  159. package/src/vue/use-bundle-versions-map.ts +6 -4
  160. package/src/vue/use-bundles.ts +13 -13
  161. package/src/vue/use-catalog-entries.ts +13 -10
  162. package/src/vue/use-discovery.ts +8 -5
  163. package/src/vue/use-entitlement.ts +3 -3
  164. package/src/vue/use-live-plan-versions.ts +4 -4
  165. package/src/vue/use-marketing-projections.ts +9 -6
  166. package/src/vue/use-nav.ts +8 -2
  167. package/src/vue/use-plan-editor.ts +2 -2
  168. package/src/vue/use-plan-versions.ts +3 -3
  169. package/src/vue/use-plans.ts +12 -12
  170. package/src/vue/use-platform-tenant-actions.ts +9 -6
  171. package/src/vue/use-promotions.ts +9 -10
  172. package/src/vue/use-subscription-draft.ts +2 -3
  173. package/src/vue/use-super-admin-context.ts +3 -3
  174. package/src/vue/use-super-admin-i18n.ts +82 -0
  175. package/src/vue/use-tenant-action-flow.ts +11 -6
  176. package/src/vue/use-tenant-manifest.ts +1 -3
  177. package/src/vue/use-tenant-subscription-bundles.ts +4 -4
  178. package/src/vue/use-tenants.ts +3 -3
  179. package/dist/action-registry-CQ2wL3R-.d.cts +0 -85
  180. package/dist/action-registry-CQ2wL3R-.d.ts +0 -85
  181. package/dist/chunk-LVGUSD3T.js +0 -26
  182. package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
  183. package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
  184. package/src/pages-standard/BusinessTypesPage.vue +0 -662
  185. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
  186. package/src/vue/use-business-types.ts +0 -269
@@ -6,13 +6,13 @@
6
6
  flat
7
7
  dense
8
8
  icon="arrow_back"
9
- :label="backLabel"
9
+ :label="labels.back"
10
10
  :to="backRoute"
11
11
  class="sa-tenant-detail__back"
12
12
  />
13
- <h1 class="sa-page-head__title">{{ data?.name ?? titleLabel }}</h1>
13
+ <h1 class="sa-page-head__title">{{ data?.name ?? labels.title }}</h1>
14
14
  <p v-if="data" class="sa-page-head__sub">
15
- {{ slugLabel }}: <code>{{ data.slug }}</code>
15
+ {{ labels.slug }}: <code>{{ data.slug }}</code>
16
16
  </p>
17
17
  </div>
18
18
  <slot name="header-actions" :data="data" :reload="load" />
@@ -20,7 +20,7 @@
20
20
 
21
21
  <div class="sa-tenant-detail__body">
22
22
  <div v-if="loading" class="sa-tenant-detail__state">
23
- <q-spinner size="32px" /> wird geladen…
23
+ <q-spinner size="32px" /> {{ common.loading }}
24
24
  </div>
25
25
 
26
26
  <template v-else-if="data">
@@ -28,7 +28,7 @@
28
28
  <div class="sa-card q-mb-md">
29
29
  <header class="sa-tenant-detail__card-head">
30
30
  <div>
31
- <h2 class="sa-card__title">{{ stammdatenLabel }}</h2>
31
+ <h2 class="sa-card__title">{{ labels.masterData }}</h2>
32
32
  <p v-if="stammdatenSub" class="sa-tenant-detail__card-sub">
33
33
  {{ stammdatenSub }}
34
34
  </p>
@@ -49,24 +49,24 @@
49
49
  </header>
50
50
  <slot name="stammdaten" :data="data">
51
51
  <div class="sa-tenant-detail__grid">
52
- <KvBlock :label="planLabel" :value="data.subscription?.plan ?? '—'" />
52
+ <KvBlock :label="labels.plan" :value="data.subscription?.plan ?? '—'" />
53
53
  <KvBlock
54
- :label="statusLabel"
54
+ :label="labels.status"
55
55
  :value="data.subscription?.status ?? '—'"
56
56
  />
57
57
  <KvBlock
58
- :label="pilotLabel"
59
- :value="data.subscription?.isPilot ? 'ja' : 'nein'"
58
+ :label="labels.pilot"
59
+ :value="data.subscription?.isPilot ? common.yes : common.no"
60
60
  />
61
61
  <KvBlock
62
- :label="trialEndLabel"
62
+ :label="labels.trialEnd"
63
63
  :value="formatDate(data.subscription?.trialEndsAt)"
64
64
  />
65
65
  <KvBlock
66
- :label="pilotEndLabel"
66
+ :label="labels.pilotEnd"
67
67
  :value="formatDate(data.subscription?.pilotEndsAt)"
68
68
  />
69
- <KvBlock v-if="data.vatId" :label="vatIdLabel" :value="data.vatId" />
69
+ <KvBlock v-if="data.vatId" :label="labels.vatId" :value="data.vatId" />
70
70
  <slot name="extra-stammdaten" :data="data" />
71
71
  </div>
72
72
  </slot>
@@ -74,7 +74,7 @@
74
74
 
75
75
  <!-- Usage -->
76
76
  <div v-if="verbrauchFields.length > 0" class="sa-card q-mb-md">
77
- <h3 class="sa-card__title">{{ verbrauchLabel }}</h3>
77
+ <h3 class="sa-card__title">{{ labels.usage }}</h3>
78
78
  <div class="sa-tenant-detail__grid">
79
79
  <KvBlock
80
80
  v-for="(field, i) in verbrauchFields"
@@ -87,11 +87,11 @@
87
87
 
88
88
  <!-- Users -->
89
89
  <div v-if="showUsers && data.users" class="sa-card q-mb-md">
90
- <h3 class="sa-card__title">{{ usersLabel }}</h3>
90
+ <h3 class="sa-card__title">{{ labels.users }}</h3>
91
91
  <q-table
92
92
  flat
93
93
  :rows="data.users"
94
- :columns="userColumns ?? DEFAULT_USER_COLUMNS"
94
+ :columns="userColumns ?? defaultUserColumns"
95
95
  row-key="id"
96
96
  :pagination="{ rowsPerPage: 0 }"
97
97
  hide-pagination
@@ -127,9 +127,11 @@ import type { RouteLocationRaw } from 'vue-router';
127
127
  import type { QTableColumn } from 'quasar';
128
128
  import { useSuperAdminNotify } from '../quasar/notify.js';
129
129
  import type { AdminManifest, TenantActionDef, TenantDto } from '@saasicat/types';
130
+ import { formatMessage } from '../client/i18n/format.js';
130
131
  import KvBlock from '../components/KvBlock.vue';
131
132
  import MfaPromptDialog from '../components/MfaPromptDialog.vue';
132
133
  import TenantActionConfirmDialog from '../components/TenantActionConfirmDialog.vue';
134
+ import { useSaMessages } from '../vue/use-super-admin-i18n.js';
133
135
  import { useTenantActionFlow } from '../vue/use-tenant-action-flow.js';
134
136
 
135
137
  export interface TenantDetailData {
@@ -185,21 +187,29 @@ const props = withDefaults(
185
187
  {
186
188
  verbrauchFields: () => [],
187
189
  showUsers: true,
188
- backLabel: 'Mandanten-Liste',
189
- titleLabel: 'Mandant',
190
- slugLabel: 'Slug',
191
- stammdatenLabel: 'Stammdaten',
192
- planLabel: 'Plan',
193
- statusLabel: 'Status',
194
- pilotLabel: 'Pilot',
195
- trialEndLabel: 'Trial-Ende',
196
- pilotEndLabel: 'Pilot-Ende',
197
- vatIdLabel: 'USt-IdNr.',
198
- verbrauchLabel: 'Verbrauch',
199
- usersLabel: 'User',
200
190
  },
201
191
  );
202
192
 
193
+ const msg = useSaMessages('tenants');
194
+ const common = useSaMessages('common');
195
+
196
+ // Every label stays overridable via props — only the German literals moved
197
+ // into the catalog.
198
+ const labels = computed(() => ({
199
+ back: props.backLabel ?? msg.value.detail.backToList,
200
+ title: props.titleLabel ?? msg.value.tenant,
201
+ slug: props.slugLabel ?? msg.value.detail.slug,
202
+ masterData: props.stammdatenLabel ?? msg.value.detail.masterData,
203
+ plan: props.planLabel ?? msg.value.plan,
204
+ status: props.statusLabel ?? common.value.status,
205
+ pilot: props.pilotLabel ?? msg.value.detail.pilot,
206
+ trialEnd: props.trialEndLabel ?? msg.value.detail.trialEnd,
207
+ pilotEnd: props.pilotEndLabel ?? msg.value.detail.pilotEnd,
208
+ vatId: props.vatIdLabel ?? msg.value.detail.vatId,
209
+ usage: props.verbrauchLabel ?? msg.value.detail.usage,
210
+ users: props.usersLabel ?? msg.value.detail.users,
211
+ }));
212
+
203
213
  const notify = useSuperAdminNotify();
204
214
  const data = ref<TenantDetailData | null>(null);
205
215
  const loading = ref(false);
@@ -250,7 +260,10 @@ function showMfaDialog(def: TenantActionDef, ctx: { row: TenantDto }): Promise<s
250
260
  return new Promise((resolve) => {
251
261
  mfaState.value = {
252
262
  show: true,
253
- description: `${def.label} — Tenant „${ctx.row.name}". TOTP-Code aus Authenticator eingeben.`,
263
+ description: formatMessage(msg.value.actions.mfaDescription, {
264
+ action: def.label,
265
+ tenant: ctx.row.name,
266
+ }),
254
267
  error: '',
255
268
  };
256
269
  pendingMfaResolve = (code) => {
@@ -347,34 +360,35 @@ function toneColor(actionKey: string): string {
347
360
  return 'primary';
348
361
  }
349
362
 
350
- const DEFAULT_USER_COLUMNS: QTableColumn[] = [
351
- { name: 'email', label: 'E-Mail', field: 'email', align: 'left' },
363
+ const defaultUserColumns = computed<QTableColumn[]>(() => [
364
+ { name: 'email', label: msg.value.detail.userEmail, field: 'email', align: 'left' },
352
365
  {
353
366
  name: 'name',
354
- label: 'Name',
367
+ label: common.value.name,
355
368
  field: (r: unknown) => {
356
369
  const row = r as Record<string, unknown>;
357
370
  return `${row.firstName ?? ''} ${row.lastName ?? ''}`.trim();
358
371
  },
359
372
  align: 'left',
360
373
  },
361
- { name: 'role', label: 'Rolle', field: 'role', align: 'left' },
374
+ { name: 'role', label: msg.value.detail.userRole, field: 'role', align: 'left' },
362
375
  {
363
376
  name: 'status',
364
- label: 'Status',
365
- field: (r: unknown) => ((r as Record<string, unknown>).isActive ? 'aktiv' : 'deaktiviert'),
377
+ label: common.value.status,
378
+ field: (r: unknown) =>
379
+ (r as Record<string, unknown>).isActive ? common.value.active : common.value.inactive,
366
380
  align: 'left',
367
381
  },
368
382
  {
369
383
  name: 'lastLogin',
370
- label: 'Letzter Login',
384
+ label: msg.value.detail.userLastLogin,
371
385
  field: (r: unknown) => {
372
386
  const v = (r as Record<string, unknown>).lastLoginAt;
373
387
  return v ? String(v).slice(0, 10) : '—';
374
388
  },
375
389
  align: 'left',
376
390
  },
377
- ];
391
+ ]);
378
392
  </script>
379
393
 
380
394
  <style scoped>
@@ -2,7 +2,7 @@
2
2
  <div class="sa-tenants">
3
3
  <header class="sa-tenants__head">
4
4
  <div>
5
- <h1 class="sa-tenants__title">Mandanten</h1>
5
+ <h1 class="sa-tenants__title">{{ msg.list.title }}</h1>
6
6
  <p v-if="subtitle" class="sa-tenants__sub">{{ subtitle }}</p>
7
7
  </div>
8
8
  </header>
@@ -12,7 +12,7 @@
12
12
  <q-icon name="search" size="15px" />
13
13
  <input
14
14
  v-model="searchInput"
15
- placeholder="Slug oder Name …"
15
+ :placeholder="msg.list.searchPlaceholder"
16
16
  @input="reloadDebounced"
17
17
  />
18
18
  <button v-if="searchInput" class="sa-tenants__clear" @click="onClearSearch">
@@ -20,8 +20,8 @@
20
20
  </button>
21
21
  </div>
22
22
  <select v-model="statusFilter" class="sa-tenants__select" @change="applyFilter">
23
- <option :value="null">Alle Aktiv-Status</option>
24
- <option v-for="opt in statusOptions" :key="opt.value" :value="opt.value">
23
+ <option :value="null">{{ msg.list.allStatuses }}</option>
24
+ <option v-for="opt in resolvedStatusOptions" :key="opt.value" :value="opt.value">
25
25
  {{ opt.label }}
26
26
  </option>
27
27
  </select>
@@ -31,14 +31,14 @@
31
31
  class="sa-tenants__select"
32
32
  @change="applyFilter"
33
33
  >
34
- <option :value="null">{{ planFilterLabel }}</option>
34
+ <option :value="null">{{ resolvedPlanFilterLabel }}</option>
35
35
  <option v-for="p in planOptions" :key="p" :value="p">{{ p }}</option>
36
36
  </select>
37
37
  <slot name="filters-extra" />
38
38
  </div>
39
39
 
40
40
  <q-banner v-if="error" class="bg-red-1 text-red-9 q-mb-md" rounded>
41
- <strong>Fehler:</strong> {{ error.message }}
41
+ <strong>{{ common.error }}:</strong> {{ error.message }}
42
42
  </q-banner>
43
43
 
44
44
  <div class="sa-tenants__card">
@@ -46,11 +46,13 @@
46
46
  <table>
47
47
  <thead>
48
48
  <tr>
49
- <th>Mandant</th>
50
- <th v-if="showPlanColumn">{{ planColumnLabel }}</th>
51
- <th>Status</th>
52
- <th v-if="usageFields.length > 0" class="num">Verbrauch</th>
53
- <th class="num">Angelegt</th>
49
+ <th>{{ msg.tenant }}</th>
50
+ <th v-if="showPlanColumn">{{ resolvedPlanColumnLabel }}</th>
51
+ <th>{{ common.status }}</th>
52
+ <th v-if="usageFields.length > 0" class="num">
53
+ {{ msg.list.columnUsage }}
54
+ </th>
55
+ <th class="num">{{ msg.list.columnCreatedAt }}</th>
54
56
  <th v-if="hasActions" class="num"></th>
55
57
  </tr>
56
58
  </thead>
@@ -100,7 +102,7 @@
100
102
  </div>
101
103
  </td>
102
104
  <td class="num sa-tenants__mono">
103
- {{ formatDateDe(usageStr(row, 'createdAt')) }}
105
+ {{ formatCreatedAt(row) }}
104
106
  </td>
105
107
  <td v-if="hasActions" class="num sa-tenants__actions">
106
108
  <slot name="row-actions" :row="row" :actions="visibleActions(row)">
@@ -127,17 +129,16 @@
127
129
  </table>
128
130
  <div v-if="items.length === 0 && !loading" class="sa-tenants__empty">
129
131
  <q-icon name="search_off" size="32px" />
130
- <div>Keine Mandanten mit diesen Filtern.</div>
132
+ <div>{{ msg.list.empty }}</div>
131
133
  </div>
132
134
  <div v-if="loading" class="sa-tenants__loading">
133
- <q-spinner size="20px" /> wird geladen…
135
+ <q-spinner size="20px" /> {{ common.loading }}
134
136
  </div>
135
137
  </div>
136
138
  </div>
137
139
 
138
140
  <footer class="sa-tenants__foot">
139
- {{ total }} Mandanten · Seite {{ page }} /
140
- {{ Math.max(1, Math.ceil(total / pageSize)) }}
141
+ {{ paginationLabel }}
141
142
  </footer>
142
143
 
143
144
  <!-- Manifest-Driven Action Flow: only mounted when the `manifest` prop
@@ -166,13 +167,10 @@
166
167
 
167
168
  <script setup lang="ts">
168
169
  import { computed, ref, watch } from 'vue';
169
- import type {
170
- AdminManifest,
171
- TenantActionDef,
172
- TenantDto,
173
- TenantListFilter,
174
- } from '@saasicat/types';
170
+ import type { AdminManifest, TenantActionDef, TenantDto, TenantListFilter } from '@saasicat/types';
175
171
  import type { HttpClient } from '../client/types.js';
172
+ import { formatMessage } from '../client/i18n/format.js';
173
+ import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
176
174
  import { useTenants } from '../vue/use-tenants.js';
177
175
  import {
178
176
  usePlatformTenantActions,
@@ -181,12 +179,7 @@ import {
181
179
  import MfaPromptDialog from '../components/MfaPromptDialog.vue';
182
180
  import TenantActionConfirmDialog from '../components/TenantActionConfirmDialog.vue';
183
181
  import StatusPill, { type PillTone } from './tenants/StatusPill.vue';
184
- import {
185
- DEFAULT_PLAN_ACCENTS,
186
- formatDateDe,
187
- planAccent,
188
- tenantInitials,
189
- } from './tenants/format.js';
182
+ import { DEFAULT_PLAN_ACCENTS, formatDate, planAccent, tenantInitials } from './tenants/format.js';
190
183
 
191
184
  // Platform standard page: tenant list.
192
185
  //
@@ -196,7 +189,7 @@ import {
196
189
  //
197
190
  // App-specific bits via props:
198
191
  // - `subtitle` : header subtitle
199
- // - `statusOptions` : filter dropdown values (default: Aktiv/Suspendiert)
192
+ // - `statusOptions` : filter dropdown values (default: active/suspended)
200
193
  // - `planOptions` : filter dropdown values for plan; omitted = hides dropdown
201
194
  // - `planAccents` : plan id → accent color (avatar + plan dot)
202
195
  // - `planLabelField` : field for the plan display (e.g. "plan" or "bundleKey")
@@ -331,12 +324,6 @@ const props = withDefaults(
331
324
  }>(),
332
325
  {
333
326
  pageSize: 25,
334
- statusOptions: () => [
335
- { value: 'ACTIVE', label: 'Aktiv' },
336
- { value: 'INACTIVE', label: 'Suspendiert' },
337
- ],
338
- planFilterLabel: 'Alle Pläne',
339
- planColumnLabel: 'Plan',
340
327
  showPlanColumn: true,
341
328
  planLabelField: 'plan',
342
329
  planAccents: () => DEFAULT_PLAN_ACCENTS,
@@ -345,6 +332,20 @@ const props = withDefaults(
345
332
  },
346
333
  );
347
334
 
335
+ const msg = useSaMessages('tenants');
336
+ const common = useSaMessages('common');
337
+ const { intlLocale } = useSuperAdminI18n();
338
+
339
+ const resolvedStatusOptions = computed<ReadonlyArray<{ value: string; label: string }>>(
340
+ () =>
341
+ props.statusOptions ?? [
342
+ { value: 'ACTIVE', label: common.value.active },
343
+ { value: 'INACTIVE', label: msg.value.list.suspended },
344
+ ],
345
+ );
346
+ const resolvedPlanFilterLabel = computed(() => props.planFilterLabel ?? msg.value.list.allPlans);
347
+ const resolvedPlanColumnLabel = computed(() => props.planColumnLabel ?? msg.value.plan);
348
+
348
349
  const filter = ref<TenantListFilter>({});
349
350
  const searchInput = ref('');
350
351
  const statusFilter = ref<string | null>(null);
@@ -360,6 +361,14 @@ const { items, page, total, loading, error, goToPage, setPageSize } = list;
360
361
 
361
362
  setPageSize(props.pageSize);
362
363
 
364
+ const paginationLabel = computed(() =>
365
+ formatMessage(msg.value.list.pagination, {
366
+ total: total.value,
367
+ page: page.value,
368
+ pages: Math.max(1, Math.ceil(total.value / props.pageSize)),
369
+ }),
370
+ );
371
+
363
372
  let searchTimer: ReturnType<typeof setTimeout> | null = null;
364
373
  function reloadDebounced(): void {
365
374
  if (searchTimer) clearTimeout(searchTimer);
@@ -427,7 +436,7 @@ if (manifestFlow && typeof window !== 'undefined') {
427
436
  if (orphans.length > 0) {
428
437
  // eslint-disable-next-line no-console
429
438
  console.warn(
430
- '[PlatformTenantsPage] Manifest-Actions ohne Handler in createSuperAdminApp({ actions }):',
439
+ '[PlatformTenantsPage] Manifest actions without a handler in createSuperAdminApp({ actions }):',
431
440
  orphans,
432
441
  );
433
442
  }
@@ -449,8 +458,8 @@ const combinedActions = computed<TenantRowAction[]>(() => {
449
458
  if (typeof window !== 'undefined') {
450
459
  // eslint-disable-next-line no-console
451
460
  console.warn(
452
- `[PlatformTenantsPage] Custom-Action "${a.id}" (actionKey="${a.actionKey}") ` +
453
- `kollidiert mit Manifest-ActionManifest gewinnt.`,
461
+ `[PlatformTenantsPage] Custom action "${a.id}" (actionKey="${a.actionKey}") ` +
462
+ `collides with a manifest action the manifest wins.`,
454
463
  );
455
464
  }
456
465
  return false;
@@ -488,14 +497,18 @@ function usageStr(row: TenantRow, key: string): string | undefined {
488
497
  return typeof v === 'string' ? v : undefined;
489
498
  }
490
499
 
500
+ function formatCreatedAt(row: TenantRow): string {
501
+ return formatDate(usageStr(row, 'createdAt'), intlLocale.value);
502
+ }
503
+
491
504
  function resolvedPills(row: TenantRow): StatusPillDef[] {
492
505
  if (props.pillsForRow) return props.pillsForRow(row);
493
- // Default: only Aktiv/Suspendiert.
506
+ // Default: only active/suspended.
494
507
  const isActive = (row as TenantDto).isActive;
495
508
  return [
496
509
  isActive
497
- ? { label: 'Aktiv', icon: 'check_circle', tone: 'positive' }
498
- : { label: 'Suspendiert', icon: 'block', tone: 'negative' },
510
+ ? { label: common.value.active, icon: 'check_circle', tone: 'positive' }
511
+ : { label: msg.value.list.suspended, icon: 'block', tone: 'negative' },
499
512
  ];
500
513
  }
501
514