@saasicat/ui-vue 0.4.0 → 0.6.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 (214) hide show
  1. package/README.md +64 -4
  2. package/dist/chunk-A3W2N26K.js +714 -0
  3. package/dist/chunk-QZOSDCNP.js +98 -0
  4. package/dist/chunk-X5SQ5HMB.js +3231 -0
  5. package/dist/client/index.cjs +3988 -0
  6. package/dist/client/index.d.cts +321 -0
  7. package/dist/client/index.d.ts +321 -0
  8. package/dist/client/index.js +70 -0
  9. package/dist/index.cjs +4446 -1249
  10. package/dist/index.d.cts +297 -888
  11. package/dist/index.d.ts +297 -888
  12. package/dist/index.js +609 -1348
  13. package/dist/messages-7b0P8W75.d.cts +1652 -0
  14. package/dist/messages-7b0P8W75.d.ts +1652 -0
  15. package/dist/quasar/index.cjs +3340 -0
  16. package/dist/quasar/index.d.cts +95 -0
  17. package/dist/quasar/index.d.ts +95 -0
  18. package/dist/quasar/index.js +96 -0
  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/use-super-admin-i18n-B3v3-SWZ.d.ts +210 -0
  22. package/dist/use-super-admin-i18n-DMktRVEt.d.cts +210 -0
  23. package/package.json +23 -3
  24. package/src/{action-registry.ts → client/action-registry.ts} +4 -4
  25. package/src/{batch-column-fetcher.ts → client/batch-column-fetcher.ts} +4 -4
  26. package/src/{boot-loader.ts → 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 +27 -0
  51. package/src/{manifest-loader.ts → client/manifest-loader.ts} +4 -4
  52. package/src/{nav-builder.ts → client/nav-builder.ts} +44 -51
  53. package/src/{plan-versions-catalog.ts → client/plan-versions-catalog.ts} +42 -30
  54. package/src/components/BundleVersionPublishDialog.vue +35 -24
  55. package/src/components/FeatureGate.vue +1 -1
  56. package/src/components/MarketingPromotionsTab.vue +112 -59
  57. package/src/components/MfaPromptDialog.vue +11 -11
  58. package/src/components/TenantActionConfirmDialog.vue +19 -17
  59. package/src/components/VersionDiffPreview.vue +72 -43
  60. package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
  61. package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
  62. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
  63. package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
  64. package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
  65. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
  66. package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
  67. package/src/components/bundle-editor/bundle-version-status.ts +29 -29
  68. package/src/components/dialogs/PilotCreateDialog.vue +58 -63
  69. package/src/components/dialogs/PilotEditDialog.vue +40 -34
  70. package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
  71. package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
  72. package/src/components/dialogs/types.ts +3 -13
  73. package/src/components/plan/PlanCycleToggle.vue +21 -10
  74. package/src/components/plan/PlanGrid.vue +2 -2
  75. package/src/components/plan/PriceSummary.vue +1 -1
  76. package/src/components/plan/PromoCodeInput.vue +1 -1
  77. package/src/components/plan/PublicBundleGrid.vue +1 -1
  78. package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
  79. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
  80. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
  81. package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
  82. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
  83. package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
  84. package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
  85. package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
  86. package/src/components/plan-detail/PlanAuditLog.vue +11 -6
  87. package/src/components/plan-detail/PlanDetail.vue +21 -23
  88. package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
  89. package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
  90. package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
  91. package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
  92. package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
  93. package/src/components/plan-list/PlanList.vue +99 -57
  94. package/src/components/plan-matrix/PlanMatrix.vue +85 -38
  95. package/src/components/plan-review/PlanReview.vue +105 -80
  96. package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
  97. package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
  98. package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
  99. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
  100. package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
  101. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
  102. package/src/index.ts +82 -79
  103. package/src/pages-standard/AdminLayout.vue +43 -18
  104. package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
  105. package/src/pages-standard/AuditPage.vue +58 -19
  106. package/src/pages-standard/BundlesPage.vue +29 -18
  107. package/src/pages-standard/DashboardPage.vue +31 -17
  108. package/src/pages-standard/DiscoveryPage.vue +40 -28
  109. package/src/pages-standard/EmailHistoryPage.vue +98 -69
  110. package/src/pages-standard/MarketingCatalogPage.vue +40 -21
  111. package/src/pages-standard/PilotsPage.vue +64 -52
  112. package/src/pages-standard/PlanVersionsPage.vue +9 -5
  113. package/src/pages-standard/PlansPage.vue +91 -59
  114. package/src/pages-standard/PlatformEmailPage.vue +120 -53
  115. package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
  116. package/src/pages-standard/PromoCodesPage.vue +51 -54
  117. package/src/pages-standard/SubscriptionsPage.vue +14 -9
  118. package/src/pages-standard/SuperAdminLoginPage.vue +19 -16
  119. package/src/pages-standard/SuperAdminSetupWizard.vue +53 -49
  120. package/src/pages-standard/TenantDetailPage.vue +54 -40
  121. package/src/pages-standard/TenantsPage.vue +57 -44
  122. package/src/pages-standard/UsersPage.vue +70 -52
  123. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
  124. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
  125. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
  126. package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
  127. package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
  128. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
  129. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
  130. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
  131. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
  132. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
  133. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
  134. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
  135. package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
  136. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
  137. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
  138. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
  139. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
  140. package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
  141. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +42 -26
  142. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +46 -27
  143. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +8 -6
  144. package/src/pages-standard/plan-versions/PlanVersionsList.vue +40 -17
  145. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +1 -1
  146. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +25 -13
  147. package/src/pages-standard/plan-versions/format.ts +33 -17
  148. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
  149. package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
  150. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
  151. package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
  152. package/src/pages-standard/tenants/format.ts +3 -2
  153. package/src/pages-tenant/MySubscriptionBundlesPage.vue +73 -38
  154. package/src/pages-tenant/OnboardingConfigurator.vue +3 -3
  155. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  156. package/src/pages-tenant/PlanChangeWizard.vue +10 -6
  157. package/src/pages-tenant/TenantPlanSection.vue +20 -16
  158. package/src/pages-tenant/default-i18n.ts +217 -1
  159. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +9 -7
  160. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +11 -12
  161. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
  162. package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +1 -1
  163. package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +1 -1
  164. package/src/quasar/create-super-admin-app.ts +192 -0
  165. package/src/quasar/index.ts +11 -0
  166. package/src/quasar/notify.ts +30 -0
  167. package/src/testing-e2e/admin-pages-suite.ts +14 -11
  168. package/src/{manifest-store-factory.ts → vue/manifest-store-factory.ts} +1 -1
  169. package/src/{platform-loaders.ts → vue/platform-loaders.ts} +4 -4
  170. package/src/{project-page-host.ts → vue/project-page-host.ts} +6 -4
  171. package/src/{create-super-admin-app.ts → vue/super-admin-context.ts} +20 -139
  172. package/src/vue/ui-notify.ts +30 -0
  173. package/src/{use-actions.ts → vue/use-actions.ts} +1 -1
  174. package/src/{use-api-list.ts → vue/use-api-list.ts} +1 -1
  175. package/src/{use-audit-entries.ts → vue/use-audit-entries.ts} +3 -3
  176. package/src/{use-batch-columns.ts → vue/use-batch-columns.ts} +1 -1
  177. package/src/{use-bulk-publish.ts → vue/use-bulk-publish.ts} +7 -5
  178. package/src/{use-bundle-versions-map.ts → vue/use-bundle-versions-map.ts} +7 -5
  179. package/src/{use-bundles.ts → vue/use-bundles.ts} +14 -14
  180. package/src/{use-catalog-entries.ts → vue/use-catalog-entries.ts} +14 -11
  181. package/src/{use-discovery.ts → vue/use-discovery.ts} +9 -6
  182. package/src/{use-entitlement.ts → vue/use-entitlement.ts} +4 -4
  183. package/src/{use-live-plan-versions.ts → vue/use-live-plan-versions.ts} +5 -5
  184. package/src/{use-manifest.ts → vue/use-manifest.ts} +1 -1
  185. package/src/{use-marketing-projections.ts → vue/use-marketing-projections.ts} +10 -7
  186. package/src/{use-nav.ts → vue/use-nav.ts} +9 -3
  187. package/src/{use-plan-editor.ts → vue/use-plan-editor.ts} +2 -2
  188. package/src/{use-plan-versions.ts → vue/use-plan-versions.ts} +3 -3
  189. package/src/{use-plans.ts → vue/use-plans.ts} +13 -13
  190. package/src/{use-platform-tenant-actions.ts → vue/use-platform-tenant-actions.ts} +9 -6
  191. package/src/{use-promotions.ts → vue/use-promotions.ts} +10 -11
  192. package/src/{use-public-boot.ts → vue/use-public-boot.ts} +1 -1
  193. package/src/{use-subscription-draft.ts → vue/use-subscription-draft.ts} +2 -3
  194. package/src/{use-super-admin-context.ts → vue/use-super-admin-context.ts} +5 -5
  195. package/src/vue/use-super-admin-i18n.ts +82 -0
  196. package/src/{use-tenant-action-flow.ts → vue/use-tenant-action-flow.ts} +12 -7
  197. package/src/{use-tenant-billing-catalog.ts → vue/use-tenant-billing-catalog.ts} +2 -2
  198. package/src/{use-tenant-billing.ts → vue/use-tenant-billing.ts} +2 -2
  199. package/src/{use-tenant-manifest.ts → vue/use-tenant-manifest.ts} +2 -4
  200. package/src/{use-tenant-subscription-bundles.ts → vue/use-tenant-subscription-bundles.ts} +5 -5
  201. package/src/{use-tenants.ts → vue/use-tenants.ts} +3 -3
  202. package/dist/testing-e2e/admin-pages-suite.d.cts +0 -37
  203. package/dist/testing-e2e/admin-pages-suite.d.ts +0 -37
  204. package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
  205. package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
  206. package/src/pages-standard/BusinessTypesPage.vue +0 -662
  207. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
  208. package/src/use-business-types.ts +0 -269
  209. /package/src/{http-json.ts → client/http-json.ts} +0 -0
  210. /package/src/{types.ts → client/types.ts} +0 -0
  211. /package/src/{version.ts → client/version.ts} +0 -0
  212. /package/src/{create-admin-routes.ts → vue/create-admin-routes.ts} +0 -0
  213. /package/src/{entitlement-provider.ts → vue/entitlement-provider.ts} +0 -0
  214. /package/src/{feature-router-guard.ts → vue/feature-router-guard.ts} +0 -0
@@ -1,21 +1,29 @@
1
1
  <template>
2
2
  <div class="sa-pv-list">
3
3
  <div class="sa-pv-list__kpis">
4
- <PlanVersionsKpi label="Pakete" :value="snapshot.plans.length" icon="inventory_2" />
5
4
  <PlanVersionsKpi
6
- label="Self-Service"
5
+ :label="msg.list.kpiPlans"
6
+ :value="snapshot.plans.length"
7
+ icon="inventory_2"
8
+ />
9
+ <PlanVersionsKpi
10
+ :label="msg.list.kpiSelfService"
7
11
  :value="marketedCount"
8
12
  icon="storefront"
9
- :sub="`von ${snapshot.plans.length}`"
13
+ :sub="totalPlansLabel"
14
+ />
15
+ <PlanVersionsKpi
16
+ :label="msg.list.kpiFeaturesTotal"
17
+ :value="totalFeatures"
18
+ icon="extension"
10
19
  />
11
- <PlanVersionsKpi label="Features gesamt" :value="totalFeatures" icon="extension" />
12
20
  </div>
13
21
 
14
22
  <div v-if="missingPlans.length > 0" class="sa-pv-list__missing">
15
23
  <q-icon name="info" size="16px" />
16
24
  <div>
17
- <strong>Fehlende Pakete:</strong> {{ missingPlans.join(', ') }} — keine Live-Version
18
- in diesem Snapshot.
25
+ <strong>{{ msg.list.missingPlansLabel }}</strong> {{ missingPlans.join(', ') }}
26
+ {{ msg.list.missingPlansHint }}
19
27
  <template v-if="canEdit">
20
28
  <button
21
29
  v-for="planId in missingPlans"
@@ -24,7 +32,7 @@
24
32
  class="sa-pv-list__missing-btn"
25
33
  @click="emit('createPlanDraft', planId)"
26
34
  >
27
- Draft für {{ planId }}
35
+ {{ createDraftLabel(planId) }}
28
36
  </button>
29
37
  </template>
30
38
  </div>
@@ -36,8 +44,8 @@
36
44
  class="sa-pv-list__head sa-pv-list__row sa-pv-list__row--default"
37
45
  :style="rowGridStyle"
38
46
  >
39
- <div>Paket</div>
40
- <div class="sa-pv-list__th--num">Preis (netto)</div>
47
+ <div>{{ msg.list.colPlan }}</div>
48
+ <div class="sa-pv-list__th--num">{{ msg.list.colPriceNet }}</div>
41
49
  <div
42
50
  v-for="col in quotaColumns"
43
51
  :key="`hd-${col.key}`"
@@ -45,7 +53,7 @@
45
53
  >
46
54
  {{ col.label }}
47
55
  </div>
48
- <div class="sa-pv-list__th--num">Features</div>
56
+ <div class="sa-pv-list__th--num">{{ msg.list.colFeatures }}</div>
49
57
  </div>
50
58
  </slot>
51
59
 
@@ -65,7 +73,9 @@
65
73
  <div class="sa-pv-list__default-name">
66
74
  <strong>{{ plan.planId }}</strong>
67
75
  <span class="sa-pv-list__default-version">v{{ plan.version }}</span>
68
- <span v-if="plan.isDraft" class="sa-pv-list__default-chip">DRAFT</span>
76
+ <span v-if="plan.isDraft" class="sa-pv-list__default-chip">{{
77
+ msg.status.draft
78
+ }}</span>
69
79
  </div>
70
80
  <div class="sa-pv-list__th--num">
71
81
  {{ formatEuro(plan.monthlyNet) }}
@@ -86,10 +96,10 @@
86
96
  <div v-if="snapshot.kind === 'drafts'" class="sa-pv-list__draft-hint">
87
97
  <q-icon name="info" size="18px" />
88
98
  <div>
89
- <strong>Arbeitsstand:</strong> Drafts oben in der Liste. Limits, Preise und Features
90
- per Klick auf <em>„Bearbeiten"</em> ändern (ohne MFA). Erst
91
- <strong>Publish-Flow</strong> (Validate → Diff-Review → MFA → Apply) macht sie für
92
- künftige Renewals wirksam.
99
+ <strong>{{ msg.list.draftHintLabel }}</strong> {{ msg.list.draftHintBefore }}
100
+ <em>{{ msg.list.draftHintEditButton }}</em> {{ msg.list.draftHintMiddle }}
101
+ <strong>{{ msg.list.draftHintPublishFlow }}</strong>
102
+ {{ msg.list.draftHintAfter }}
93
103
  </div>
94
104
  </div>
95
105
  </div>
@@ -97,7 +107,9 @@
97
107
 
98
108
  <script setup lang="ts">
99
109
  import { computed, ref } from 'vue';
100
- import type { CatalogSnapshot } from '../../plan-versions-catalog.js';
110
+ import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
111
+ import { formatMessage } from '../../client/i18n/format.js';
112
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
101
113
  import PlanVersionsKpi from './PlanVersionsKpi.vue';
102
114
  import { fmtEuro } from './format.js';
103
115
 
@@ -136,9 +148,16 @@ const emit = defineEmits<{
136
148
  (e: 'createPlanDraft', planId: string): void;
137
149
  }>();
138
150
 
151
+ const msg = useSaMessages('planVersions');
152
+ const { locale } = useSuperAdminI18n();
153
+
139
154
  const openId = ref<string | null>(null);
140
155
  const canEdit = computed(() => props.snapshot.kind === 'drafts');
141
156
 
157
+ const totalPlansLabel = computed(() =>
158
+ formatMessage(msg.value.list.kpiOfTotal, { count: props.snapshot.plans.length }),
159
+ );
160
+
142
161
  const marketedCount = computed(() => props.snapshot.plans.filter((p) => p.marketed).length);
143
162
  const totalFeatures = computed(() => new Set(props.snapshot.plans.flatMap((p) => p.features)).size);
144
163
 
@@ -163,8 +182,12 @@ function onToggle(id: string): void {
163
182
  openId.value = openId.value === id ? null : id;
164
183
  }
165
184
 
185
+ function createDraftLabel(planId: string): string {
186
+ return formatMessage(msg.value.list.createDraftFor, { planId });
187
+ }
188
+
166
189
  function formatEuro(n: number): string {
167
- return fmtEuro(n);
190
+ return fmtEuro(n, locale.value);
168
191
  }
169
192
 
170
193
  function formatQuotaValue(value: number | undefined, col: QuotaColumnConfig): string {
@@ -52,7 +52,7 @@
52
52
 
53
53
  <script setup lang="ts">
54
54
  import { computed } from 'vue';
55
- import type { CatalogSnapshot } from '../../plan-versions-catalog.js';
55
+ import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
56
56
  import { fmtEuro } from './format.js';
57
57
 
58
58
  // Generic feature matrix. Apps pass `featureRegistry` for label/icon/
@@ -2,9 +2,9 @@
2
2
  <aside class="sa-pv-timeline">
3
3
  <div class="sa-pv-timeline__head">
4
4
  <div class="sa-pv-timeline__row">
5
- <div class="sa-pv-timeline__title">{{ title ?? 'Plan-Catalog' }}</div>
5
+ <div class="sa-pv-timeline__title">{{ title ?? msg.timeline.title }}</div>
6
6
  </div>
7
- <div class="sa-pv-timeline__count">{{ snapshots.length }} Snapshots</div>
7
+ <div class="sa-pv-timeline__count">{{ snapshotCountLabel }}</div>
8
8
  </div>
9
9
 
10
10
  <div class="sa-pv-timeline__list">
@@ -56,7 +56,7 @@
56
56
  <div class="sa-pv-timeline__foot">
57
57
  <div class="sa-pv-timeline__hint">
58
58
  <q-icon name="info" size="13px" />
59
- <span>Rechtsklick auf Snapshot → vergleichen</span>
59
+ <span>{{ msg.timeline.compareHint }}</span>
60
60
  </div>
61
61
  <button
62
62
  v-if="compareId"
@@ -64,17 +64,20 @@
64
64
  class="sa-pv-timeline__clear-btn"
65
65
  @click="emit('clearCompare')"
66
66
  >
67
- Vergleich beenden
67
+ {{ msg.compareEnd }}
68
68
  </button>
69
69
  </div>
70
70
  </aside>
71
71
  </template>
72
72
 
73
73
  <script setup lang="ts">
74
- import type { CatalogSnapshot } from '../../plan-versions-catalog.js';
75
- import { formatRelativeDe } from './format.js';
74
+ import { computed } from 'vue';
75
+ import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
76
+ import { formatMessage } from '../../client/i18n/format.js';
77
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
78
+ import { formatRelative } from './format.js';
76
79
 
77
- defineProps<{
80
+ const props = defineProps<{
78
81
  snapshots: CatalogSnapshot[];
79
82
  selectedId: string;
80
83
  compareId: string | null;
@@ -87,18 +90,27 @@ const emit = defineEmits<{
87
90
  (e: 'clearCompare'): void;
88
91
  }>();
89
92
 
93
+ const msg = useSaMessages('planVersions');
94
+ const { locale } = useSuperAdminI18n();
95
+
96
+ const snapshotCountLabel = computed(() =>
97
+ formatMessage(msg.value.timeline.snapshotCount, { count: props.snapshots.length }),
98
+ );
99
+
90
100
  function statusLabel(status: CatalogSnapshot['status']): string {
91
- if (status === 'DRAFT') return 'DRAFT';
92
- if (status === 'ACTIVE') return 'AKTIV';
93
- return 'ARCHIV';
101
+ if (status === 'DRAFT') return msg.value.status.draft;
102
+ if (status === 'ACTIVE') return msg.value.status.active;
103
+ return msg.value.status.archived;
94
104
  }
95
105
 
96
106
  function metaText(s: CatalogSnapshot): string {
97
107
  if (s.kind === 'drafts') {
98
- return s.draftCount === 0 ? 'keine offenen Drafts' : `${s.draftCount} offen`;
108
+ return s.draftCount === 0
109
+ ? msg.value.timeline.noOpenDrafts
110
+ : formatMessage(msg.value.timeline.openCount, { count: s.draftCount });
99
111
  }
100
- if (s.publishedAt) return formatRelativeDe(s.publishedAt);
101
- return 'unbekannt';
112
+ if (s.publishedAt) return formatRelative(s.publishedAt, locale.value);
113
+ return msg.value.timeline.unknown;
102
114
  }
103
115
  </script>
104
116
 
@@ -1,13 +1,24 @@
1
1
  // Formatting helpers for the plan-versions views.
2
2
  //
3
3
  // Phase 2c: Ported from a consumer admin.
4
- // The time reference point for `formatRelativeDe` is the moment of the call,
4
+ // The time reference point for `formatRelative` is the moment of the call,
5
5
  // not a hard-coded demo date (the ported template had `2026-05-04` —
6
6
  // the platform must work in production).
7
7
 
8
- export function fmtEuro(n: number): string {
9
- if (n === 0) return 'individuell';
10
- return n.toFixed(2).replace('.', ',') + ' ';
8
+ import { formatCurrency } from '../../client/i18n/currency.js';
9
+ import { formatMessage } from '../../client/i18n/format.js';
10
+ import { DEFAULT_SA_LOCALE, SA_INTL_LOCALES, type SaLocale } from '../../client/i18n/locale.js';
11
+ import { planVersionsMessages } from '../../client/i18n/messages/plan-versions.js';
12
+
13
+ const EMPTY_VALUE = '—';
14
+
15
+ function texts(locale: SaLocale) {
16
+ return planVersionsMessages[locale].format;
17
+ }
18
+
19
+ export function fmtEuro(n: number, locale: SaLocale = DEFAULT_SA_LOCALE): string {
20
+ if (n === 0) return texts(locale).priceIndividual;
21
+ return formatCurrency(n, locale);
11
22
  }
12
23
 
13
24
  export function fmtStorage(gb: number): string {
@@ -16,34 +27,39 @@ export function fmtStorage(gb: number): string {
16
27
  return `${gb} GB`;
17
28
  }
18
29
 
19
- export function formatRelativeDe(iso: string | undefined, now: Date = new Date()): string {
20
- if (!iso) return '—';
30
+ export function formatRelative(
31
+ iso: string | undefined,
32
+ locale: SaLocale = DEFAULT_SA_LOCALE,
33
+ now: Date = new Date(),
34
+ ): string {
35
+ if (!iso) return EMPTY_VALUE;
36
+ const msg = texts(locale);
21
37
  const d = new Date(iso);
22
38
  const diffMs = now.getTime() - d.getTime();
23
39
  const days = Math.floor(diffMs / 86_400_000);
24
- if (days === 0) return 'heute';
25
- if (days === 1) return 'gestern';
26
- if (days < 7) return `vor ${days} Tagen`;
40
+ if (days === 0) return msg.today;
41
+ if (days === 1) return msg.yesterday;
42
+ if (days < 7) return formatMessage(msg.daysAgo, { count: days });
27
43
  if (days < 30) {
28
44
  const w = Math.floor(days / 7);
29
- return `vor ${w} Woche${w > 1 ? 'n' : ''}`;
45
+ return formatMessage(w > 1 ? msg.weeksAgoMany : msg.weeksAgoOne, { count: w });
30
46
  }
31
- if (days < 365) return `vor ${Math.floor(days / 30)} Monaten`;
47
+ if (days < 365) return formatMessage(msg.monthsAgo, { count: Math.floor(days / 30) });
32
48
  const y = Math.floor(days / 365);
33
- return `vor ${y} Jahr${y > 1 ? 'en' : ''}`;
49
+ return formatMessage(y > 1 ? msg.yearsAgoMany : msg.yearsAgoOne, { count: y });
34
50
  }
35
51
 
36
- export function formatDateDe(iso: string | undefined): string {
37
- if (!iso) return '—';
38
- return new Date(iso).toLocaleDateString('de-DE', {
52
+ export function formatDate(iso: string | undefined, locale: SaLocale = DEFAULT_SA_LOCALE): string {
53
+ if (!iso) return EMPTY_VALUE;
54
+ return new Date(iso).toLocaleDateString(SA_INTL_LOCALES[locale], {
39
55
  day: '2-digit',
40
56
  month: 'short',
41
57
  year: 'numeric',
42
58
  });
43
59
  }
44
60
 
45
- export function formatTsDe(iso: string): string {
46
- return new Date(iso).toLocaleString('de-DE', {
61
+ export function formatTimestamp(iso: string, locale: SaLocale = DEFAULT_SA_LOCALE): string {
62
+ return new Date(iso).toLocaleString(SA_INTL_LOCALES[locale], {
47
63
  day: '2-digit',
48
64
  month: 'short',
49
65
  year: 'numeric',
@@ -6,25 +6,25 @@
6
6
  >
7
7
  <q-card style="min-width: 440px">
8
8
  <q-card-section>
9
- <div class="text-h6">Plan komplett löschen?</div>
9
+ <div class="text-h6">{{ msg.archiveDialog.title }}</div>
10
10
  <div class="text-body2 q-mt-sm">
11
- Plan <code>{{ target?.plan.planKey }}</code> wird
12
- <b>unwiderruflich aus der DB entfernt</b>. Da der Plan keine published Version
13
- hat, sind keine Subscriptions betroffen.
11
+ {{ msg.archiveDialog.bodyLead }} <code>{{ target?.plan.planKey }}</code>
12
+ {{ msg.archiveDialog.bodyVerb }}
13
+ <b>{{ msg.archiveDialog.bodyEmphasis }}</b
14
+ >{{ msg.archiveDialog.bodyTail }}
14
15
  </div>
15
16
  <div class="text-caption text-grey-7 q-mt-sm">
16
- Vertragsschutz P1: Pläne mit jemals veröffentlichten Versionen können nicht
17
- gelöscht werden — das Backend würde 422 antworten.
17
+ {{ msg.archiveDialog.contractProtectionNote }}
18
18
  </div>
19
19
  </q-card-section>
20
20
  <q-banner v-if="error" class="bg-warning q-mx-md q-mb-md" rounded>
21
21
  {{ error }}
22
22
  </q-banner>
23
23
  <q-card-actions align="right">
24
- <q-btn flat label="Abbrechen" @click="$emit('update:modelValue', false)" />
24
+ <q-btn flat :label="common.cancel" @click="$emit('update:modelValue', false)" />
25
25
  <q-btn
26
26
  color="negative"
27
- label="Löschen"
27
+ :label="common.delete"
28
28
  :loading="archiving"
29
29
  @click="$emit('execute')"
30
30
  />
@@ -34,8 +34,12 @@
34
34
  </template>
35
35
 
36
36
  <script setup lang="ts">
37
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
37
38
  import type { PlanArchiveTarget } from './types.js';
38
39
 
40
+ const msg = useSaMessages('plans');
41
+ const common = useSaMessages('common');
42
+
39
43
  defineProps<{
40
44
  modelValue: boolean;
41
45
  target: PlanArchiveTarget | null;
@@ -1,23 +1,29 @@
1
1
  <template>
2
2
  <section class="sa-bundles">
3
3
  <div class="sa-bundles__head">
4
- <h2 class="sa-bundles__title">Bundles</h2>
4
+ <h2 class="sa-bundles__title">{{ msg.bundleOverview.title }}</h2>
5
5
  <span class="sa-bundles__count">{{ bundles.length }}</span>
6
6
  </div>
7
7
 
8
8
  <div v-if="bundles.length === 0" class="sa-bundles__empty">
9
- Keine Bundles vorhanden.
9
+ {{ msg.bundleOverview.empty }}
10
10
  </div>
11
11
 
12
12
  <div v-else class="sa-bundles__grid">
13
- <q-card v-for="bundle in bundles" :key="bundle.bundleKey" flat bordered class="sa-bundle">
13
+ <q-card
14
+ v-for="bundle in bundles"
15
+ :key="bundle.bundleKey"
16
+ flat
17
+ bordered
18
+ class="sa-bundle"
19
+ >
14
20
  <q-card-section class="sa-bundle__header">
15
21
  <div class="sa-bundle__label">{{ bundle.label ?? bundle.bundleKey }}</div>
16
22
  <div class="sa-bundle__key">{{ bundle.bundleKey }}</div>
17
23
  </q-card-section>
18
24
  <q-separator />
19
25
  <q-card-section>
20
- <div class="sa-bundle__caption">Enthält</div>
26
+ <div class="sa-bundle__caption">{{ msg.bundleOverview.contains }}</div>
21
27
  <div class="sa-bundle__chips">
22
28
  <q-chip
23
29
  v-for="feature in bundle.features"
@@ -33,7 +39,7 @@
33
39
  </div>
34
40
  </q-card-section>
35
41
  <q-card-section class="sa-bundle__compat">
36
- <div class="sa-bundle__caption">Kompatibel mit</div>
42
+ <div class="sa-bundle__caption">{{ msg.bundleOverview.compatibleWith }}</div>
37
43
  <div class="sa-bundle__plans">{{ compatLabel(bundle) }}</div>
38
44
  </q-card-section>
39
45
  </q-card>
@@ -43,6 +49,7 @@
43
49
 
44
50
  <script setup lang="ts">
45
51
  import type { PlanRow } from '@saasicat/types';
52
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
46
53
 
47
54
  interface BundleEntry {
48
55
  bundleKey: string;
@@ -57,6 +64,8 @@ const props = defineProps<{
57
64
  featureRegistry: Record<string, { label?: string; group?: string }>;
58
65
  }>();
59
66
 
67
+ const msg = useSaMessages('plans');
68
+
60
69
  function featureLabel(featureKey: string): string {
61
70
  return props.featureRegistry[featureKey]?.label ?? featureKey;
62
71
  }
@@ -68,7 +77,7 @@ function planLabel(planKey: string): string {
68
77
  // Empty compatibility list = bundle applies to all plans (cf. PlanMatrix.hasBundle).
69
78
  function compatLabel(bundle: BundleEntry): string {
70
79
  const keys = bundle.compatiblePlanKeys ?? [];
71
- if (keys.length === 0) return 'Alle Pläne';
80
+ if (keys.length === 0) return msg.value.bundleOverview.allPlans;
72
81
  return keys.map(planLabel).join(', ');
73
82
  }
74
83
  </script>
@@ -6,21 +6,20 @@
6
6
  >
7
7
  <q-card style="min-width: 400px">
8
8
  <q-card-section>
9
- <div class="text-h6">Draft verwerfen?</div>
9
+ <div class="text-h6">{{ msg.discardDialog.title }}</div>
10
10
  <div class="text-body2 q-mt-sm">
11
- Draft v{{ target?.draft.version }} von
12
- <code>{{ target?.plan.planKey }}</code> wird unwiderruflich gelöscht. Published
13
- Versions bleiben unverändert.
11
+ {{ bodyLead }}
12
+ <code>{{ target?.plan.planKey }}</code> {{ msg.discardDialog.bodyTail }}
14
13
  </div>
15
14
  </q-card-section>
16
15
  <q-banner v-if="error" class="bg-warning q-mx-md q-mb-md" rounded>
17
16
  {{ error }}
18
17
  </q-banner>
19
18
  <q-card-actions align="right">
20
- <q-btn flat label="Abbrechen" @click="$emit('update:modelValue', false)" />
19
+ <q-btn flat :label="common.cancel" @click="$emit('update:modelValue', false)" />
21
20
  <q-btn
22
21
  color="negative"
23
- label="Verwerfen"
22
+ :label="common.discard"
24
23
  :loading="discarding"
25
24
  @click="$emit('execute')"
26
25
  />
@@ -30,15 +29,27 @@
30
29
  </template>
31
30
 
32
31
  <script setup lang="ts">
32
+ import { computed } from 'vue';
33
+ import { formatMessage } from '../../client/i18n/format.js';
34
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
33
35
  import type { PlanDiscardTarget } from './types.js';
34
36
 
35
- defineProps<{
37
+ const props = defineProps<{
36
38
  modelValue: boolean;
37
39
  target: PlanDiscardTarget | null;
38
40
  error: string | null;
39
41
  discarding: boolean;
40
42
  }>();
41
43
 
44
+ const msg = useSaMessages('plans');
45
+ const common = useSaMessages('common');
46
+
47
+ const bodyLead = computed(() =>
48
+ formatMessage(msg.value.discardDialog.bodyLead, {
49
+ version: props.target?.draft.version ?? '',
50
+ }),
51
+ );
52
+
42
53
  defineEmits<{
43
54
  (e: 'update:modelValue', value: boolean): void;
44
55
  (e: 'execute'): void;
@@ -6,25 +6,24 @@
6
6
  >
7
7
  <q-card style="min-width: 420px">
8
8
  <q-card-section>
9
- <div class="text-h6">Version publishen?</div>
9
+ <div class="text-h6">{{ msg.publishDialog.title }}</div>
10
10
  <div class="text-body2 q-mt-sm">
11
- Plan <code>{{ selectedPlan?.planKey }}</code
12
- >, Draft v{{ publishTarget?.version }}.
11
+ {{ msg.publishDialog.bodyLead }} <code>{{ selectedPlan?.planKey }}</code
12
+ >{{ bodyTail }}
13
13
  </div>
14
14
  <div class="text-caption text-grey-7 q-mt-sm">
15
- Eine ggf. zuvor live Version wird auf <code>supersededAt</code> gesetzt
16
- (Vertragsschutz P1: Bestand-Subscriptions bleiben auf der alten Version bis zum
17
- nächsten Renewal).
15
+ {{ msg.publishDialog.supersededNoteLead }} <code>supersededAt</code>
16
+ {{ msg.publishDialog.supersededNoteTail }}
18
17
  </div>
19
18
  <q-checkbox
20
19
  :model-value="forceRegressive"
21
- label="Force-Publish auch bei Regression (Feature entfernt / Quota gesenkt / Preis erhöht)"
20
+ :label="msg.publishDialog.forceRegressive"
22
21
  class="q-mt-md"
23
22
  @update:model-value="$emit('update:forceRegressive', Boolean($event))"
24
23
  />
25
24
  <q-checkbox
26
25
  :model-value="allowZeroPrice"
27
- label="Preis 0,00 bewusst zulassen (kostenloser Sondervertrag)"
26
+ :label="msg.publishDialog.allowZeroPrice"
28
27
  class="q-mt-sm"
29
28
  @update:model-value="$emit('update:allowZeroPrice', Boolean($event))"
30
29
  />
@@ -33,7 +32,7 @@
33
32
  {{ publishError }}
34
33
  </q-banner>
35
34
  <q-card-section v-if="regressionChanges.length > 0" class="q-pt-none">
36
- <div class="text-subtitle2 q-mb-xs">Regressive Änderungen:</div>
35
+ <div class="text-subtitle2 q-mb-xs">{{ msg.publishDialog.regressiveChanges }}</div>
37
36
  <ul class="sa-publish-regression">
38
37
  <li v-for="c in regressionChanges" :key="c.field">
39
38
  <strong>{{ fieldLabel(c.field) }}:</strong>
@@ -48,10 +47,10 @@
48
47
  </ul>
49
48
  </q-card-section>
50
49
  <q-card-actions align="right">
51
- <q-btn flat label="Abbrechen" @click="$emit('update:modelValue', false)" />
50
+ <q-btn flat :label="common.cancel" @click="$emit('update:modelValue', false)" />
52
51
  <q-btn
53
52
  color="primary"
54
- label="Publish"
53
+ :label="msg.publishDialog.confirm"
55
54
  :loading="publishing"
56
55
  @click="$emit('execute')"
57
56
  />
@@ -61,10 +60,13 @@
61
60
  </template>
62
61
 
63
62
  <script setup lang="ts">
63
+ import { computed } from 'vue';
64
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';
65
67
  import type { RegressionChange } from './types.js';
66
68
 
67
- defineProps<{
69
+ const props = defineProps<{
68
70
  modelValue: boolean;
69
71
  selectedPlan: PlanRow | null;
70
72
  publishTarget: PlanVersionRow | null;
@@ -77,6 +79,15 @@ defineProps<{
77
79
  formatChangeValue: (value: unknown) => string;
78
80
  }>();
79
81
 
82
+ const msg = useSaMessages('plans');
83
+ const common = useSaMessages('common');
84
+
85
+ const bodyTail = computed(() =>
86
+ formatMessage(msg.value.publishDialog.bodyTail, {
87
+ version: props.publishTarget?.version ?? '',
88
+ }),
89
+ );
90
+
80
91
  defineEmits<{
81
92
  (e: 'update:modelValue', value: boolean): void;
82
93
  (e: 'update:forceRegressive', value: boolean): void;
@@ -28,9 +28,10 @@ export function planAccent(
28
28
  return accents[planId] ?? '#3f6bff';
29
29
  }
30
30
 
31
- export function formatDateDe(iso: string | null | undefined): string {
31
+ /** `locale` is a BCP-47 tag pages pass `intlLocale.value` of the active UI locale. */
32
+ export function formatDate(iso: string | null | undefined, locale: string): string {
32
33
  if (!iso) return '—';
33
- return new Date(iso).toLocaleDateString('de-DE', {
34
+ return new Date(iso).toLocaleDateString(locale, {
34
35
  day: '2-digit',
35
36
  month: 'short',
36
37
  year: 'numeric',