@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
@@ -7,13 +7,8 @@
7
7
  <q-card class="pl-dlg">
8
8
  <q-card-section class="pl-dlg__head">
9
9
  <div>
10
- <div class="pl-dlg__title">Pilot-Mandant anlegen</div>
11
- <div class="pl-dlg__sub">
12
- {{
13
- subtitle ??
14
- 'Neuer Mandant + Initial-Admin werden in einem Schritt angelegt.'
15
- }}
16
- </div>
10
+ <div class="pl-dlg__title">{{ msg.createDialog.title }}</div>
11
+ <div class="pl-dlg__sub">{{ subtitle ?? msg.createDialog.subtitle }}</div>
17
12
  </div>
18
13
  <q-btn
19
14
  class="pl-dlg__close"
@@ -32,7 +27,9 @@
32
27
  <header class="pl-section__head">
33
28
  <span class="pl-section__num">1</span>
34
29
  <div>
35
- <div class="pl-section__title">Mandant</div>
30
+ <div class="pl-section__title">
31
+ {{ msg.createDialog.sectionTenant }}
32
+ </div>
36
33
  <div class="pl-section__sub">{{ copy.tenantSubtitle }}</div>
37
34
  </div>
38
35
  </header>
@@ -49,10 +46,8 @@
49
46
 
50
47
  <div class="pl-field pl-field--full">
51
48
  <label>
52
- Slug
53
- <span class="pl-field__hint"
54
- >wird automatisch aus dem Namen erzeugt</span
55
- >
49
+ {{ msg.form.slugLabel }}
50
+ <span class="pl-field__hint">{{ msg.form.slugHint }}</span>
56
51
  </label>
57
52
  <div class="pl-slug-input">
58
53
  <span v-if="slugPrefix" class="pl-slug-input__prefix">{{
@@ -66,27 +61,27 @@
66
61
  />
67
62
  </div>
68
63
  <div v-if="slugConflict" class="pl-field__error">
69
- Dieser Slug ist bereits vergeben.
64
+ {{ msg.form.slugConflict }}
70
65
  </div>
71
66
  </div>
72
67
 
73
68
  <div v-if="showLegalFields" class="pl-field">
74
- <label>Rechtsform</label>
69
+ <label>{{ msg.createDialog.legalFormLabel }}</label>
75
70
  <input
76
71
  v-model="form.tenant.legalForm"
77
72
  class="pl-input"
78
- placeholder="e.V., GmbH, …"
73
+ :placeholder="msg.createDialog.legalFormPlaceholder"
79
74
  />
80
75
  </div>
81
76
  <div v-if="showLegalFields" class="pl-field">
82
77
  <label>
83
- USt-IdNr.
84
- <span class="pl-field__hint">optional</span>
78
+ {{ msg.createDialog.vatIdLabel }}
79
+ <span class="pl-field__hint">{{ msg.createDialog.vatIdHint }}</span>
85
80
  </label>
86
81
  <input
87
82
  v-model="form.tenant.vatId"
88
83
  class="pl-input"
89
- placeholder="DE123456789"
84
+ :placeholder="msg.createDialog.vatIdPlaceholder"
90
85
  />
91
86
  </div>
92
87
  </div>
@@ -98,15 +93,15 @@
98
93
  <header class="pl-section__head">
99
94
  <span class="pl-section__num">2</span>
100
95
  <div>
101
- <div class="pl-section__title">Initial-Admin</div>
96
+ <div class="pl-section__title">{{ msg.createDialog.sectionAdmin }}</div>
102
97
  <div class="pl-section__sub">
103
- Erhält eine Einladungs-E-Mail mit Initial-Passwort.
98
+ {{ msg.createDialog.sectionAdminSub }}
104
99
  </div>
105
100
  </div>
106
101
  </header>
107
102
  <div class="pl-grid">
108
103
  <div class="pl-field pl-field--full">
109
- <label>E-Mail</label>
104
+ <label>{{ msg.createDialog.emailLabel }}</label>
110
105
  <input
111
106
  v-model="form.admin.email"
112
107
  class="pl-input"
@@ -115,36 +110,36 @@
115
110
  :placeholder="copy.adminEmailPlaceholder"
116
111
  />
117
112
  <div v-if="form.admin.email && !emailValid" class="pl-field__error">
118
- Bitte eine gültige E-Mail-Adresse eingeben.
113
+ {{ msg.createDialog.emailInvalid }}
119
114
  </div>
120
115
  </div>
121
116
  <div class="pl-field">
122
- <label>Vorname</label>
117
+ <label>{{ msg.createDialog.firstNameLabel }}</label>
123
118
  <input
124
119
  v-model="form.admin.firstName"
125
120
  class="pl-input"
126
- placeholder="Erika"
121
+ :placeholder="msg.createDialog.firstNamePlaceholder"
127
122
  />
128
123
  </div>
129
124
  <div class="pl-field">
130
- <label>Nachname</label>
125
+ <label>{{ msg.createDialog.lastNameLabel }}</label>
131
126
  <input
132
127
  v-model="form.admin.lastName"
133
128
  class="pl-input"
134
- placeholder="Mustermann"
129
+ :placeholder="msg.createDialog.lastNamePlaceholder"
135
130
  />
136
131
  </div>
137
132
  <div class="pl-field pl-field--full">
138
133
  <label>
139
- Initial-Passwort
140
- <span class="pl-field__hint"
141
- >leer lassen → automatisch generieren</span
142
- >
134
+ {{ msg.createDialog.initialPasswordLabel }}
135
+ <span class="pl-field__hint">{{
136
+ msg.createDialog.initialPasswordHint
137
+ }}</span>
143
138
  </label>
144
139
  <input
145
140
  v-model="form.admin.initialPassword"
146
141
  class="pl-input"
147
- placeholder="•••••••• (automatisch generieren)"
142
+ :placeholder="msg.createDialog.initialPasswordPlaceholder"
148
143
  />
149
144
  </div>
150
145
  </div>
@@ -155,15 +150,15 @@
155
150
  <header class="pl-section__head">
156
151
  <span class="pl-section__num">3</span>
157
152
  <div>
158
- <div class="pl-section__title">Pilot-Konfiguration</div>
153
+ <div class="pl-section__title">{{ msg.createDialog.sectionPilot }}</div>
159
154
  <div class="pl-section__sub">
160
- Plan-Zuweisung, Laufzeit und interne Notiz
155
+ {{ msg.createDialog.sectionPilotSub }}
161
156
  </div>
162
157
  </div>
163
158
  </header>
164
159
  <div class="pl-grid">
165
160
  <div class="pl-field">
166
- <label>Plan</label>
161
+ <label>{{ msg.form.planLabel }}</label>
167
162
  <div class="pl-plan-select">
168
163
  <button
169
164
  v-for="p in normalizedPlanOptions"
@@ -192,23 +187,23 @@
192
187
  </div>
193
188
 
194
189
  <div class="pl-field">
195
- <label>Endet am</label>
190
+ <label>{{ msg.form.endsAtLabel }}</label>
196
191
  <div class="pl-end-row">
197
192
  <input v-model="form.pilot.endsAt" class="pl-input" type="date" />
198
193
  <button
199
194
  v-if="form.pilot.endsAt"
200
195
  type="button"
201
196
  class="pl-btn-mini"
202
- title="Auf unbegrenzt setzen"
197
+ :title="msg.form.endsAtClearTitle"
203
198
  @click="form.pilot.endsAt = ''"
204
199
  >
205
200
  <q-icon name="close" size="12px" />
206
- unbegrenzt
201
+ {{ common.unlimited }}
207
202
  </button>
208
203
  </div>
209
204
  <div class="pl-end-presets">
210
205
  <button
211
- v-for="p in PRESET_ENDS"
206
+ v-for="p in presetEnds"
212
207
  :key="p.days"
213
208
  type="button"
214
209
  class="pl-preset-btn"
@@ -221,10 +216,8 @@
221
216
 
222
217
  <div class="pl-field pl-field--full">
223
218
  <label>
224
- Note
225
- <span class="pl-field__hint"
226
- >intern · nicht für Kunden sichtbar</span
227
- >
219
+ {{ msg.form.noteLabel }}
220
+ <span class="pl-field__hint">{{ msg.form.noteHint }}</span>
228
221
  </label>
229
222
  <textarea
230
223
  v-model="form.pilot.note"
@@ -242,13 +235,13 @@
242
235
  <q-card-actions align="right" class="pl-dlg__foot">
243
236
  <span v-if="form.admin.email && emailValid" class="pl-foot-hint">
244
237
  <q-icon name="send" size="14px" />
245
- Einladung geht an <strong>{{ form.admin.email }}</strong>
238
+ {{ msg.createDialog.invitationHint }} <strong>{{ form.admin.email }}</strong>
246
239
  </span>
247
- <q-btn flat label="Abbrechen" v-close-popup :disable="loading" />
240
+ <q-btn flat :label="common.cancel" v-close-popup :disable="loading" />
248
241
  <q-btn
249
242
  unelevated
250
243
  color="primary"
251
- label="Pilot anlegen"
244
+ :label="msg.createAction"
252
245
  :loading="loading"
253
246
  :disable="!isValid"
254
247
  @click="onSubmit"
@@ -270,12 +263,9 @@
270
263
  <script setup lang="ts">
271
264
  import { computed, reactive, ref, watch } from 'vue';
272
265
  import MfaPromptDialog from '../MfaPromptDialog.vue';
273
- import {
274
- DEFAULT_PILOT_COPY,
275
- type PilotCopy,
276
- type PilotCreatePayload,
277
- type PilotCreateResult,
278
- } from './types.js';
266
+ import { formatMessage } from '../../client/i18n/format.js';
267
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
268
+ import type { PilotCopy, PilotCreatePayload, PilotCreateResult } from './types.js';
279
269
 
280
270
  // Platform pilot-create dialog (sim layout: 3 numbered sections,
281
271
  // slug-prefix display, plan-tile picker, ends-at quick-sets, footer hint).
@@ -315,14 +305,17 @@ const emit = defineEmits<{
315
305
  (e: 'created', result: PilotCreateResult): void;
316
306
  }>();
317
307
 
318
- const PRESET_ENDS: ReadonlyArray<{ label: string; days: number }> = [
319
- { label: '30 Tage', days: 30 },
320
- { label: '90 Tage', days: 90 },
321
- { label: '6 Monate', days: 180 },
322
- { label: '1 Jahr', days: 365 },
323
- ];
308
+ const msg = useSaMessages('pilots');
309
+ const common = useSaMessages('common');
310
+
311
+ const presetEnds = computed<ReadonlyArray<{ label: string; days: number }>>(() => [
312
+ { label: msg.value.form.preset30Days, days: 30 },
313
+ { label: msg.value.form.preset90Days, days: 90 },
314
+ { label: msg.value.form.preset6Months, days: 180 },
315
+ { label: msg.value.form.preset1Year, days: 365 },
316
+ ]);
324
317
 
325
- const copy = computed<Required<PilotCopy>>(() => ({ ...DEFAULT_PILOT_COPY, ...props.copy }));
318
+ const copy = computed<Required<PilotCopy>>(() => ({ ...msg.value.copyDefaults, ...props.copy }));
326
319
 
327
320
  const normalizedPlanOptions = computed<Array<{ value: string; label: string; color?: string }>>(
328
321
  () =>
@@ -419,7 +412,10 @@ watch(
419
412
  function onSubmit(): void {
420
413
  if (!isValid.value) return;
421
414
  if (props.requireMfa) {
422
- mfaDescription.value = `Pilot-Mandant "${form.tenant.name}" anlegen — Plan ${form.pilot.plan}.`;
415
+ mfaDescription.value = formatMessage(msg.value.createDialog.mfaDescription, {
416
+ name: form.tenant.name,
417
+ plan: form.pilot.plan,
418
+ });
423
419
  mfaError.value = '';
424
420
  showMfa.value = true;
425
421
  return;
@@ -472,12 +468,11 @@ async function doSubmit(code: string): Promise<void> {
472
468
  status === 401 &&
473
469
  (reason === 'MFA_FAILED' || reason === 'MFA_REQUIRED')
474
470
  ) {
475
- mfaError.value = 'TOTP-Code ungültig.';
471
+ mfaError.value = msg.value.mfa.invalidCode;
476
472
  } else if (props.requireMfa && status === 401 && reason === 'MFA_NOT_SET_UP') {
477
- mfaError.value =
478
- response?.data?.message ?? 'MFA ist für diesen Account nicht eingerichtet.';
473
+ mfaError.value = response?.data?.message ?? msg.value.mfa.notSetUp;
479
474
  } else {
480
- error.value = response?.data?.message ?? 'Anlegen fehlgeschlagen';
475
+ error.value = response?.data?.message ?? common.value.createFailed;
481
476
  showMfa.value = false;
482
477
  }
483
478
  } finally {
@@ -7,7 +7,7 @@
7
7
  <q-card class="ple-dlg">
8
8
  <q-card-section class="ple-dlg__head">
9
9
  <div>
10
- <div class="ple-dlg__title">Pilot bearbeiten</div>
10
+ <div class="ple-dlg__title">{{ msg.editDialog.title }}</div>
11
11
  <div class="ple-dlg__sub" v-if="row">
12
12
  <strong>{{ row.tenant.name }}</strong>
13
13
  <span class="ple-dlg__sep">·</span>
@@ -31,11 +31,8 @@
31
31
  <header class="ple-section__head">
32
32
  <span class="ple-section__num">1</span>
33
33
  <div>
34
- <div class="ple-section__title">Plan</div>
35
- <div class="ple-section__sub">
36
- Aktive Plan-Zuweisung. Legacy-Plans tauchen als „nicht im Katalog"
37
- auf.
38
- </div>
34
+ <div class="ple-section__title">{{ msg.form.planLabel }}</div>
35
+ <div class="ple-section__sub">{{ msg.editDialog.sectionPlanSub }}</div>
39
36
  </div>
40
37
  </header>
41
38
  <div class="ple-plan-select">
@@ -70,9 +67,11 @@
70
67
  <header class="ple-section__head">
71
68
  <span class="ple-section__num">2</span>
72
69
  <div>
73
- <div class="ple-section__title">Enddatum</div>
70
+ <div class="ple-section__title">
71
+ {{ msg.editDialog.sectionEndDate }}
72
+ </div>
74
73
  <div class="ple-section__sub">
75
- Leeres Datum = offene Pilotphase ohne Ablauf.
74
+ {{ msg.editDialog.sectionEndDateSub }}
76
75
  </div>
77
76
  </div>
78
77
  </header>
@@ -82,16 +81,16 @@
82
81
  v-if="form.endsAt"
83
82
  type="button"
84
83
  class="ple-btn-mini"
85
- title="Datum löschen — offene Pilotphase"
84
+ :title="msg.editDialog.clearDateTitle"
86
85
  @click="form.endsAt = ''"
87
86
  >
88
87
  <q-icon name="close" size="12px" />
89
- unbegrenzt
88
+ {{ common.unlimited }}
90
89
  </button>
91
90
  </div>
92
91
  <div class="ple-end-presets">
93
92
  <button
94
- v-for="p in PRESET_ENDS"
93
+ v-for="p in presetEnds"
95
94
  :key="p.days"
96
95
  type="button"
97
96
  class="ple-preset-btn"
@@ -107,8 +106,8 @@
107
106
  <header class="ple-section__head">
108
107
  <span class="ple-section__num">3</span>
109
108
  <div>
110
- <div class="ple-section__title">Notiz</div>
111
- <div class="ple-section__sub">Intern · nicht für Kunden sichtbar.</div>
109
+ <div class="ple-section__title">{{ msg.editDialog.sectionNote }}</div>
110
+ <div class="ple-section__sub">{{ msg.editDialog.sectionNoteSub }}</div>
112
111
  </div>
113
112
  </header>
114
113
  <textarea
@@ -123,11 +122,11 @@
123
122
  </q-card-section>
124
123
 
125
124
  <q-card-actions align="right" class="ple-dlg__foot">
126
- <q-btn flat label="Abbrechen" v-close-popup :disable="loading" />
125
+ <q-btn flat :label="common.cancel" v-close-popup :disable="loading" />
127
126
  <q-btn
128
127
  unelevated
129
128
  color="primary"
130
- label="Speichern"
129
+ :label="common.save"
131
130
  :loading="loading"
132
131
  :disable="!isDirty"
133
132
  @click="onSubmit"
@@ -149,13 +148,10 @@
149
148
  <script setup lang="ts">
150
149
  import { computed, reactive, ref, watch } from 'vue';
151
150
  import MfaPromptDialog from '../MfaPromptDialog.vue';
151
+ import { formatMessage } from '../../client/i18n/format.js';
152
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
152
153
  import type { PilotRow } from '../../pages-standard/PilotsPage.vue';
153
- import {
154
- DEFAULT_PILOT_COPY,
155
- type PilotCopy,
156
- type PilotEditPayload,
157
- type PilotEditResult,
158
- } from './types.js';
154
+ import type { PilotCopy, PilotEditPayload, PilotEditResult } from './types.js';
159
155
 
160
156
  // Platform dialog for editing an existing pilot subscription
161
157
  // (sim layout: numbered sections, plan tile picker, ends-at quick sets).
@@ -187,15 +183,18 @@ const emit = defineEmits<{
187
183
  (e: 'updated', result: PilotEditResult): void;
188
184
  }>();
189
185
 
190
- const PRESET_ENDS: ReadonlyArray<{ label: string; days: number }> = [
191
- { label: '30 Tage', days: 30 },
192
- { label: '90 Tage', days: 90 },
193
- { label: '6 Monate', days: 180 },
194
- { label: '1 Jahr', days: 365 },
195
- ];
186
+ const msg = useSaMessages('pilots');
187
+ const common = useSaMessages('common');
188
+
189
+ const presetEnds = computed<ReadonlyArray<{ label: string; days: number }>>(() => [
190
+ { label: msg.value.form.preset30Days, days: 30 },
191
+ { label: msg.value.form.preset90Days, days: 90 },
192
+ { label: msg.value.form.preset6Months, days: 180 },
193
+ { label: msg.value.form.preset1Year, days: 365 },
194
+ ]);
196
195
 
197
196
  const notePlaceholder = computed(
198
- () => props.copy?.notePlaceholder ?? DEFAULT_PILOT_COPY.notePlaceholder,
197
+ () => props.copy?.notePlaceholder ?? msg.value.copyDefaults.notePlaceholder,
199
198
  );
200
199
 
201
200
  function normalize(opt: PlanOption): { value: string; label: string; color?: string } {
@@ -212,7 +211,13 @@ const effectivePlanOptions = computed<Array<{ value: string; label: string; colo
212
211
  const current = props.row?.plan;
213
212
  if (!current) return opts;
214
213
  if (opts.some((o) => o.value === current)) return opts;
215
- return [{ value: current, label: `${current} (nicht im Katalog)` }, ...opts];
214
+ return [
215
+ {
216
+ value: current,
217
+ label: formatMessage(msg.value.editDialog.planNotInCatalog, { plan: current }),
218
+ },
219
+ ...opts,
220
+ ];
216
221
  },
217
222
  );
218
223
 
@@ -276,7 +281,9 @@ const isDirty = computed(() => Object.keys(diff.value).length > 0);
276
281
  function onSubmit(): void {
277
282
  if (!isDirty.value || !props.row) return;
278
283
  if (props.requireMfa) {
279
- mfaDescription.value = `Pilot "${props.row.tenant.name}" bearbeiten.`;
284
+ mfaDescription.value = formatMessage(msg.value.editDialog.mfaDescription, {
285
+ name: props.row.tenant.name,
286
+ });
280
287
  mfaError.value = '';
281
288
  showMfa.value = true;
282
289
  return;
@@ -309,12 +316,11 @@ async function doSubmit(code: string): Promise<void> {
309
316
  status === 401 &&
310
317
  (reason === 'MFA_FAILED' || reason === 'MFA_REQUIRED')
311
318
  ) {
312
- mfaError.value = 'TOTP-Code ungültig.';
319
+ mfaError.value = msg.value.mfa.invalidCode;
313
320
  } else if (props.requireMfa && status === 401 && reason === 'MFA_NOT_SET_UP') {
314
- mfaError.value =
315
- response?.data?.message ?? 'MFA ist für diesen Account nicht eingerichtet.';
321
+ mfaError.value = response?.data?.message ?? msg.value.mfa.notSetUp;
316
322
  } else {
317
- error.value = response?.data?.message ?? 'Speichern fehlgeschlagen';
323
+ error.value = response?.data?.message ?? common.value.errorSaveFailed;
318
324
  showMfa.value = false;
319
325
  }
320
326
  } finally {