@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
@@ -7,10 +7,14 @@
7
7
  <q-card class="pc-dlg">
8
8
  <q-card-section class="pc-dlg__head">
9
9
  <div>
10
- <div class="pc-dlg__title">Promo-Code bearbeiten</div>
10
+ <div class="pc-dlg__title">{{ msg.editDialog.title }}</div>
11
11
  <div class="pc-dlg__sub" v-if="row">
12
- Code <strong>{{ row.code }}</strong> ·
13
- {{ row.redemptionsCount }} Einlösungen bisher
12
+ {{ msg.form.codeLabel }} <strong>{{ row.code }}</strong> ·
13
+ {{
14
+ formatMessage(msg.editDialog.redemptionsSoFar, {
15
+ count: row.redemptionsCount,
16
+ })
17
+ }}
14
18
  </div>
15
19
  </div>
16
20
  <q-btn
@@ -27,23 +31,23 @@
27
31
  <q-card-section class="pc-dlg__body">
28
32
  <!-- Section: Code & discount -->
29
33
  <div class="pc-section">
30
- <div class="pc-section__title">Code &amp; Rabatt</div>
34
+ <div class="pc-section__title">{{ msg.form.sectionCodeDiscount }}</div>
31
35
  <div class="pc-grid pc-grid--2">
32
36
  <div class="pc-field">
33
- <div class="pc-field__label">Code</div>
37
+ <div class="pc-field__label">{{ msg.form.codeLabel }}</div>
34
38
  <input
35
39
  :value="row?.code ?? ''"
36
40
  class="pc-input pc-input--code"
37
41
  disabled
38
42
  />
39
- <div class="pc-field__hint">Nach Anlage stabil.</div>
43
+ <div class="pc-field__hint">{{ msg.form.codeStableHint }}</div>
40
44
  </div>
41
45
 
42
46
  <div class="pc-field">
43
- <div class="pc-field__label">Rabatt-Typ</div>
47
+ <div class="pc-field__label">{{ msg.form.valueTypeLabel }}</div>
44
48
  <div class="pc-type-grid">
45
49
  <button
46
- v-for="o in TYPE_OPTIONS"
50
+ v-for="o in typeOptions"
47
51
  :key="o.k"
48
52
  type="button"
49
53
  class="pc-type-opt"
@@ -59,7 +63,11 @@
59
63
 
60
64
  <div class="pc-field" style="max-width: 280px">
61
65
  <div class="pc-field__label">
62
- {{ form.valueType === 'PERCENT' ? 'Rabatt in %' : 'Rabatt in €' }}
66
+ {{
67
+ form.valueType === 'PERCENT'
68
+ ? msg.form.valuePercentLabel
69
+ : msg.form.valueAbsoluteLabel
70
+ }}
63
71
  </div>
64
72
  <input
65
73
  v-model.number="form.value"
@@ -73,10 +81,10 @@
73
81
 
74
82
  <!-- Section: Validity & duration -->
75
83
  <div class="pc-section">
76
- <div class="pc-section__title">Gültigkeit &amp; Laufzeit</div>
84
+ <div class="pc-section__title">{{ msg.form.sectionValidity }}</div>
77
85
 
78
86
  <div v-if="plans.length > 0" class="pc-field">
79
- <div class="pc-field__label">Anwendbar auf Pläne</div>
87
+ <div class="pc-field__label">{{ msg.form.plansLabel }}</div>
80
88
  <div class="pc-plan-pick">
81
89
  <button
82
90
  v-for="p in plans"
@@ -95,16 +103,20 @@
95
103
  </button>
96
104
  </div>
97
105
  <div class="pc-field__hint">
98
- Leer = alle Pläne ({{ form.appliesToPlans.length }} ausgewählt)
106
+ {{
107
+ formatMessage(msg.form.plansHint, {
108
+ count: form.appliesToPlans.length,
109
+ })
110
+ }}
99
111
  </div>
100
112
  </div>
101
113
 
102
114
  <div class="pc-grid pc-grid--2">
103
115
  <div class="pc-field">
104
- <div class="pc-field__label">Laufzeit des Rabatts</div>
116
+ <div class="pc-field__label">{{ msg.form.durationLabel }}</div>
105
117
  <div class="pc-dur">
106
118
  <button
107
- v-for="o in DURATION_OPTIONS"
119
+ v-for="o in durationOptions"
108
120
  :key="o.k"
109
121
  type="button"
110
122
  class="pc-dur-opt"
@@ -122,43 +134,45 @@
122
134
  min="1"
123
135
  style="margin-top: 8px; max-width: 120px"
124
136
  :placeholder="
125
- form.durationType === 'MONTHS' ? 'Monate' : 'Perioden'
137
+ form.durationType === 'MONTHS'
138
+ ? msg.form.durationMonthsPlaceholder
139
+ : msg.form.durationCyclesPlaceholder
126
140
  "
127
141
  />
128
142
  </div>
129
143
 
130
144
  <div class="pc-field">
131
- <div class="pc-field__label">Max. Einlösungen</div>
145
+ <div class="pc-field__label">{{ msg.form.maxRedemptionsLabel }}</div>
132
146
  <input
133
147
  v-model.number="form.maxRedemptions"
134
148
  class="pc-input"
135
149
  type="number"
136
150
  min="1"
137
- placeholder="leer = ∞"
151
+ :placeholder="msg.form.maxRedemptionsPlaceholder"
138
152
  />
139
153
  <div class="pc-field__hint">
140
- Nur erhöhbar, nicht senkbar (bestehende Einlösungen bleiben gültig).
154
+ {{ msg.form.maxRedemptionsHintEdit }}
141
155
  </div>
142
156
  </div>
143
157
  </div>
144
158
 
145
159
  <div class="pc-grid pc-grid--2">
146
160
  <div class="pc-field">
147
- <div class="pc-field__label">Gültig ab</div>
161
+ <div class="pc-field__label">{{ msg.form.validFromLabel }}</div>
148
162
  <input v-model="form.validFrom" class="pc-input" type="date" />
149
163
  </div>
150
164
  <div class="pc-field">
151
- <div class="pc-field__label">Gültig bis</div>
165
+ <div class="pc-field__label">{{ msg.form.validUntilLabel }}</div>
152
166
  <input v-model="form.validUntil" class="pc-input" type="date" />
153
167
  </div>
154
168
  </div>
155
169
 
156
170
  <div class="pc-grid pc-grid--2">
157
171
  <div class="pc-field">
158
- <div class="pc-field__label">Status</div>
172
+ <div class="pc-field__label">{{ common.status }}</div>
159
173
  <div class="pc-status">
160
174
  <button
161
- v-for="o in STATUS_OPTIONS"
175
+ v-for="o in statusOptions"
162
176
  :key="o.k"
163
177
  type="button"
164
178
  class="pc-status-opt"
@@ -171,32 +185,30 @@
171
185
  {{ o.label }}
172
186
  </button>
173
187
  </div>
174
- <div class="pc-field__hint">
175
- Bestehende Redemptions bleiben unverändert.
176
- </div>
188
+ <div class="pc-field__hint">{{ msg.form.statusHint }}</div>
177
189
  </div>
178
190
  </div>
179
191
  </div>
180
192
 
181
193
  <!-- Section: Campaign & note -->
182
194
  <div class="pc-section">
183
- <div class="pc-section__title">Kampagne &amp; Notizen</div>
195
+ <div class="pc-section__title">{{ msg.form.sectionCampaign }}</div>
184
196
  <div v-if="showCampaignTag" class="pc-field">
185
- <div class="pc-field__label">Kampagne (optional)</div>
197
+ <div class="pc-field__label">{{ msg.form.campaignLabel }}</div>
186
198
  <input
187
199
  v-model="form.campaignTag"
188
200
  class="pc-input"
189
- placeholder="z. B. Frühjahrs-Aktion 2026"
201
+ :placeholder="msg.form.campaignPlaceholder"
190
202
  />
191
- <div class="pc-field__hint">Gruppiert mehrere Codes optisch</div>
203
+ <div class="pc-field__hint">{{ msg.form.campaignHint }}</div>
192
204
  </div>
193
205
  <div class="pc-field">
194
- <div class="pc-field__label">Notiz (intern)</div>
206
+ <div class="pc-field__label">{{ msg.form.noteLabel }}</div>
195
207
  <textarea
196
208
  v-model="form.description"
197
209
  class="pc-input"
198
210
  rows="2"
199
- placeholder="Kontext, Quelle, Sales-Bezug — wird nicht an Kunden ausgespielt."
211
+ :placeholder="msg.form.notePlaceholder"
200
212
  />
201
213
  </div>
202
214
  </div>
@@ -212,45 +224,45 @@
212
224
  :name="advancedOpen ? 'expand_more' : 'chevron_right'"
213
225
  size="16px"
214
226
  />
215
- Erweiterte Einschränkungen
227
+ {{ msg.form.advancedToggle }}
216
228
  </button>
217
229
  <div v-if="advancedOpen" class="pc-advanced">
218
230
  <div class="pc-grid pc-grid--2">
219
231
  <div class="pc-field">
220
- <div class="pc-field__label">Nur für Abrechnungs-Zyklus</div>
232
+ <div class="pc-field__label">{{ msg.form.billingCycleLabel }}</div>
221
233
  <select v-model="form.appliesToBilling" class="pc-input">
222
- <option :value="null">Beide</option>
223
- <option value="MONTHLY">Monatlich</option>
224
- <option value="YEARLY">Jährlich</option>
234
+ <option :value="null">{{ common.both }}</option>
235
+ <option value="MONTHLY">{{ common.monthly }}</option>
236
+ <option value="YEARLY">{{ common.yearly }}</option>
225
237
  </select>
226
238
  </div>
227
239
  <div class="pc-field">
228
- <div class="pc-field__label">Mindest-Plan-Betrag (€ brutto)</div>
240
+ <div class="pc-field__label">{{ msg.form.minAmountLabel }}</div>
229
241
  <input
230
242
  v-model.number="form.minimumPlanAmountGross"
231
243
  class="pc-input"
232
244
  type="number"
233
245
  min="0"
234
- placeholder="leer = keine Schwelle"
246
+ :placeholder="msg.form.minAmountPlaceholder"
235
247
  />
236
248
  </div>
237
249
  </div>
238
250
  <div class="pc-grid pc-grid--2">
239
251
  <label class="pc-check">
240
252
  <input v-model="form.firstTimeCustomersOnly" type="checkbox" />
241
- <span>Nur Neukunden</span>
253
+ <span>{{ msg.form.firstTimeOnly }}</span>
242
254
  </label>
243
255
  <label class="pc-check">
244
256
  <input v-model="form.allowZeroInvoice" type="checkbox" />
245
- <span>0-€-Rechnung zulassen</span>
257
+ <span>{{ msg.form.allowZeroInvoice }}</span>
246
258
  </label>
247
259
  </div>
248
260
  <div class="pc-field">
249
- <div class="pc-field__label">Erlös-Minderungs-Konto (Buchhaltung)</div>
261
+ <div class="pc-field__label">{{ msg.form.revenueAccountLabel }}</div>
250
262
  <input
251
263
  v-model="form.revenueDeductionAccount"
252
264
  class="pc-input"
253
- placeholder="z. B. 8736"
265
+ :placeholder="msg.form.revenueAccountPlaceholder"
254
266
  />
255
267
  </div>
256
268
  </div>
@@ -258,7 +270,7 @@
258
270
 
259
271
  <!-- Live preview -->
260
272
  <div class="pc-preview">
261
- <div class="pc-preview__eyebrow">Vorschau im Catalog</div>
273
+ <div class="pc-preview__eyebrow">{{ msg.form.previewEyebrow }}</div>
262
274
  <div class="pc-preview__body">
263
275
  <code class="pc-preview__code">{{ row?.code || 'CODE' }}</code>
264
276
  <span class="pc-preview__disc">{{ previewValue }}</span>
@@ -270,11 +282,11 @@
270
282
  </q-card-section>
271
283
 
272
284
  <q-card-actions align="right" class="pc-dlg__foot">
273
- <q-btn flat label="Abbrechen" v-close-popup :disable="loading" />
285
+ <q-btn flat :label="common.cancel" v-close-popup :disable="loading" />
274
286
  <q-btn
275
287
  unelevated
276
288
  color="primary"
277
- label="Speichern"
289
+ :label="common.save"
278
290
  :loading="loading"
279
291
  :disable="!isValid || !hasChanges"
280
292
  @click="onSubmit"
@@ -286,6 +298,8 @@
286
298
 
287
299
  <script setup lang="ts">
288
300
  import { computed, reactive, ref, watch } from 'vue';
301
+ import { formatMessage } from '../../client/i18n/format.js';
302
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
289
303
  import type {
290
304
  PromoCodeDurationType,
291
305
  PromoCodePlanOption,
@@ -341,21 +355,36 @@ const emit = defineEmits<{
341
355
  (e: 'updated'): void;
342
356
  }>();
343
357
 
344
- const STATUS_OPTIONS: ReadonlyArray<{ k: 'ACTIVE' | 'PAUSED'; label: string; icon: string }> = [
345
- { k: 'ACTIVE', label: 'Aktiv', icon: 'play_arrow' },
346
- { k: 'PAUSED', label: 'Pausiert', icon: 'pause' },
347
- ];
348
-
349
- const TYPE_OPTIONS: ReadonlyArray<{ k: PromoCodeValueType; label: string; sub: string }> = [
350
- { k: 'PERCENT', label: 'Prozent', sub: '−x %' },
351
- { k: 'ABSOLUTE', label: 'Fester €', sub: '−x €' },
352
- ];
358
+ const msg = useSaMessages('promos');
359
+ const common = useSaMessages('common');
360
+
361
+ const statusOptions = computed<
362
+ ReadonlyArray<{ k: 'ACTIVE' | 'PAUSED'; label: string; icon: string }>
363
+ >(() => [
364
+ { k: 'ACTIVE', label: common.value.active, icon: 'play_arrow' },
365
+ { k: 'PAUSED', label: msg.value.form.statusPaused, icon: 'pause' },
366
+ ]);
367
+
368
+ const typeOptions = computed<ReadonlyArray<{ k: PromoCodeValueType; label: string; sub: string }>>(
369
+ () => [
370
+ {
371
+ k: 'PERCENT',
372
+ label: msg.value.form.valueTypePercent,
373
+ sub: msg.value.form.valueTypePercentSub,
374
+ },
375
+ {
376
+ k: 'ABSOLUTE',
377
+ label: msg.value.form.valueTypeAbsolute,
378
+ sub: msg.value.form.valueTypeAbsoluteSub,
379
+ },
380
+ ],
381
+ );
353
382
 
354
- const DURATION_OPTIONS: ReadonlyArray<{ k: PromoCodeDurationType; label: string }> = [
355
- { k: 'ONCE', label: 'Einmalig' },
356
- { k: 'MONTHS', label: 'N Monate' },
357
- { k: 'BILLING_CYCLES', label: 'N Perioden' },
358
- ];
383
+ const durationOptions = computed<ReadonlyArray<{ k: PromoCodeDurationType; label: string }>>(() => [
384
+ { k: 'ONCE', label: msg.value.form.durationOnce },
385
+ { k: 'MONTHS', label: msg.value.form.durationMonths },
386
+ { k: 'BILLING_CYCLES', label: msg.value.form.durationBillingCycles },
387
+ ]);
359
388
 
360
389
  interface EditForm {
361
390
  status: 'ACTIVE' | 'PAUSED';
@@ -450,17 +479,20 @@ const previewValue = computed(() => {
450
479
 
451
480
  const previewMeta = computed(() => {
452
481
  const parts: string[] = [];
453
- if (form.durationType === 'ONCE') parts.push('einmalig');
454
- else if (form.durationType === 'MONTHS') parts.push(`${form.durationValue || 0} Monate`);
455
- else parts.push(`${form.durationValue || 0} Perioden`);
482
+ const count = form.durationValue || 0;
483
+ if (form.durationType === 'ONCE') parts.push(msg.value.form.previewOnce);
484
+ else if (form.durationType === 'MONTHS')
485
+ parts.push(formatMessage(msg.value.form.previewMonths, { count }));
486
+ else parts.push(formatMessage(msg.value.form.previewCycles, { count }));
456
487
  parts.push(
457
488
  form.appliesToPlans.length > 0
458
489
  ? form.appliesToPlans.join(', ')
459
490
  : props.plans.length > 0
460
- ? 'alle Pläne'
461
- : 'kein Plan-Filter',
491
+ ? msg.value.form.previewAllPlans
492
+ : msg.value.form.previewNoPlanFilter,
462
493
  );
463
- if (form.maxRedemptions) parts.push(`max. ${form.maxRedemptions}`);
494
+ if (form.maxRedemptions)
495
+ parts.push(formatMessage(msg.value.form.previewMax, { count: form.maxRedemptions }));
464
496
  return parts.join(' · ');
465
497
  });
466
498
 
@@ -554,7 +586,7 @@ async function onSubmit() {
554
586
  error.value =
555
587
  (err as { response?: { data?: { message?: string } } }).response?.data?.message ??
556
588
  (err as Error).message ??
557
- 'Speichern fehlgeschlagen';
589
+ common.value.errorSaveFailed;
558
590
  } finally {
559
591
  loading.value = false;
560
592
  }
@@ -34,9 +34,9 @@ export interface PilotEditResult {
34
34
  }
35
35
 
36
36
  /**
37
- * Tenant-specific vocabulary for the pilot dialogs. The platform
38
- * provides neutral defaults ("Mandant"); consumers override with
39
- * their domain language (e.g. "Verein" or "Händler").
37
+ * Tenant-specific vocabulary for the pilot dialogs. The platform provides
38
+ * neutral defaults via the `pilots.copyDefaults` message namespace; consumers
39
+ * override with their domain language (e.g. "Verein" or "Händler").
40
40
  */
41
41
  export interface PilotCopy {
42
42
  /** Subtitle of the tenant section in the create dialog. */
@@ -53,16 +53,6 @@ export interface PilotCopy {
53
53
  notePlaceholder?: string;
54
54
  }
55
55
 
56
- /** Neutral, tenant-agnostic defaults for {@link PilotCopy}. */
57
- export const DEFAULT_PILOT_COPY: Required<PilotCopy> = {
58
- tenantSubtitle: 'Stammdaten des Mandanten',
59
- tenantNameLabel: 'Name des Mandanten',
60
- tenantNamePlaceholder: 'z. B. Muster GmbH',
61
- slugPlaceholder: 'muster-mandant',
62
- adminEmailPlaceholder: 'admin@example.com',
63
- notePlaceholder: 'z. B. Sales-Pilot · Demo-Zugang',
64
- };
65
-
66
56
  export type PromoCodeValueType = 'PERCENT' | 'ABSOLUTE';
67
57
  export type PromoCodeDurationType = 'ONCE' | 'MONTHS' | 'BILLING_CYCLES';
68
58
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="sp-cycle" role="radiogroup" :aria-label="i18n.ariaLabel">
2
+ <div class="sp-cycle" role="radiogroup" :aria-label="labels.ariaLabel">
3
3
  <button
4
4
  type="button"
5
5
  class="sp-cycle__btn"
@@ -8,7 +8,7 @@
8
8
  :aria-checked="modelValue === 'MONTHLY'"
9
9
  @click="emit('update:modelValue', 'MONTHLY')"
10
10
  >
11
- {{ i18n.monthly }}
11
+ {{ labels.monthly }}
12
12
  </button>
13
13
  <button
14
14
  type="button"
@@ -18,33 +18,44 @@
18
18
  :aria-checked="modelValue === 'YEARLY'"
19
19
  @click="emit('update:modelValue', 'YEARLY')"
20
20
  >
21
- {{ i18n.yearly }}
22
- <span v-if="i18n.savePill" class="sp-cycle__pill">{{ i18n.savePill }}</span>
21
+ {{ labels.yearly }}
22
+ <span v-if="labels.savePill" class="sp-cycle__pill">{{ labels.savePill }}</span>
23
23
  </button>
24
24
  </div>
25
25
  </template>
26
26
 
27
27
  <script setup lang="ts">
28
- import type { BillingCycleStr } from '../../use-tenant-billing.js';
28
+ import { computed } from 'vue';
29
+ import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
30
+ import type { BillingCycleStr } from '../../vue/use-tenant-billing.js';
29
31
 
30
32
  interface I18n {
31
- ariaLabel: string;
32
- monthly: string;
33
- yearly: string;
33
+ ariaLabel?: string;
34
+ monthly?: string;
35
+ yearly?: string;
34
36
  /** Optional: Spar-Pill neben "Jährlich" (z. B. "−2 Mt"). */
35
37
  savePill?: string;
36
38
  }
37
39
 
38
40
  const props = defineProps<{
39
41
  modelValue: BillingCycleStr;
40
- i18n: I18n;
42
+ /** Per-string overrides; unset entries fall back to the platform catalog. */
43
+ i18n?: I18n;
41
44
  }>();
42
45
 
43
46
  const emit = defineEmits<{
44
47
  'update:modelValue': [BillingCycleStr];
45
48
  }>();
46
49
 
47
- void props;
50
+ const msg = useSaMessages('plans');
51
+ const common = useSaMessages('common');
52
+
53
+ const labels = computed(() => ({
54
+ ariaLabel: props.i18n?.ariaLabel ?? msg.value.cycle.ariaLabel,
55
+ monthly: props.i18n?.monthly ?? common.value.monthly,
56
+ yearly: props.i18n?.yearly ?? common.value.yearly,
57
+ savePill: props.i18n?.savePill,
58
+ }));
48
59
  </script>
49
60
 
50
61
  <style scoped>
@@ -48,8 +48,8 @@
48
48
  </template>
49
49
 
50
50
  <script setup lang="ts">
51
- import type { CatalogPlan } from '../../use-tenant-billing-catalog.js';
52
- import type { BillingCycleStr } from '../../use-tenant-billing.js';
51
+ import type { CatalogPlan } from '../../vue/use-tenant-billing-catalog.js';
52
+ import type { BillingCycleStr } from '../../vue/use-tenant-billing.js';
53
53
 
54
54
  interface I18n {
55
55
  popular: string;
@@ -90,7 +90,7 @@
90
90
  </template>
91
91
 
92
92
  <script setup lang="ts">
93
- import type { DraftPricing } from '../../use-subscription-draft.js';
93
+ import type { DraftPricing } from '../../vue/use-subscription-draft.js';
94
94
 
95
95
  interface I18n {
96
96
  noPlan: string;
@@ -46,7 +46,7 @@
46
46
 
47
47
  <script setup lang="ts">
48
48
  import { ref, watch } from 'vue';
49
- import type { PromoState } from '../../use-subscription-draft.js';
49
+ import type { PromoState } from '../../vue/use-subscription-draft.js';
50
50
 
51
51
  interface I18n {
52
52
  openLabel: string;
@@ -76,7 +76,7 @@ import {
76
76
  type BundleAvailabilityState,
77
77
  type PublicMarketingBundle,
78
78
  } from '@saasicat/types';
79
- import type { BillingCycleStr } from '../../use-tenant-billing.js';
79
+ import type { BillingCycleStr } from '../../vue/use-tenant-billing.js';
80
80
 
81
81
  interface I18n {
82
82
  perMonth: string;
@@ -55,6 +55,9 @@
55
55
  <script setup lang="ts">
56
56
  import { computed } from 'vue';
57
57
  import type { PlanRow, PlanVersionRow } from '@saasicat/types';
58
+ import { formatMessage } from '../../client/i18n/format.js';
59
+ import { formatCurrency } from '../../client/i18n/currency.js';
60
+ import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
58
61
  import PlanCockpitAuditLog from './PlanCockpitAuditLog.vue';
59
62
  import PlanCockpitDiffPanel from './PlanCockpitDiffPanel.vue';
60
63
  import PlanCockpitHeader from './PlanCockpitHeader.vue';
@@ -114,6 +117,9 @@ const emit = defineEmits<{
114
117
  (e: 'viewDiff', from: PlanVersionRow, to: PlanVersionRow): void;
115
118
  }>();
116
119
 
120
+ const msg = useSaMessages('planDetail');
121
+ const { locale } = useSuperAdminI18n();
122
+
117
123
  function forwardViewDiff(from: PlanVersionRow, to: PlanVersionRow): void {
118
124
  emit('viewDiff', from, to);
119
125
  }
@@ -176,7 +182,7 @@ const timelineAxis = computed(() => {
176
182
  for (const v of [...props.versions].sort((a, b) => a.version - b.version)) {
177
183
  if (v.validFrom) labels.push(v.validFrom.slice(0, 7));
178
184
  }
179
- labels.push('jetzt');
185
+ labels.push(msg.value.versions.timelineNow);
180
186
  return labels;
181
187
  });
182
188
 
@@ -184,8 +190,7 @@ const timelineAxis = computed(() => {
184
190
  function formatMoney(raw: string | number): string {
185
191
  const num = typeof raw === 'string' ? Number(raw) : raw;
186
192
  if (!Number.isFinite(num)) return String(raw);
187
- if (Number.isInteger(num)) return `${num} €`;
188
- return `${num.toFixed(2).replace('.', ',')} €`;
193
+ return formatCurrency(num, locale.value);
189
194
  }
190
195
 
191
196
  // ── Diff ────────────────────────────────────────────────────────────
@@ -255,34 +260,44 @@ const diff = computed<DiffSummary>(() => {
255
260
  removedQuotas,
256
261
  changedQuotas,
257
262
  priceChanged,
258
- priceFrom: `${formatMoney(from.monthlyNet)} / Mo · ${formatMoney(from.yearlyNet)} / J`,
259
- priceTo: `${formatMoney(to.monthlyNet)} / Mo · ${formatMoney(to.yearlyNet)} / J`,
263
+ priceFrom: formatPricePair(from),
264
+ priceTo: formatPricePair(to),
260
265
  };
261
266
  });
262
267
 
268
+ function formatPricePair(version: PlanVersionRow): string {
269
+ return formatMessage(msg.value.diff.pricePair, {
270
+ monthly: formatMoney(version.monthlyNet),
271
+ yearly: formatMoney(version.yearlyNet),
272
+ });
273
+ }
274
+
263
275
  const STYLES = {
264
- added: { bg: '#ecfdf5', border: '#a7f3d0', color: '#047857', sign: '+', tag: 'neu' },
265
- removed: { bg: '#fef2f2', border: '#fecaca', color: '#b91c1c', sign: '−', tag: 'entfernt' },
266
- changed: { bg: '#fffbeb', border: '#fde68a', color: '#b45309', sign: '~', tag: 'geändert' },
276
+ added: { bg: '#ecfdf5', border: '#a7f3d0', color: '#047857', sign: '+' },
277
+ removed: { bg: '#fef2f2', border: '#fecaca', color: '#b91c1c', sign: '−' },
278
+ changed: { bg: '#fffbeb', border: '#fde68a', color: '#b45309', sign: '~' },
267
279
  } as const;
268
280
 
269
281
  const diffRows = computed<DiffRow[]>(() => {
282
+ const texts = msg.value.diff;
270
283
  const out: DiffRow[] = [];
271
284
  for (const f of diff.value.addedFeatures) {
272
285
  out.push({
273
286
  id: `add-f-${f}`,
274
- section: 'Features',
287
+ section: texts.sectionLabelFeatures,
275
288
  label: featureLabel(f),
276
289
  sub: f,
290
+ tag: texts.tagNew,
277
291
  ...STYLES.added,
278
292
  });
279
293
  }
280
294
  for (const f of diff.value.removedFeatures) {
281
295
  out.push({
282
296
  id: `rem-f-${f}`,
283
- section: 'Features',
297
+ section: texts.sectionLabelFeatures,
284
298
  label: featureLabel(f),
285
299
  sub: f,
300
+ tag: texts.tagRemoved,
286
301
  ...STYLES.removed,
287
302
  });
288
303
  }
@@ -290,11 +305,12 @@ const diffRows = computed<DiffRow[]>(() => {
290
305
  const unit = quotaUnit(q.key);
291
306
  out.push({
292
307
  id: `chg-q-${q.key}`,
293
- section: 'Quotas',
308
+ section: texts.sectionLabelQuotas,
294
309
  label: quotaLabel(q.key),
295
310
  sub: q.key,
296
311
  from: `${q.from}${unit ? ' ' + unit : ''}`,
297
312
  to: `${q.to}${unit ? ' ' + unit : ''}`,
313
+ tag: texts.tagChanged,
298
314
  ...STYLES.changed,
299
315
  });
300
316
  }
@@ -302,10 +318,11 @@ const diffRows = computed<DiffRow[]>(() => {
302
318
  const unit = quotaUnit(q.key);
303
319
  out.push({
304
320
  id: `add-q-${q.key}`,
305
- section: 'Quotas',
321
+ section: texts.sectionLabelQuotas,
306
322
  label: quotaLabel(q.key),
307
323
  sub: q.key,
308
324
  to: `${q.value}${unit ? ' ' + unit : ''}`,
325
+ tag: texts.tagNew,
309
326
  ...STYLES.added,
310
327
  });
311
328
  }
@@ -313,20 +330,22 @@ const diffRows = computed<DiffRow[]>(() => {
313
330
  const unit = quotaUnit(q.key);
314
331
  out.push({
315
332
  id: `rem-q-${q.key}`,
316
- section: 'Quotas',
333
+ section: texts.sectionLabelQuotas,
317
334
  label: quotaLabel(q.key),
318
335
  sub: q.key,
319
336
  from: `${q.value}${unit ? ' ' + unit : ''}`,
337
+ tag: texts.tagRemoved,
320
338
  ...STYLES.removed,
321
339
  });
322
340
  }
323
341
  if (diff.value.priceChanged) {
324
342
  out.push({
325
343
  id: 'chg-price',
326
- section: 'Preis',
327
- label: 'Jahres- + Monatspreis',
344
+ section: texts.sectionLabelPrice,
345
+ label: texts.priceYearMonthLabel,
328
346
  from: diff.value.priceFrom,
329
347
  to: diff.value.priceTo,
348
+ tag: texts.tagChanged,
330
349
  ...STYLES.changed,
331
350
  });
332
351
  }