@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,9 +2,9 @@
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">Versionen</h3>
5
+ <h3 class="pd-panel-title">{{ msg.versions.title }}</h3>
6
6
  <div class="pd-panel-sub">
7
- Pro Plan max. 1 offene Draft-Version · Publish setzt Vorgänger auf
7
+ {{ msg.versions.subtitle }}
8
8
  <code class="pd-code">supersededAt</code>
9
9
  </div>
10
10
  </div>
@@ -25,7 +25,7 @@
25
25
  >
26
26
  <path d="M12 5v14M5 12h14" />
27
27
  </svg>
28
- <span>Draft v{{ nextDraftVersion }}</span>
28
+ <span>{{ newDraftButtonLabel }}</span>
29
29
  </button>
30
30
  </div>
31
31
  </div>
@@ -44,7 +44,7 @@
44
44
  <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
45
45
  </svg>
46
46
  </span>
47
- Klicke eine Version, um sie zu wählen
47
+ {{ msg.versions.timelineHint }}
48
48
  </div>
49
49
  <div class="pd-timeline-bar">
50
50
  <div
@@ -56,10 +56,13 @@
56
56
  v.id === selectedId ? 'is-selected' : '',
57
57
  ]"
58
58
  :style="{ flex: statusOf(v) === 'draft' ? 1.4 : 1 }"
59
- :title="`v${v.version} wählen (${statusOf(v)})`"
59
+ :title="timelineSelectTitle(v)"
60
60
  @click="$emit('update:selectedId', v.id)"
61
61
  >
62
- v{{ v.version }}<template v-if="statusOf(v) === 'draft'"> · draft</template>
62
+ v{{ v.version
63
+ }}<template v-if="statusOf(v) === 'draft'">{{
64
+ msg.versions.timelineDraftSuffix
65
+ }}</template>
63
66
  </div>
64
67
  </div>
65
68
  <div class="pd-timeline-ticks">
@@ -69,11 +72,11 @@
69
72
 
70
73
  <div class="pd-versions-tbl">
71
74
  <div class="pd-versions-head">
72
- <div>Version</div>
73
- <div>Gültig</div>
74
- <div>Preis</div>
75
- <div>Auswirkung</div>
76
- <div>Änderungsnotiz</div>
75
+ <div>{{ msg.versions.colVersion }}</div>
76
+ <div>{{ msg.versions.colValidity }}</div>
77
+ <div>{{ msg.versions.colPrice }}</div>
78
+ <div>{{ msg.versions.colEffect }}</div>
79
+ <div>{{ msg.versions.colChangeNote }}</div>
77
80
  <div></div>
78
81
  </div>
79
82
 
@@ -107,27 +110,23 @@
107
110
  </span>
108
111
  <span v-else class="pd-arrow-inf" style="font-size: 14px">∞</span>
109
112
  </div>
110
- <span class="pd-validity-sub">
111
- {{
112
- statusOf(v) === 'draft'
113
- ? 'geplant'
114
- : statusOf(v) === 'live'
115
- ? 'aktiv'
116
- : 'historisch'
117
- }}
118
- </span>
113
+ <span class="pd-validity-sub">{{ validityLabel(v) }}</span>
119
114
  </div>
120
115
  </div>
121
116
  <div>
122
117
  <div>
123
- <div class="pd-pricing-m">{{ formatMoney(v.monthlyNet) }} / Mo</div>
124
- <div class="pd-pricing-y">{{ formatMoney(v.yearlyNet) }} / Jahr</div>
118
+ <div class="pd-pricing-m">
119
+ {{ formatMoney(v.monthlyNet) }} {{ msg.versions.perMonthUnit }}
120
+ </div>
121
+ <div class="pd-pricing-y">
122
+ {{ formatMoney(v.yearlyNet) }} {{ msg.versions.perYearUnit }}
123
+ </div>
125
124
  </div>
126
125
  </div>
127
126
  <div>
128
127
  <div>
129
128
  <div class="pd-impact-num">{{ impactByVersion[v.version] ?? 0 }}</div>
130
- <div class="pd-impact-sub">Mandanten</div>
129
+ <div class="pd-impact-sub">{{ msg.versions.tenants }}</div>
131
130
  </div>
132
131
  </div>
133
132
  <div>
@@ -139,18 +138,15 @@
139
138
  <span
140
139
  v-if="editabilityOf(v).reason === 'pre-active'"
141
140
  class="pd-pre-active-chip"
142
- :title="
143
- 'Published, aber noch nicht aktiv — Features, Quotas und ' +
144
- 'Preis können bis zum Aktivierungsdatum noch korrigiert werden'
145
- "
141
+ :title="msg.versions.preActiveTitle"
146
142
  >
147
- editierbar · noch nicht aktiv
143
+ {{ msg.versions.preActiveChip }}
148
144
  </span>
149
145
  <template v-if="statusOf(v) === 'draft'">
150
146
  <button
151
147
  class="btn btn--sm primary"
152
148
  type="button"
153
- title="Draft veröffentlichen"
149
+ :title="msg.versions.publishDraftTitle"
154
150
  @click="$emit('publish', v)"
155
151
  >
156
152
  <svg
@@ -171,8 +167,8 @@
171
167
  type="button"
172
168
  :title="
173
169
  editabilityOf(v).reason === 'pre-active'
174
- ? 'Future-Version bearbeiten'
175
- : 'Draft bearbeiten'
170
+ ? msg.versions.editFutureVersionTitle
171
+ : msg.versions.editDraftTitle
176
172
  "
177
173
  @click="$emit('editDraft', v)"
178
174
  >
@@ -194,14 +190,18 @@
194
190
  <span
195
191
  v-if="v.endsAt"
196
192
  class="pd-endsat-badge"
197
- :title="`Endet am ${formatDate(v.endsAt)}`"
193
+ :title="endsAtTitle(v.endsAt)"
198
194
  >
199
- Endet {{ formatDate(v.endsAt) }}
195
+ {{ endsAtBadge(v.endsAt) }}
200
196
  </span>
201
197
  <button
202
198
  class="btn btn--sm"
203
199
  type="button"
204
- :title="v.endsAt ? 'Enddatum ändern' : 'Version terminieren'"
200
+ :title="
201
+ v.endsAt
202
+ ? msg.terminateDialog.titleChangeEndDate
203
+ : msg.terminateDialog.titleTerminate
204
+ "
205
205
  @click="$emit('openTerminate', v)"
206
206
  >
207
207
  <svg
@@ -217,7 +217,11 @@
217
217
  <line x1="8" y1="2" x2="8" y2="6" />
218
218
  <line x1="3" y1="10" x2="21" y2="10" />
219
219
  </svg>
220
- <span>{{ v.endsAt ? 'Enddatum…' : 'Terminieren…' }}</span>
220
+ <span>{{
221
+ v.endsAt
222
+ ? msg.versions.changeEndDateAction
223
+ : msg.versions.terminateAction
224
+ }}</span>
221
225
  </button>
222
226
  </template>
223
227
  </div>
@@ -227,10 +231,13 @@
227
231
  </template>
228
232
 
229
233
  <script setup lang="ts">
234
+ import { computed } from 'vue';
230
235
  import type { PlanVersionRow } from '@saasicat/types';
236
+ import { formatMessage } from '../../client/i18n/format.js';
237
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
231
238
  import type { EditabilityOf, StatusChipOf, StatusOf } from './types.js';
232
239
 
233
- defineProps<{
240
+ const props = defineProps<{
234
241
  chronological: PlanVersionRow[];
235
242
  tableRows: PlanVersionRow[];
236
243
  selectedId: string | null;
@@ -252,4 +259,32 @@ defineEmits<{
252
259
  (e: 'editDraft', version: PlanVersionRow): void;
253
260
  (e: 'openTerminate', version: PlanVersionRow): void;
254
261
  }>();
262
+
263
+ const msg = useSaMessages('planDetail');
264
+
265
+ const newDraftButtonLabel = computed(() =>
266
+ formatMessage(msg.value.versions.newDraftButton, { version: props.nextDraftVersion }),
267
+ );
268
+
269
+ function timelineSelectTitle(version: PlanVersionRow): string {
270
+ return formatMessage(msg.value.versions.timelineSelectTitle, {
271
+ version: version.version,
272
+ status: props.statusOf(version),
273
+ });
274
+ }
275
+
276
+ function validityLabel(version: PlanVersionRow): string {
277
+ const status = props.statusOf(version);
278
+ if (status === 'draft') return msg.value.versions.validityPlanned;
279
+ if (status === 'live') return msg.value.versions.validityActive;
280
+ return msg.value.versions.validityHistoric;
281
+ }
282
+
283
+ function endsAtTitle(endsAt: string): string {
284
+ return formatMessage(msg.value.versions.endsAtTitle, { date: props.formatDate(endsAt) });
285
+ }
286
+
287
+ function endsAtBadge(endsAt: string): string {
288
+ return formatMessage(msg.value.versions.endsAtBadge, { date: props.formatDate(endsAt) });
289
+ }
255
290
  </script>
@@ -3,11 +3,8 @@
3
3
  <!-- Page header -->
4
4
  <div class="pl-page-head">
5
5
  <div>
6
- <h2 class="pl-h-title">Alle Pläne</h2>
7
- <p class="pl-h-sub">
8
- {{ resolvedPlans.length }} Pläne · {{ liveCount }} live ·
9
- {{ draftCount }} offene Drafts · {{ totalTenants }} Mandanten zugeordnet
10
- </p>
6
+ <h2 class="pl-h-title">{{ msg.list.title }}</h2>
7
+ <p class="pl-h-sub">{{ summary }}</p>
11
8
  </div>
12
9
  <div class="pl-head-actions">
13
10
  <button class="pl-btn" type="button" @click="$emit('showMatrix')">
@@ -24,7 +21,7 @@
24
21
  <rect x="3" y="14" width="7" height="7" rx="1" />
25
22
  <rect x="14" y="14" width="7" height="7" rx="1" />
26
23
  </svg>
27
- <span>Matrix-Ansicht</span>
24
+ <span>{{ msg.list.matrixView }}</span>
28
25
  </button>
29
26
  <button class="pl-btn pl-btn--primary" type="button" @click="$emit('createPlan')">
30
27
  <svg
@@ -37,7 +34,7 @@
37
34
  >
38
35
  <path d="M12 5v14M5 12h14" />
39
36
  </svg>
40
- <span>Neuer Plan</span>
37
+ <span>{{ msg.list.newPlan }}</span>
41
38
  </button>
42
39
  </div>
43
40
  </div>
@@ -59,28 +56,28 @@
59
56
  <path d="M21 21l-4.35-4.35" />
60
57
  </svg>
61
58
  </span>
62
- <input v-model="search" placeholder="Plan suchen…" />
59
+ <input v-model="search" :placeholder="msg.list.searchPlaceholder" />
63
60
  <span class="pl-kbd">⌘ K</span>
64
61
  </div>
65
62
  <div class="pl-toolbar-spacer" />
66
- <div class="pl-sortinfo">Sortiert nach: Order</div>
63
+ <div class="pl-sortinfo">{{ msg.list.sortedBy }}</div>
67
64
  </div>
68
65
 
69
66
  <div class="pl-list">
70
67
  <div class="pl-list-head">
71
- <div>Plan</div>
72
- <div>Status</div>
73
- <div>Version</div>
74
- <div>Preis</div>
75
- <div>Mandanten</div>
68
+ <div>{{ msg.list.columnPlan }}</div>
69
+ <div>{{ common.status }}</div>
70
+ <div>{{ msg.list.columnVersion }}</div>
71
+ <div>{{ msg.list.columnPrice }}</div>
72
+ <div>{{ msg.list.columnTenants }}</div>
76
73
  <div />
77
74
  </div>
78
75
 
79
76
  <div v-if="filteredPlans.length === 0" class="pl-empty">
80
77
  <template v-if="resolvedPlans.length === 0">
81
- Noch keine Pläne — über „Neuer Plan" oben rechts den ersten anlegen.
78
+ {{ msg.list.emptyNoPlans }}
82
79
  </template>
83
- <template v-else> Keine Pläne entsprechen der Suche „{{ search }}". </template>
80
+ <template v-else>{{ emptyNoMatch }}</template>
84
81
  </div>
85
82
 
86
83
  <template v-for="p in filteredPlans" :key="p.plan.id">
@@ -107,7 +104,7 @@
107
104
  <span
108
105
  v-if="highlightPlanKey === p.planKey"
109
106
  class="pl-chip pl-chip--new"
110
- >NEU</span
107
+ >{{ msg.list.badgeNew }}</span
111
108
  >
112
109
  </div>
113
110
  <div class="pl-plan-desc">{{ p.description || '—' }}</div>
@@ -117,22 +114,24 @@
117
114
 
118
115
  <div class="pl-cell pl-cell--status">
119
116
  <template v-if="p.currentLive">
120
- <span class="pl-chip pl-chip--live pl-chip--dot">live</span>
117
+ <span class="pl-chip pl-chip--live pl-chip--dot">{{
118
+ msg.list.chipLive
119
+ }}</span>
121
120
  <span
122
121
  v-if="!p.currentLive.marketed"
123
122
  class="pl-chip pl-chip--supersed pl-chip--tiny"
124
- >privat</span
123
+ >{{ msg.list.chipPrivate }}</span
125
124
  >
126
125
  </template>
127
126
  <template v-else-if="p.primary && p.primary.publishedAt">
128
127
  <span class="pl-chip pl-chip--scheduled pl-chip--dot">
129
- ab {{ p.primary.validFrom?.slice(0, 10) }}
128
+ {{ validFromLabel(p.primary.validFrom) }}
130
129
  </span>
131
130
  </template>
132
131
  <template v-else>
133
- <span class="pl-chip pl-chip--supersed pl-chip--dot"
134
- >keine live</span
135
- >
132
+ <span class="pl-chip pl-chip--supersed pl-chip--dot">{{
133
+ msg.list.chipNoLive
134
+ }}</span>
136
135
  </template>
137
136
  </div>
138
137
 
@@ -142,11 +141,13 @@
142
141
  </div>
143
142
  <div v-else class="pl-version-num pl-version-num--muted">—</div>
144
143
  <div v-if="p.primary?.validFrom" class="pl-version-sub">
145
- {{ p.currentLive ? 'seit' : 'ab' }}
146
- {{ p.primary.validFrom.slice(0, 10)
147
- }}<template v-if="p.primary.validUntil">
148
- bis {{ p.primary.validUntil.slice(0, 10) }}</template
149
- >
144
+ {{
145
+ validityRange(
146
+ p.currentLive ? msg.list.since : msg.list.from,
147
+ p.primary.validFrom,
148
+ p.primary.validUntil,
149
+ )
150
+ }}
150
151
  </div>
151
152
  </div>
152
153
 
@@ -160,17 +161,20 @@
160
161
  Number(p.primary.yearlyNet) === 0
161
162
  "
162
163
  >
163
- <span class="pl-price-text">Kostenlos</span>
164
+ <span class="pl-price-text">{{ msg.list.priceFree }}</span>
164
165
  </template>
165
166
  <template v-else>
166
167
  <div>
167
168
  <span class="pl-price-big">{{
168
169
  formatMoney(p.primary.monthlyNet)
169
170
  }}</span>
170
- <span class="pl-price-unit"> / Mo</span>
171
+ <span class="pl-price-unit">{{
172
+ ' ' + msg.list.perMonthShort
173
+ }}</span>
171
174
  </div>
172
175
  <div class="pl-price-sub">
173
- {{ formatMoney(p.primary.yearlyNet) }} / Jahr
176
+ {{ formatMoney(p.primary.yearlyNet) }}
177
+ {{ msg.list.perYearShort }}
174
178
  </div>
175
179
  </template>
176
180
  </div>
@@ -194,7 +198,7 @@
194
198
  class="pl-btn pl-btn--sm pl-btn--ghost"
195
199
  type="button"
196
200
  disabled
197
- title="Plan hat published Versionen — kann nicht gelöscht werden (Vertragsschutz P1)"
201
+ :title="msg.list.actionDeleteBlocked"
198
202
  >
199
203
  <svg
200
204
  width="14"
@@ -213,7 +217,7 @@
213
217
  v-else
214
218
  class="pl-btn pl-btn--sm pl-btn--ghost pl-btn--danger"
215
219
  type="button"
216
- title="Plan komplett löschen"
220
+ :title="msg.list.actionDeletePlan"
217
221
  @click="$emit('archivePlan', p.plan, false)"
218
222
  >
219
223
  <svg
@@ -232,7 +236,7 @@
232
236
  <button
233
237
  class="pl-btn pl-btn--sm pl-btn--ghost"
234
238
  type="button"
235
- title="Plan klonen"
239
+ :title="msg.list.actionClonePlan"
236
240
  @click="$emit('clonePlan', p.plan)"
237
241
  >
238
242
  <svg
@@ -250,7 +254,7 @@
250
254
  <button
251
255
  class="pl-btn pl-btn--sm pl-btn--ghost"
252
256
  type="button"
253
- title="Neue Version anlegen"
257
+ :title="msg.list.actionNewVersion"
254
258
  :disabled="!!p.draft"
255
259
  @click="onNewVersion(p)"
256
260
  >
@@ -270,7 +274,7 @@
270
274
  <button
271
275
  class="pl-btn pl-btn--sm"
272
276
  type="button"
273
- title="Plan öffnen"
277
+ :title="msg.list.actionOpenPlan"
274
278
  @click="$emit('openPlan', p.plan)"
275
279
  >
276
280
  <svg
@@ -304,18 +308,18 @@
304
308
  <template v-if="sub.publishedAt === null">
305
309
  <span
306
310
  class="pl-chip pl-chip--draft pl-chip--dot pl-chip--tiny"
307
- >Draft</span
311
+ >{{ msg.list.chipDraft }}</span
308
312
  >
309
313
  </template>
310
314
  <template v-else>
311
315
  <span
312
316
  class="pl-chip pl-chip--scheduled pl-chip--dot pl-chip--tiny"
313
- >geplant</span
317
+ >{{ msg.list.chipScheduled }}</span
314
318
  >
315
319
  </template>
316
320
  </div>
317
321
  <div class="pl-sub-desc">
318
- {{ sub.changeNote || 'keine Change-Note' }}
322
+ {{ sub.changeNote || msg.list.noChangeNote }}
319
323
  </div>
320
324
  </div>
321
325
  </div>
@@ -324,22 +328,19 @@
324
328
  <span
325
329
  v-if="sub.publishedAt === null"
326
330
  class="pl-chip pl-chip--draft pl-chip--dot pl-chip--tiny"
327
- >Draft</span
331
+ >{{ msg.list.chipDraft }}</span
328
332
  >
329
333
  <span
330
334
  v-else
331
335
  class="pl-chip pl-chip--scheduled pl-chip--dot pl-chip--tiny"
332
- >ab {{ sub.validFrom?.slice(0, 10) }}</span
336
+ >{{ validFromLabel(sub.validFrom) }}</span
333
337
  >
334
338
  </div>
335
339
 
336
340
  <div class="pl-cell">
337
341
  <div class="pl-version-num pl-version-num--sub">v{{ sub.version }}</div>
338
342
  <div v-if="sub.validFrom" class="pl-version-sub">
339
- ab {{ sub.validFrom.slice(0, 10)
340
- }}<template v-if="sub.validUntil">
341
- bis {{ sub.validUntil.slice(0, 10) }}</template
342
- >
343
+ {{ validityRange(msg.list.from, sub.validFrom, sub.validUntil) }}
343
344
  </div>
344
345
  </div>
345
346
 
@@ -347,23 +348,27 @@
347
348
  <template
348
349
  v-if="Number(sub.monthlyNet) === 0 && Number(sub.yearlyNet) === 0"
349
350
  >
350
- <span class="pl-price-text">Kostenlos</span>
351
+ <span class="pl-price-text">{{ msg.list.priceFree }}</span>
351
352
  </template>
352
353
  <template v-else>
353
354
  <div>
354
355
  <span class="pl-price-big">{{
355
356
  formatMoney(sub.monthlyNet)
356
357
  }}</span>
357
- <span class="pl-price-unit"> / Mo</span>
358
+ <span class="pl-price-unit">{{
359
+ ' ' + msg.list.perMonthShort
360
+ }}</span>
358
361
  </div>
359
362
  <div class="pl-price-sub">
360
- {{ formatMoney(sub.yearlyNet) }} / Jahr
363
+ {{ formatMoney(sub.yearlyNet) }} {{ msg.list.perYearShort }}
361
364
  </div>
362
365
  </template>
363
366
  </div>
364
367
 
365
368
  <div class="pl-cell pl-cell--sub-impact">
366
- <span class="pl-version-sub">{{ p.tenantCount }} Mandanten</span>
369
+ <span class="pl-version-sub">{{
370
+ tenantCountLabel(p.tenantCount)
371
+ }}</span>
367
372
  </div>
368
373
 
369
374
  <div class="pl-cell pl-cell--actions" @click.stop>
@@ -371,7 +376,7 @@
371
376
  v-if="sub.publishedAt === null"
372
377
  class="pl-btn pl-btn--sm pl-btn--ghost pl-btn--danger"
373
378
  type="button"
374
- :title="`Draft v${sub.version} verwerfen`"
379
+ :title="discardDraftTitle(sub.version)"
375
380
  @click="$emit('discardDraft', p.plan, sub)"
376
381
  >
377
382
  <svg
@@ -391,7 +396,7 @@
391
396
  v-if="sub.publishedAt === null"
392
397
  class="pl-btn pl-btn--sm pl-btn--ghost"
393
398
  type="button"
394
- title="Draft bearbeiten"
399
+ :title="msg.list.actionEditDraft"
395
400
  @click="$emit('editDraft', p.plan, sub)"
396
401
  >
397
402
  <svg
@@ -410,7 +415,7 @@
410
415
  <button
411
416
  class="pl-btn pl-btn--sm"
412
417
  type="button"
413
- title="Im Cockpit öffnen"
418
+ :title="msg.list.actionOpenInCockpit"
414
419
  @click="$emit('openPlan', p.plan)"
415
420
  >
416
421
  <svg
@@ -435,6 +440,9 @@
435
440
  <script setup lang="ts">
436
441
  import { computed, ref } from 'vue';
437
442
  import type { PlanRow, PlanVersionRow } from '@saasicat/types';
443
+ import { formatMessage } from '../../client/i18n/format.js';
444
+ import { formatCurrency } from '../../client/i18n/currency.js';
445
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
438
446
 
439
447
  // PlanList — list view of all plans (default view in PlansPage,
440
448
  // corresponds to the ListScreen from the plan simulation). One row per
@@ -468,6 +476,10 @@ const emit = defineEmits<{
468
476
  (e: 'showMatrix'): void;
469
477
  }>();
470
478
 
479
+ const msg = useSaMessages('plans');
480
+ const { locale, intlLocale } = useSuperAdminI18n();
481
+ const common = useSaMessages('common');
482
+
471
483
  const search = ref('');
472
484
 
473
485
  interface ResolvedPlan {
@@ -594,12 +606,12 @@ const filteredPlans = computed(() => {
594
606
  // (SPEC_V2 §4.2.1: only currently-valid + future ones stay visible
595
607
  // in the admin listing).
596
608
  const base = resolvedPlans.value.filter((p) => !p.allExpired);
597
- const q = search.value.trim().toLocaleLowerCase('de-DE');
609
+ const q = search.value.trim().toLocaleLowerCase(intlLocale.value);
598
610
  if (!q) return base;
599
611
  return base.filter(
600
612
  (p) =>
601
- p.planKey.toLocaleLowerCase('de-DE').includes(q) ||
602
- p.label.toLocaleLowerCase('de-DE').includes(q),
613
+ p.planKey.toLocaleLowerCase(intlLocale.value).includes(q) ||
614
+ p.label.toLocaleLowerCase(intlLocale.value).includes(q),
603
615
  );
604
616
  });
605
617
 
@@ -607,11 +619,41 @@ const liveCount = computed(() => resolvedPlans.value.filter((p) => p.currentLive
607
619
  const draftCount = computed(() => resolvedPlans.value.filter((p) => p.draft !== null).length);
608
620
  const totalTenants = computed(() => resolvedPlans.value.reduce((sum, p) => sum + p.tenantCount, 0));
609
621
 
622
+ const summary = computed(() =>
623
+ formatMessage(msg.value.list.summary, {
624
+ plans: resolvedPlans.value.length,
625
+ live: liveCount.value,
626
+ drafts: draftCount.value,
627
+ tenants: totalTenants.value,
628
+ }),
629
+ );
630
+
631
+ const emptyNoMatch = computed(() =>
632
+ formatMessage(msg.value.list.emptyNoMatch, { query: search.value }),
633
+ );
634
+
635
+ function validFromLabel(validFrom: string | null | undefined): string {
636
+ return formatMessage(msg.value.list.validFrom, { date: validFrom?.slice(0, 10) ?? '' });
637
+ }
638
+
639
+ function validityRange(prefix: string, validFrom: string, validUntil: string | null): string {
640
+ const range = `${prefix} ${validFrom.slice(0, 10)}`;
641
+ if (!validUntil) return range;
642
+ return `${range} ${msg.value.list.until} ${validUntil.slice(0, 10)}`;
643
+ }
644
+
645
+ function tenantCountLabel(count: number): string {
646
+ return formatMessage(msg.value.list.tenantCount, { count });
647
+ }
648
+
649
+ function discardDraftTitle(version: number): string {
650
+ return formatMessage(msg.value.list.actionDiscardDraft, { version });
651
+ }
652
+
610
653
  function formatMoney(raw: string | number): string {
611
654
  const num = typeof raw === 'string' ? Number(raw) : raw;
612
655
  if (!Number.isFinite(num)) return String(raw);
613
- if (Number.isInteger(num)) return `${num} €`;
614
- return `${num.toFixed(2).replace('.', ',')} €`;
656
+ return formatCurrency(num, locale.value);
615
657
  }
616
658
 
617
659
  function tenantBarWidth(count: number): string {