@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
@@ -2,12 +2,14 @@
2
2
  <section v-if="auditRows.length > 0 || loadingAudit" class="pc-card pc-audit-card">
3
3
  <div class="pc-card-head pc-card-head--audit">
4
4
  <div class="pc-card-head-text">
5
- <div class="pc-card-title">Audit-Log</div>
6
- <div class="pc-card-sub">Letzte Änderungen an diesem Plan</div>
5
+ <div class="pc-card-title">{{ msg.auditLog.title }}</div>
6
+ <div class="pc-card-sub">{{ msg.auditLog.subtitle }}</div>
7
7
  </div>
8
8
  </div>
9
9
  <div class="pc-audit">
10
- <div v-if="loadingAudit" class="pc-empty pc-empty--inline">Lade Audit-Log…</div>
10
+ <div v-if="loadingAudit" class="pc-empty pc-empty--inline">
11
+ {{ msg.auditLog.loading }}
12
+ </div>
11
13
  <div v-for="a in auditRows" :key="a.id" class="pc-audit-row">
12
14
  <span :class="['pc-audit-dot', `pc-audit-${auditKind(a.action)}`]" />
13
15
  <span class="pc-audit-when">{{ formatAuditDate(a.createdAt) }}</span>
@@ -20,6 +22,8 @@
20
22
  </template>
21
23
 
22
24
  <script setup lang="ts">
25
+ import { formatMessage } from '../../client/i18n/format.js';
26
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
23
27
  import type { AuditRow } from './types';
24
28
 
25
29
  defineProps<{
@@ -27,6 +31,9 @@ defineProps<{
27
31
  loadingAudit: boolean;
28
32
  }>();
29
33
 
34
+ const msg = useSaMessages('planDetail');
35
+ const { intlLocale } = useSuperAdminI18n();
36
+
30
37
  function auditKind(action: string): string {
31
38
  const a = action.toLowerCase();
32
39
  if (a.includes('publish')) return 'publish';
@@ -58,8 +65,8 @@ function formatAuditDate(iso: string): string {
58
65
  if (Number.isNaN(d.getTime())) return iso;
59
66
  const today = new Date();
60
67
  const isToday = d.toDateString() === today.toDateString();
61
- const time = d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
62
- if (isToday) return `heute · ${time}`;
63
- return `${d.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' })} · ${time}`;
68
+ const time = d.toLocaleTimeString(intlLocale.value, { hour: '2-digit', minute: '2-digit' });
69
+ if (isToday) return formatMessage(msg.value.auditLog.today, { time });
70
+ return `${d.toLocaleDateString(intlLocale.value, { day: '2-digit', month: 'short' })} · ${time}`;
64
71
  }
65
72
  </script>
@@ -2,14 +2,9 @@
2
2
  <section class="pc-card">
3
3
  <div class="pc-card-head">
4
4
  <div class="pc-card-head-text">
5
- <div class="pc-card-title">
6
- <template v-if="diffPair">
7
- Diff v{{ diffPair.from.version }} → v{{ diffPair.to.version }}
8
- </template>
9
- <template v-else>Diff</template>
10
- </div>
5
+ <div class="pc-card-title">{{ panelTitle }}</div>
11
6
  <div class="pc-card-sub">
12
- {{ diffPair ? 'Was sich beim Publish ändert' : 'Kein Vergleich verfügbar' }}
7
+ {{ diffPair ? msg.diff.subOnPublish : msg.diff.noComparison }}
13
8
  </div>
14
9
  </div>
15
10
  <div v-if="diffPair" class="pc-pillrow">
@@ -74,18 +69,32 @@
74
69
  </span>
75
70
  </div>
76
71
  <div v-if="diffRows.length === 0" class="pc-empty pc-empty--inline">
77
- Keine Änderungen erkannt.
72
+ {{ msg.diff.noChangesDetected }}
78
73
  </div>
79
74
  </div>
80
75
  </section>
81
76
  </template>
82
77
 
83
78
  <script setup lang="ts">
79
+ import { computed } from 'vue';
80
+ import { formatMessage } from '../../client/i18n/format.js';
81
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
84
82
  import type { DiffRow, DiffSummary, PlanVersionPair } from './types';
85
83
 
86
- defineProps<{
84
+ const props = defineProps<{
87
85
  diffPair: PlanVersionPair | null;
88
86
  diff: DiffSummary;
89
87
  diffRows: DiffRow[];
90
88
  }>();
89
+
90
+ const msg = useSaMessages('planDetail');
91
+
92
+ const panelTitle = computed(() => {
93
+ const pair = props.diffPair;
94
+ if (!pair) return msg.value.diff.title;
95
+ return formatMessage(msg.value.diff.titleCompare, {
96
+ from: pair.from.version,
97
+ to: pair.to.version,
98
+ });
99
+ });
91
100
  </script>
@@ -13,7 +13,7 @@
13
13
  <path d="M5 12h14M13 5l7 7-7 7" />
14
14
  </svg>
15
15
  </span>
16
- <span>Zurück zur Liste</span>
16
+ <span>{{ msg.header.backToList }}</span>
17
17
  </button>
18
18
  </div>
19
19
 
@@ -24,7 +24,7 @@
24
24
  </div>
25
25
  <div class="pc-header-titles">
26
26
  <h2 class="pc-h-title">{{ plan.label }}</h2>
27
- <p class="pc-h-sub">{{ plan.description || 'Keine Beschreibung.' }}</p>
27
+ <p class="pc-h-sub">{{ plan.description || msg.header.noDescription }}</p>
28
28
  </div>
29
29
  </div>
30
30
  <div class="pc-header-right">
@@ -40,7 +40,7 @@
40
40
  <rect x="9" y="9" width="11" height="11" rx="2" />
41
41
  <path d="M5 15V5a2 2 0 012-2h10" />
42
42
  </svg>
43
- <span>Plan klonen</span>
43
+ <span>{{ msg.header.clonePlan }}</span>
44
44
  </button>
45
45
  <button class="pc-btn" type="button" @click="emit('viewCatalog')">
46
46
  <svg
@@ -54,13 +54,13 @@
54
54
  <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
55
55
  <circle cx="12" cy="12" r="3" />
56
56
  </svg>
57
- <span>Im Katalog ansehen</span>
57
+ <span>{{ msg.header.viewInCatalog }}</span>
58
58
  </button>
59
59
  <button
60
60
  class="pc-btn pc-btn--primary"
61
61
  type="button"
62
62
  :disabled="hasOpenDraft"
63
- :title="hasOpenDraft ? 'Es gibt bereits eine offene Draft' : undefined"
63
+ :title="hasOpenDraft ? msg.header.draftAlreadyOpen : undefined"
64
64
  @click="emit('createDraft')"
65
65
  >
66
66
  <svg
@@ -73,7 +73,7 @@
73
73
  >
74
74
  <path d="M12 5v14M5 12h14" />
75
75
  </svg>
76
- <span>Neue Draft-Version</span>
76
+ <span>{{ msg.header.newDraftVersion }}</span>
77
77
  </button>
78
78
  </div>
79
79
  </div>
@@ -81,6 +81,7 @@
81
81
 
82
82
  <script setup lang="ts">
83
83
  import type { PlanRow } from '@saasicat/types';
84
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
84
85
 
85
86
  defineProps<{
86
87
  plan: PlanRow;
@@ -95,4 +96,6 @@ const emit = defineEmits<{
95
96
  (e: 'viewCatalog'): void;
96
97
  (e: 'createDraft'): void;
97
98
  }>();
99
+
100
+ const msg = useSaMessages('planDetail');
98
101
  </script>
@@ -2,10 +2,8 @@
2
2
  <section v-if="tenantImpact" class="pc-card">
3
3
  <div class="pc-card-head">
4
4
  <div class="pc-card-head-text">
5
- <div class="pc-card-title">Tenant-Impact</div>
6
- <div class="pc-card-sub">
7
- Wer ist betroffen, wenn die nächste Version published wird
8
- </div>
5
+ <div class="pc-card-title">{{ msg.kpis.tenantImpact }}</div>
6
+ <div class="pc-card-sub">{{ msg.impact.subtitle }}</div>
9
7
  </div>
10
8
  </div>
11
9
  <div class="pc-impact-strip">
@@ -19,7 +17,7 @@
19
17
  background: '#10b981',
20
18
  }"
21
19
  />
22
- <span class="pc-impact-bar-label">{{ tenantImpact.auto }} auto-migrieren</span>
20
+ <span class="pc-impact-bar-label">{{ autoLabel }}</span>
23
21
  </div>
24
22
  <div class="pc-impact-bar">
25
23
  <span
@@ -29,9 +27,7 @@
29
27
  background: '#f59e0b',
30
28
  }"
31
29
  />
32
- <span class="pc-impact-bar-label"
33
- >{{ tenantImpact.review }} brauchen Review</span
34
- >
30
+ <span class="pc-impact-bar-label">{{ reviewLabel }}</span>
35
31
  </div>
36
32
  <div class="pc-impact-bar">
37
33
  <span
@@ -41,7 +37,7 @@
41
37
  background: '#ef4444',
42
38
  }"
43
39
  />
44
- <span class="pc-impact-bar-label">{{ tenantImpact.conflict }} Konflikt</span>
40
+ <span class="pc-impact-bar-label">{{ conflictLabel }}</span>
45
41
  </div>
46
42
  </div>
47
43
  </div>
@@ -64,13 +60,28 @@
64
60
  </template>
65
61
 
66
62
  <script setup lang="ts">
63
+ import { computed } from 'vue';
64
+ import { formatMessage } from '../../client/i18n/format.js';
65
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
67
66
  import type { ImpactTenant, TenantImpact } from './types';
68
67
 
69
- defineProps<{
68
+ const props = defineProps<{
70
69
  tenantImpact: TenantImpact | null;
71
70
  tenantImpactTotal: number;
72
71
  }>();
73
72
 
73
+ const msg = useSaMessages('planDetail');
74
+
75
+ const autoLabel = computed(() =>
76
+ formatMessage(msg.value.impact.autoMigrate, { count: props.tenantImpact?.auto ?? 0 }),
77
+ );
78
+ const reviewLabel = computed(() =>
79
+ formatMessage(msg.value.impact.needReview, { count: props.tenantImpact?.review ?? 0 }),
80
+ );
81
+ const conflictLabel = computed(() =>
82
+ formatMessage(msg.value.impact.conflict, { count: props.tenantImpact?.conflict ?? 0 }),
83
+ );
84
+
74
85
  function pct(part: number, total: number): string {
75
86
  if (total <= 0) return '0%';
76
87
  return `${Math.max(2, Math.round((part / total) * 100))}%`;
@@ -86,7 +97,9 @@ function initialsOf(name: string): string {
86
97
  }
87
98
 
88
99
  function impactStateLabel(state: ImpactTenant['state']): string {
89
- return state === 'auto' ? 'auto' : state === 'review' ? 'review' : 'Konflikt';
100
+ if (state === 'auto') return msg.value.impact.stateAuto;
101
+ if (state === 'review') return msg.value.impact.stateReview;
102
+ return msg.value.impact.stateConflict;
90
103
  }
91
104
 
92
105
  function impactStateChip(state: ImpactTenant['state']): string {
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="pc-kpis">
3
3
  <div class="pc-kpi">
4
- <div class="pc-kpi-label">Aktive Version</div>
4
+ <div class="pc-kpi-label">{{ msg.kpis.activeVersion }}</div>
5
5
  <div class="pc-kpi-val">
6
6
  <template v-if="liveVersion">
7
7
  v{{ liveVersion.version }}
@@ -9,44 +9,39 @@
9
9
  </template>
10
10
  <template v-else>—</template>
11
11
  </div>
12
- <div class="pc-kpi-sub">
13
- {{
14
- liveVersion?.validFrom
15
- ? `seit ${liveVersion.validFrom.slice(0, 10)}`
16
- : 'keine veröffentlichte Version'
17
- }}
18
- </div>
12
+ <div class="pc-kpi-sub">{{ activeVersionSub }}</div>
19
13
  </div>
20
14
  <div class="pc-kpi">
21
- <div class="pc-kpi-label">Tenant-Impact</div>
15
+ <div class="pc-kpi-label">{{ msg.kpis.tenantImpact }}</div>
22
16
  <div class="pc-kpi-val">{{ tenantImpactTotal }}</div>
23
- <div class="pc-kpi-sub">Mandanten auf diesem Plan</div>
17
+ <div class="pc-kpi-sub">{{ msg.kpis.tenantsOnPlan }}</div>
24
18
  </div>
25
19
  <div class="pc-kpi">
26
- <div class="pc-kpi-label">Versionen</div>
20
+ <div class="pc-kpi-label">{{ msg.kpis.versions }}</div>
27
21
  <div class="pc-kpi-val">{{ versionsCount }}</div>
28
- <div class="pc-kpi-sub">
29
- {{ publishedCount }} live/superseded · {{ draftCount }} Draft
30
- </div>
22
+ <div class="pc-kpi-sub">{{ versionsSummary }}</div>
31
23
  </div>
32
24
  <div :class="['pc-kpi', draftVersion ? 'pc-kpi--draft' : '']">
33
25
  <div class="pc-kpi-label" :style="draftVersion ? 'color:#b45309' : ''">
34
- {{ draftVersion ? 'Offener Draft' : 'Kein Draft' }}
26
+ {{ draftVersion ? msg.kpis.openDraft : msg.kpis.noDraft }}
35
27
  </div>
36
28
  <div class="pc-kpi-val">
37
29
  {{ draftVersion ? `v${draftVersion.version}` : '—' }}
38
30
  </div>
39
31
  <div class="pc-kpi-sub" :style="draftVersion ? 'color:#b45309' : ''">
40
- {{ draftVersion ? 'Bereit zum Bearbeiten' : 'Neue Draft anlegen' }}
32
+ {{ draftVersion ? msg.kpis.readyToEdit : msg.kpis.createNewDraft }}
41
33
  </div>
42
34
  </div>
43
35
  </div>
44
36
  </template>
45
37
 
46
38
  <script setup lang="ts">
39
+ import { computed } from 'vue';
47
40
  import type { PlanVersionRow } from '@saasicat/types';
41
+ import { formatMessage } from '../../client/i18n/format.js';
42
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
48
43
 
49
- defineProps<{
44
+ const props = defineProps<{
50
45
  liveVersion: PlanVersionRow | null;
51
46
  tenantImpactTotal: number;
52
47
  versionsCount: number;
@@ -54,4 +49,20 @@ defineProps<{
54
49
  draftCount: number;
55
50
  draftVersion: PlanVersionRow | null;
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
+ formatMessage(msg.value.kpis.versionsSummaryWithDrafts, {
64
+ published: props.publishedCount,
65
+ drafts: props.draftCount,
66
+ }),
67
+ );
57
68
  </script>
@@ -2,9 +2,9 @@
2
2
  <section class="pc-card pc-card--left">
3
3
  <div class="pc-card-head">
4
4
  <div class="pc-card-head-text">
5
- <div class="pc-card-title">Versionen</div>
5
+ <div class="pc-card-title">{{ msg.versions.title }}</div>
6
6
  <div class="pc-card-sub">
7
- Pro Plan max. 1 offene Draft-Version · Publish setzt Vorgänger auf
7
+ {{ msg.versions.subtitle }}
8
8
  <code class="pc-mono">supersededAt</code>
9
9
  </div>
10
10
  </div>
@@ -24,7 +24,7 @@
24
24
  >
25
25
  <path d="M12 5v14M5 12h14" />
26
26
  </svg>
27
- <span>Draft v{{ nextDraftVersion }}</span>
27
+ <span>{{ newDraftButtonLabel }}</span>
28
28
  </button>
29
29
  </div>
30
30
 
@@ -36,7 +36,10 @@
36
36
  :class="['pc-vtl-seg', `pc-vtl-${seg.status}`]"
37
37
  :style="{ flex: seg.flex }"
38
38
  >
39
- v{{ seg.version }}<template v-if="seg.status === 'draft'"> · draft</template>
39
+ v{{ seg.version
40
+ }}<template v-if="seg.status === 'draft'">{{
41
+ msg.versions.timelineDraftSuffix
42
+ }}</template>
40
43
  </div>
41
44
  </div>
42
45
  <div class="pc-vtl-axis">
@@ -45,15 +48,19 @@
45
48
  </div>
46
49
 
47
50
  <div class="pc-vrow pc-vrow--head">
48
- <div class="pc-vrow-version pc-vrow-headlabel">Version</div>
49
- <div class="pc-vrow-cell pc-vrow-headlabel">Gültig</div>
50
- <div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">Pricing</div>
51
- <div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">Impact</div>
52
- <div class="pc-vrow-note pc-vrow-headlabel">Change-Note</div>
51
+ <div class="pc-vrow-version pc-vrow-headlabel">{{ msg.versions.colVersion }}</div>
52
+ <div class="pc-vrow-cell pc-vrow-headlabel">{{ msg.versions.colValidity }}</div>
53
+ <div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">
54
+ {{ msg.versions.colPricing }}
55
+ </div>
56
+ <div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">
57
+ {{ msg.versions.colImpact }}
58
+ </div>
59
+ <div class="pc-vrow-note pc-vrow-headlabel">{{ msg.versions.colChangeNoteShort }}</div>
53
60
  <div class="pc-vrow-actions" />
54
61
  </div>
55
62
  <div v-if="versions.length === 0" class="pc-empty">
56
- Noch keine Versionen — „Neue Draft-Version" oben rechts.
63
+ {{ msg.versions.empty }}
57
64
  </div>
58
65
  <div
59
66
  v-for="row in sortedVersions"
@@ -77,9 +84,12 @@
77
84
  </div>
78
85
  <div class="pc-vrow-cell pc-vrow-cell--right">
79
86
  <div class="pc-vrow-price">
80
- {{ formatMoney(row.monthlyNet) }}<span class="pc-vrow-price-unit">/ Mo</span>
87
+ {{ formatMoney(row.monthlyNet)
88
+ }}<span class="pc-vrow-price-unit">{{ msg.versions.perMonthUnit }}</span>
89
+ </div>
90
+ <div class="pc-vrow-sub">
91
+ {{ formatMoney(row.yearlyNet) }} {{ msg.versions.perYearUnit }}
81
92
  </div>
82
- <div class="pc-vrow-sub">{{ formatMoney(row.yearlyNet) }} / Jahr</div>
83
93
  </div>
84
94
  <div class="pc-vrow-cell pc-vrow-cell--right">
85
95
  <div
@@ -88,7 +98,7 @@
88
98
  >
89
99
  {{ impactByVersion[row.version] ?? 0 }}
90
100
  </div>
91
- <div class="pc-vrow-sub">Mandanten</div>
101
+ <div class="pc-vrow-sub">{{ msg.versions.tenants }}</div>
92
102
  </div>
93
103
  <div class="pc-vrow-note" :title="row.changeNote ?? ''">
94
104
  {{ row.changeNote || '—' }}
@@ -110,7 +120,7 @@
110
120
  >
111
121
  <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
112
122
  </svg>
113
- <span>Publish</span>
123
+ <span>{{ msg.versions.publish }}</span>
114
124
  </button>
115
125
  <button
116
126
  v-if="editabilityOf(row).editable"
@@ -118,8 +128,8 @@
118
128
  type="button"
119
129
  :title="
120
130
  editabilityOf(row).reason === 'pre-active'
121
- ? 'Published, aber noch nicht aktiv — Features, Quotas und Preis können bis zum Aktivierungsdatum noch korrigiert werden'
122
- : 'Draft bearbeiten'
131
+ ? msg.versions.preActiveTitle
132
+ : msg.versions.editDraftTitle
123
133
  "
124
134
  @click="emit('editDraft', row)"
125
135
  >
@@ -133,7 +143,7 @@
133
143
  >
134
144
  <path d="M12 20h9M16.5 3.5a2.12 2.12 0 113 3L7 19l-4 1 1-4 12.5-12.5z" />
135
145
  </svg>
136
- <span>Bearbeiten</span>
146
+ <span>{{ common.edit }}</span>
137
147
  </button>
138
148
  <button
139
149
  v-if="row.publishedAt !== null && row.supersededAt === null && draftVersion"
@@ -152,7 +162,7 @@
152
162
  <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
153
163
  <circle cx="12" cy="12" r="3" />
154
164
  </svg>
155
- <span>Diff</span>
165
+ <span>{{ msg.versions.viewDiff }}</span>
156
166
  </button>
157
167
  </div>
158
168
  </div>
@@ -160,10 +170,14 @@
160
170
  </template>
161
171
 
162
172
  <script setup lang="ts">
173
+ import { computed } from 'vue';
163
174
  import { isVersionEditable, type PlanVersionRow } from '@saasicat/types';
175
+ import { formatMessage } from '../../client/i18n/format.js';
176
+ import { formatCurrency } from '../../client/i18n/currency.js';
177
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
164
178
  import type { TimelineSegment } from './types';
165
179
 
166
- defineProps<{
180
+ const props = defineProps<{
167
181
  versions: PlanVersionRow[];
168
182
  sortedVersions: PlanVersionRow[];
169
183
  timelineSegments: TimelineSegment[];
@@ -181,6 +195,14 @@ const emit = defineEmits<{
181
195
  (e: 'viewDiff', from: PlanVersionRow, to: PlanVersionRow): void;
182
196
  }>();
183
197
 
198
+ const msg = useSaMessages('planDetail');
199
+ const { locale } = useSuperAdminI18n();
200
+ const common = useSaMessages('common');
201
+
202
+ const newDraftButtonLabel = computed(() =>
203
+ formatMessage(msg.value.versions.newDraftButton, { version: props.nextDraftVersion }),
204
+ );
205
+
184
206
  function editabilityOf(version: PlanVersionRow): {
185
207
  editable: boolean;
186
208
  reason: 'draft' | 'pre-active' | null;
@@ -203,8 +225,7 @@ function statusLabel(row: PlanVersionRow): string {
203
225
  function formatMoney(raw: string | number): string {
204
226
  const num = typeof raw === 'string' ? Number(raw) : raw;
205
227
  if (!Number.isFinite(num)) return String(raw);
206
- if (Number.isInteger(num)) return `${num} €`;
207
- return `${num.toFixed(2).replace('.', ',')} €`;
228
+ return formatCurrency(num, locale.value);
208
229
  }
209
230
 
210
231
  function updatedLabel(row: PlanVersionRow): string {
@@ -218,9 +239,13 @@ function updatedLabel(row: PlanVersionRow): string {
218
239
  if (Number.isNaN(d.getTime())) return '';
219
240
  const diffMs = Date.now() - d.getTime();
220
241
  const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
221
- if (days <= 0) return 'heute';
222
- if (days < 30) return `vor ${days} Tagen`;
223
- if (days < 365) return `vor ${Math.floor(days / 30)} Monaten`;
224
- return `vor ${Math.floor(days / 365)} Jahren`;
242
+ if (days <= 0) return msg.value.versions.updatedToday;
243
+ if (days < 30) return formatMessage(msg.value.versions.updatedDaysAgo, { days });
244
+ if (days < 365) {
245
+ return formatMessage(msg.value.versions.updatedMonthsAgo, {
246
+ months: Math.floor(days / 30),
247
+ });
248
+ }
249
+ return formatMessage(msg.value.versions.updatedYearsAgo, { years: Math.floor(days / 365) });
225
250
  }
226
251
  </script>