@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
@@ -2,12 +2,12 @@
2
2
  <section class="pd-panel pd-audit">
3
3
  <div class="pd-panel-head">
4
4
  <div style="min-width: 0">
5
- <h3 class="pd-panel-title">Audit-Log</h3>
6
- <div class="pd-panel-sub">Letzte Änderungen an diesem Plan</div>
5
+ <h3 class="pd-panel-title">{{ msg.auditLog.title }}</h3>
6
+ <div class="pd-panel-sub">{{ msg.auditLog.subtitle }}</div>
7
7
  </div>
8
8
  </div>
9
9
  <div class="pd-audit-body">
10
- <div v-if="loadingAudit" class="pd-diff-empty">Lade Audit-Log…</div>
10
+ <div v-if="loadingAudit" class="pd-diff-empty">{{ msg.auditLog.loading }}</div>
11
11
  <div v-for="a in auditRows" :key="a.id" class="pd-audit-row">
12
12
  <span :class="['pd-audit-dot', `pd-audit-${auditKind(a.action)}`]" />
13
13
  <span class="pd-audit-when">{{ formatAuditDate(a.createdAt) }}</span>
@@ -20,6 +20,8 @@
20
20
  </template>
21
21
 
22
22
  <script setup lang="ts">
23
+ import { formatMessage } from '../../client/i18n/format.js';
24
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
23
25
  import type { AuditRow } from './types.js';
24
26
 
25
27
  defineProps<{
@@ -27,6 +29,9 @@ defineProps<{
27
29
  loadingAudit: boolean;
28
30
  }>();
29
31
 
32
+ const msg = useSaMessages('planDetail');
33
+ const { intlLocale } = useSuperAdminI18n();
34
+
30
35
  function auditKind(action: string): string {
31
36
  const a = action.toLowerCase();
32
37
  if (a.includes('publish')) return 'publish';
@@ -51,9 +56,9 @@ function formatAuditDate(iso: string): string {
51
56
  const d = new Date(iso);
52
57
  if (Number.isNaN(d.getTime())) return iso;
53
58
  const isToday = d.toDateString() === new Date().toDateString();
54
- const time = d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
59
+ const time = d.toLocaleTimeString(intlLocale.value, { hour: '2-digit', minute: '2-digit' });
55
60
  return isToday
56
- ? `heute · ${time}`
57
- : `${d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' })} · ${time}`;
61
+ ? formatMessage(msg.value.auditLog.today, { time })
62
+ : `${d.toLocaleDateString(intlLocale.value, { day: '2-digit', month: 'short' })} · ${time}`;
58
63
  }
59
64
  </script>
@@ -77,11 +77,9 @@
77
77
 
78
78
  <script setup lang="ts">
79
79
  import { computed, ref, watch } from 'vue';
80
- import {
81
- isVersionEditable,
82
- type PlanRow,
83
- type PlanVersionRow,
84
- } from '@saasicat/types';
80
+ import { isVersionEditable, type PlanRow, type PlanVersionRow } from '@saasicat/types';
81
+ import { formatCurrency } from '../../client/i18n/currency.js';
82
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
85
83
  import PlanAuditLog from './PlanAuditLog.vue';
86
84
  import PlanDetailHeader from './PlanDetailHeader.vue';
87
85
  import PlanDetailKpis from './PlanDetailKpis.vue';
@@ -143,6 +141,9 @@ const emit = defineEmits<{
143
141
  (e: 'terminate', versionId: string, endsAt: string): void;
144
142
  }>();
145
143
 
144
+ const msg = useSaMessages('planDetail');
145
+ const { locale, intlLocale } = useSuperAdminI18n();
146
+
146
147
  // ── Status / Selection ──────────────────────────────────────────────
147
148
  function statusOf(v: PlanVersionRow): PlanVersionStatus {
148
149
  if (v.publishedAt === null) return 'draft';
@@ -201,7 +202,7 @@ const timelineTicks = computed(() => {
201
202
  for (const v of chronological.value) {
202
203
  if (v.validFrom) ticks.push(v.validFrom.slice(0, 7));
203
204
  }
204
- ticks.push('jetzt');
205
+ ticks.push(msg.value.versions.timelineNow);
205
206
  return ticks;
206
207
  });
207
208
 
@@ -209,9 +210,7 @@ const timelineTicks = computed(() => {
209
210
  function formatMoney(raw: string | number): string {
210
211
  const num = typeof raw === 'string' ? Number(raw) : raw;
211
212
  if (!Number.isFinite(num)) return String(raw);
212
- if (num === 0) return '0 €';
213
- if (Number.isInteger(num)) return `${num} €`;
214
- return `${num.toFixed(2).replace('.', ',')} €`;
213
+ return formatCurrency(num, locale.value);
215
214
  }
216
215
  function quotasOf(v: PlanVersionRow): Record<string, number> {
217
216
  if (v.quotas && Object.keys(v.quotas).length > 0) return v.quotas;
@@ -290,7 +289,7 @@ const diffRows = computed<DiffRow[]>(() => {
290
289
  id: 'af-' + f,
291
290
  kind: 'add',
292
291
  sign: '+',
293
- tag: 'neu',
292
+ tag: msg.value.diff.tagNew,
294
293
  label: featureLabel(f),
295
294
  key: f,
296
295
  });
@@ -299,7 +298,7 @@ const diffRows = computed<DiffRow[]>(() => {
299
298
  id: 'rf-' + f,
300
299
  kind: 'rm',
301
300
  sign: '−',
302
- tag: 'entfernt',
301
+ tag: msg.value.diff.tagRemoved,
303
302
  label: featureLabel(f),
304
303
  key: f,
305
304
  });
@@ -309,7 +308,7 @@ const diffRows = computed<DiffRow[]>(() => {
309
308
  id: 'cq-' + q.key,
310
309
  kind: 'mod',
311
310
  sign: '~',
312
- tag: 'geändert',
311
+ tag: msg.value.diff.tagChanged,
313
312
  label: quotaLabel(q.key),
314
313
  key: q.key,
315
314
  from: `${q.from} ${u}`.trim(),
@@ -322,7 +321,7 @@ const diffRows = computed<DiffRow[]>(() => {
322
321
  id: 'aq-' + q.key,
323
322
  kind: 'add',
324
323
  sign: '+',
325
- tag: 'neu',
324
+ tag: msg.value.diff.tagNew,
326
325
  label: quotaLabel(q.key),
327
326
  key: q.key,
328
327
  from: '—',
@@ -335,7 +334,7 @@ const diffRows = computed<DiffRow[]>(() => {
335
334
  id: 'rq-' + q.key,
336
335
  kind: 'rm',
337
336
  sign: '−',
338
- tag: 'entfernt',
337
+ tag: msg.value.diff.tagRemoved,
339
338
  label: quotaLabel(q.key),
340
339
  key: q.key,
341
340
  from: `${q.value} ${u}`.trim(),
@@ -347,8 +346,8 @@ const diffRows = computed<DiffRow[]>(() => {
347
346
  id: 'price',
348
347
  kind: 'mod',
349
348
  sign: '~',
350
- tag: 'geändert',
351
- label: 'Preis (Monat / Jahr)',
349
+ tag: msg.value.diff.tagChanged,
350
+ label: msg.value.diff.priceLabel,
352
351
  key: 'pricing',
353
352
  from: `${formatMoney(predecessor.value.monthlyNet)} / ${formatMoney(predecessor.value.yearlyNet)}`,
354
353
  to: `${formatMoney(selectedVersion.value.monthlyNet)} / ${formatMoney(selectedVersion.value.yearlyNet)}`,
@@ -375,7 +374,7 @@ function formatDate(iso: string | null | undefined): string {
375
374
  if (!iso) return '—';
376
375
  const d = new Date(iso);
377
376
  if (Number.isNaN(d.getTime())) return iso;
378
- return d.toLocaleDateString('de-DE', {
377
+ return d.toLocaleDateString(intlLocale.value, {
379
378
  day: '2-digit',
380
379
  month: '2-digit',
381
380
  year: 'numeric',
@@ -401,17 +400,16 @@ async function executeTerminate(): Promise<void> {
401
400
  const e = err as { status?: number; body?: { code?: string; message?: string } };
402
401
  const code = e?.body?.code;
403
402
  if (code === 'PLAN_TERMINATE_DATE_NOT_FUTURE') {
404
- terminateError.value = 'Das Datum muss in der Zukunft liegen.';
403
+ terminateError.value = msg.value.terminateDialog.errorDateNotFuture;
405
404
  } else if (code === 'PLAN_VERSION_NOT_LIVE') {
406
- terminateError.value =
407
- 'Diese Version ist nicht live (draft oder superseded) und kann nicht terminiert werden.';
405
+ terminateError.value = msg.value.terminateDialog.errorVersionNotLive;
408
406
  } else if (code === 'PLAN_TERMINATE_NOT_IMPLEMENTED') {
409
- terminateError.value =
410
- 'Backend unterstützt Terminate noch nicht — bitte API-Server neu bauen.';
407
+ terminateError.value = msg.value.terminateDialog.errorNotImplemented;
411
408
  } else if (e?.body?.message) {
412
409
  terminateError.value = e.body.message;
413
410
  } else {
414
- terminateError.value = err instanceof Error ? err.message : 'Terminate fehlgeschlagen.';
411
+ terminateError.value =
412
+ err instanceof Error ? err.message : msg.value.terminateDialog.errorFailed;
415
413
  }
416
414
  } finally {
417
415
  terminating.value = false;
@@ -13,7 +13,7 @@
13
13
  @keydown.escape="cancelName"
14
14
  @blur="commitName"
15
15
  />
16
- <span class="pd-title-hint">Draft · Enter speichert · Esc bricht ab</span>
16
+ <span class="pd-title-hint">{{ msg.header.nameEditHint }}</span>
17
17
  </template>
18
18
  <template v-else>
19
19
  <h1 class="pd-title">{{ plan.label }}</h1>
@@ -21,8 +21,8 @@
21
21
  v-if="draftVersion"
22
22
  class="pd-title-edit-btn"
23
23
  type="button"
24
- title="Plan-Name im Draft bearbeiten"
25
- aria-label="Plan-Name bearbeiten"
24
+ :title="msg.header.editNameTitle"
25
+ :aria-label="msg.header.editNameAria"
26
26
  @click="startEditName"
27
27
  >
28
28
  <svg
@@ -40,7 +40,7 @@
40
40
  </button>
41
41
  </template>
42
42
  </div>
43
- <div class="pd-desc">{{ plan.description || 'Keine Beschreibung.' }}</div>
43
+ <div class="pd-desc">{{ plan.description || msg.header.noDescription }}</div>
44
44
  </div>
45
45
  </div>
46
46
  <div class="pd-actions">
@@ -57,13 +57,13 @@
57
57
  <path d="M5 12h14M13 5l7 7-7 7" />
58
58
  </svg>
59
59
  </span>
60
- <span>Zurück</span>
60
+ <span>{{ common.back }}</span>
61
61
  </button>
62
62
  <button
63
63
  v-if="tenantTotal === 0 && !draftVersion && publishedCount === 0"
64
64
  class="btn danger"
65
65
  type="button"
66
- title="Plan löschen (nur ohne Mandanten und ohne published Versionen)"
66
+ :title="msg.header.deletePlanTitle"
67
67
  @click="$emit('deletePlan')"
68
68
  >
69
69
  <svg
@@ -78,7 +78,7 @@
78
78
  d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"
79
79
  />
80
80
  </svg>
81
- <span>Plan löschen</span>
81
+ <span>{{ msg.header.deletePlan }}</span>
82
82
  </button>
83
83
  <button class="btn" type="button" @click="$emit('clonePlan')">
84
84
  <svg
@@ -92,7 +92,7 @@
92
92
  <rect x="9" y="9" width="11" height="11" rx="2" />
93
93
  <path d="M5 15V5a2 2 0 012-2h10" />
94
94
  </svg>
95
- <span>Plan klonen</span>
95
+ <span>{{ msg.header.clonePlan }}</span>
96
96
  </button>
97
97
  <button
98
98
  v-if="!draftVersion"
@@ -110,7 +110,7 @@
110
110
  >
111
111
  <path d="M12 5v14M5 12h14" />
112
112
  </svg>
113
- <span>Neue Draft-Version</span>
113
+ <span>{{ msg.header.newDraftVersion }}</span>
114
114
  </button>
115
115
  <button
116
116
  v-else
@@ -128,15 +128,17 @@
128
128
  >
129
129
  <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
130
130
  </svg>
131
- <span>Draft v{{ draftVersion.version }} veröffentlichen</span>
131
+ <span>{{ publishDraftLabel }}</span>
132
132
  </button>
133
133
  </div>
134
134
  </div>
135
135
  </template>
136
136
 
137
137
  <script setup lang="ts">
138
- import { nextTick, ref, watch } from 'vue';
138
+ import { computed, nextTick, ref, watch } from 'vue';
139
139
  import type { PlanRow, PlanVersionRow } from '@saasicat/types';
140
+ import { formatMessage } from '../../client/i18n/format.js';
141
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
140
142
 
141
143
  const props = defineProps<{
142
144
  plan: PlanRow;
@@ -154,6 +156,13 @@ const emit = defineEmits<{
154
156
  (e: 'updatePlan', patch: { label: string }): void;
155
157
  }>();
156
158
 
159
+ const msg = useSaMessages('planDetail');
160
+ const common = useSaMessages('common');
161
+
162
+ const publishDraftLabel = computed(() =>
163
+ formatMessage(msg.value.header.publishDraft, { version: props.draftVersion?.version ?? '' }),
164
+ );
165
+
157
166
  const editingName = ref(false);
158
167
  const nameDraft = ref(props.plan.label);
159
168
  const nameInput = ref<HTMLInputElement | null>(null);
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="pd-kpis">
3
3
  <div class="pd-kpi">
4
- <div class="pd-kpi-label">Aktive Version</div>
4
+ <div class="pd-kpi-label">{{ msg.kpis.activeVersion }}</div>
5
5
  <div class="pd-kpi-big">
6
6
  <template v-if="liveVersion">
7
7
  v{{ liveVersion.version }}
@@ -9,49 +9,62 @@
9
9
  </template>
10
10
  <template v-else>—</template>
11
11
  </div>
12
- <div class="pd-kpi-sub">
13
- {{
14
- liveVersion?.validFrom
15
- ? `seit ${liveVersion.validFrom.slice(0, 10)}`
16
- : 'keine veröffentlichte Version'
17
- }}
18
- </div>
12
+ <div class="pd-kpi-sub">{{ activeVersionSub }}</div>
19
13
  </div>
20
14
 
21
15
  <div class="pd-kpi">
22
- <div class="pd-kpi-label">Tenant-Impact</div>
16
+ <div class="pd-kpi-label">{{ msg.kpis.tenantImpact }}</div>
23
17
  <div class="pd-kpi-big">{{ tenantTotal }}</div>
24
- <div class="pd-kpi-sub">Mandanten auf diesem Plan</div>
18
+ <div class="pd-kpi-sub">{{ msg.kpis.tenantsOnPlan }}</div>
25
19
  </div>
26
20
 
27
21
  <div class="pd-kpi">
28
- <div class="pd-kpi-label">Versionen</div>
22
+ <div class="pd-kpi-label">{{ msg.kpis.versions }}</div>
29
23
  <div class="pd-kpi-big">{{ versionCount }}</div>
30
- <div class="pd-kpi-sub">
31
- {{ publishedCount }} live/superseded{{ draftVersion ? ' · 1 Draft' : '' }}
32
- </div>
24
+ <div class="pd-kpi-sub">{{ versionsSummary }}</div>
33
25
  </div>
34
26
 
35
27
  <div :class="['pd-kpi', draftVersion ? 'draft' : '']">
36
- <div class="pd-kpi-label">Offener Draft</div>
28
+ <div class="pd-kpi-label">{{ msg.kpis.openDraft }}</div>
37
29
  <div class="pd-kpi-big" :style="draftVersion ? '' : 'color:#cbd5e1'">
38
30
  {{ draftVersion ? `v${draftVersion.version}` : '—' }}
39
31
  </div>
40
32
  <div class="pd-kpi-sub">
41
- {{ draftVersion ? 'Bereit zum Bearbeiten' : 'Keine Draft-Version offen' }}
33
+ {{ draftVersion ? msg.kpis.readyToEdit : msg.kpis.noOpenDraft }}
42
34
  </div>
43
35
  </div>
44
36
  </div>
45
37
  </template>
46
38
 
47
39
  <script setup lang="ts">
40
+ import { computed } from 'vue';
48
41
  import type { PlanVersionRow } from '@saasicat/types';
42
+ import { formatMessage } from '../../client/i18n/format.js';
43
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
49
44
 
50
- defineProps<{
45
+ const props = defineProps<{
51
46
  liveVersion: PlanVersionRow | null;
52
47
  draftVersion: PlanVersionRow | null;
53
48
  tenantTotal: number;
54
49
  versionCount: number;
55
50
  publishedCount: number;
56
51
  }>();
52
+
53
+ const msg = useSaMessages('planDetail');
54
+
55
+ const activeVersionSub = computed(() => {
56
+ const validFrom = props.liveVersion?.validFrom;
57
+ return validFrom
58
+ ? formatMessage(msg.value.kpis.activeSince, { date: validFrom.slice(0, 10) })
59
+ : msg.value.kpis.noPublishedVersion;
60
+ });
61
+
62
+ const versionsSummary = computed(() =>
63
+ props.draftVersion
64
+ ? formatMessage(msg.value.kpis.versionsSummaryWithDrafts, {
65
+ published: props.publishedCount,
66
+ drafts: 1,
67
+ })
68
+ : formatMessage(msg.value.kpis.versionsSummary, { published: props.publishedCount }),
69
+ );
57
70
  </script>
@@ -7,17 +7,20 @@
7
7
  <q-card style="min-width: 420px">
8
8
  <q-card-section>
9
9
  <div class="text-h6">
10
- {{ target?.endsAt ? 'Enddatum ändern' : 'Version terminieren' }}
10
+ {{
11
+ target?.endsAt
12
+ ? msg.terminateDialog.titleChangeEndDate
13
+ : msg.terminateDialog.titleTerminate
14
+ }}
11
15
  </div>
12
16
  <div class="text-body2 q-mt-sm">
13
- Plan <code>{{ plan.planKey }}</code> — v{{ target?.version }} wird am gewählten
14
- Datum auslaufen, <b>ohne</b> durch eine Nachfolge-Version ersetzt zu werden.
15
- Bestand-Subscriptions bleiben gebunden (Vertragsschutz P1); neue Buchungen sind
16
- ab diesem Datum nicht mehr möglich.
17
+ {{ msg.terminateDialog.bodyPlanPrefix }} <code>{{ plan.planKey }}</code>
18
+ {{ bodyExpiry }} <b>{{ msg.terminateDialog.bodyWithout }}</b>
19
+ {{ msg.terminateDialog.bodySuffix }}
17
20
  </div>
18
21
  <q-input
19
22
  :model-value="dateInput"
20
- label="Enddatum (YYYY-MM-DD)"
23
+ :label="msg.terminateDialog.dateLabel"
21
24
  type="text"
22
25
  class="q-mt-md"
23
26
  mask="####-##-##"
@@ -43,10 +46,10 @@
43
46
  {{ error }}
44
47
  </q-banner>
45
48
  <q-card-actions align="right">
46
- <q-btn flat label="Abbrechen" @click="$emit('update:modelValue', false)" />
49
+ <q-btn flat :label="common.cancel" @click="$emit('update:modelValue', false)" />
47
50
  <q-btn
48
51
  color="primary"
49
- label="Terminieren"
52
+ :label="msg.terminateDialog.submit"
50
53
  :loading="terminating"
51
54
  :disable="!dateInput"
52
55
  @click="$emit('execute')"
@@ -57,9 +60,12 @@
57
60
  </template>
58
61
 
59
62
  <script setup lang="ts">
63
+ import { computed } from 'vue';
60
64
  import type { PlanRow, PlanVersionRow } from '@saasicat/types';
65
+ import { formatMessage } from '../../client/i18n/format.js';
66
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
61
67
 
62
- defineProps<{
68
+ const props = defineProps<{
63
69
  modelValue: boolean;
64
70
  plan: PlanRow;
65
71
  target: PlanVersionRow | null;
@@ -68,6 +74,13 @@ defineProps<{
68
74
  terminating: boolean;
69
75
  }>();
70
76
 
77
+ const msg = useSaMessages('planDetail');
78
+ const common = useSaMessages('common');
79
+
80
+ const bodyExpiry = computed(() =>
81
+ formatMessage(msg.value.terminateDialog.bodyExpiry, { version: props.target?.version ?? '' }),
82
+ );
83
+
71
84
  defineEmits<{
72
85
  (e: 'update:modelValue', value: boolean): void;
73
86
  (e: 'update:dateInput', value: string): void;
@@ -2,22 +2,8 @@
2
2
  <section class="pd-panel">
3
3
  <div class="pd-panel-head">
4
4
  <div style="min-width: 0">
5
- <h3 class="pd-panel-title">
6
- <template v-if="predecessor">
7
- Diff v{{ predecessor.version }} → v{{ selectedVersion?.version }}
8
- </template>
9
- <template v-else>Komponenten · v{{ selectedVersion?.version }}</template>
10
- </h3>
11
- <div class="pd-panel-sub">
12
- <template v-if="predecessor && selectedVersion">
13
- {{
14
- statusOf(selectedVersion) === 'draft'
15
- ? 'Was sich beim Publish ändert'
16
- : `Änderungen v${predecessor.version} → v${selectedVersion.version}`
17
- }}
18
- </template>
19
- <template v-else>Initialversion · kein Vorgänger zum Vergleich</template>
20
- </div>
5
+ <h3 class="pd-panel-title">{{ panelTitle }}</h3>
6
+ <div class="pd-panel-sub">{{ panelSubtitle }}</div>
21
7
  </div>
22
8
  <div class="pd-panel-head-right pd-diff-chips">
23
9
  <template v-if="predecessor">
@@ -39,10 +25,10 @@
39
25
  <span class="pd-diff-kind">
40
26
  {{
41
27
  d.kind === 'add'
42
- ? 'Hinzugefügt'
28
+ ? msg.diff.kindAdded
43
29
  : d.kind === 'rm'
44
- ? 'Entfernt'
45
- : 'Geändert'
30
+ ? msg.diff.kindRemoved
31
+ : msg.diff.kindChanged
46
32
  }}
47
33
  </span>
48
34
  <span class="pd-diff-label">{{ d.label }}</span>
@@ -56,8 +42,8 @@
56
42
  </div>
57
43
  </div>
58
44
  <div v-if="diffRows.length === 0" class="pd-diff-empty">
59
- <b>Keine Änderungen</b>
60
- v{{ selectedVersion?.version }} ist identisch zu v{{ predecessor.version }}.
45
+ <b>{{ common.noChanges }}</b>
46
+ {{ emptyText }}
61
47
  </div>
62
48
  </div>
63
49
 
@@ -66,13 +52,17 @@
66
52
  v-if="selectedVersion.features.length === 0 && quotaCount(selectedVersion) === 0"
67
53
  class="pd-diff-empty"
68
54
  >
69
- <b>Keine Komponenten</b>
70
- Diese Version enthält weder Features noch Quotas.
55
+ <b>{{ msg.diff.noComponentsTitle }}</b>
56
+ {{ msg.diff.noComponentsText }}
71
57
  </div>
72
58
  <template v-if="selectedVersion.features.length > 0">
73
59
  <div class="pd-diff-section">
74
60
  <hr />
75
- <span>Features · {{ selectedVersion.features.length }}</span>
61
+ <span>{{
62
+ formatMessage(msg.diff.sectionFeatures, {
63
+ count: selectedVersion.features.length,
64
+ })
65
+ }}</span>
76
66
  <hr />
77
67
  </div>
78
68
  <div
@@ -90,7 +80,11 @@
90
80
  <template v-if="quotaCount(selectedVersion) > 0">
91
81
  <div class="pd-diff-section">
92
82
  <hr />
93
- <span>Quotas · {{ quotaCount(selectedVersion) }}</span>
83
+ <span>{{
84
+ formatMessage(msg.diff.sectionQuotas, {
85
+ count: quotaCount(selectedVersion),
86
+ })
87
+ }}</span>
94
88
  <hr />
95
89
  </div>
96
90
  <div
@@ -109,7 +103,11 @@
109
103
  <template v-if="(selectedVersion.bundles ?? []).length > 0">
110
104
  <div class="pd-diff-section">
111
105
  <hr />
112
- <span>Bundles · {{ (selectedVersion.bundles ?? []).length }}</span>
106
+ <span>{{
107
+ formatMessage(msg.diff.sectionBundles, {
108
+ count: (selectedVersion.bundles ?? []).length,
109
+ })
110
+ }}</span>
113
111
  <hr />
114
112
  </div>
115
113
  <div
@@ -129,10 +127,13 @@
129
127
  </template>
130
128
 
131
129
  <script setup lang="ts">
130
+ import { computed } from 'vue';
132
131
  import type { PlanVersionRow } from '@saasicat/types';
132
+ import { formatMessage } from '../../client/i18n/format.js';
133
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
133
134
  import type { DiffRow, PlanVersionDiff, StatusOf } from './types.js';
134
135
 
135
- defineProps<{
136
+ const props = defineProps<{
136
137
  selectedVersion: PlanVersionRow | null;
137
138
  predecessor: PlanVersionRow | null;
138
139
  diff: PlanVersionDiff;
@@ -146,4 +147,37 @@ defineProps<{
146
147
  quotaUnit: (key: string) => string;
147
148
  bundleLabel: (key: string) => string;
148
149
  }>();
150
+
151
+ const msg = useSaMessages('planDetail');
152
+ const common = useSaMessages('common');
153
+
154
+ const panelTitle = computed(() =>
155
+ props.predecessor
156
+ ? formatMessage(msg.value.diff.titleCompare, {
157
+ from: props.predecessor.version,
158
+ to: props.selectedVersion?.version ?? '',
159
+ })
160
+ : formatMessage(msg.value.diff.titleComponents, {
161
+ version: props.selectedVersion?.version ?? '',
162
+ }),
163
+ );
164
+
165
+ const panelSubtitle = computed(() => {
166
+ const predecessor = props.predecessor;
167
+ const selected = props.selectedVersion;
168
+ if (!predecessor || !selected) return msg.value.diff.subInitialVersion;
169
+ return props.statusOf(selected) === 'draft'
170
+ ? msg.value.diff.subOnPublish
171
+ : formatMessage(msg.value.diff.subChanges, {
172
+ from: predecessor.version,
173
+ to: selected.version,
174
+ });
175
+ });
176
+
177
+ const emptyText = computed(() =>
178
+ formatMessage(msg.value.diff.emptyText, {
179
+ selected: props.selectedVersion?.version ?? '',
180
+ predecessor: props.predecessor?.version ?? '',
181
+ }),
182
+ );
149
183
  </script>