@saasicat/ui-vue 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +64 -4
  2. package/dist/chunk-A3W2N26K.js +714 -0
  3. package/dist/chunk-QZOSDCNP.js +98 -0
  4. package/dist/chunk-X5SQ5HMB.js +3231 -0
  5. package/dist/client/index.cjs +3988 -0
  6. package/dist/client/index.d.cts +321 -0
  7. package/dist/client/index.d.ts +321 -0
  8. package/dist/client/index.js +70 -0
  9. package/dist/index.cjs +4446 -1249
  10. package/dist/index.d.cts +297 -888
  11. package/dist/index.d.ts +297 -888
  12. package/dist/index.js +609 -1348
  13. package/dist/messages-7b0P8W75.d.cts +1652 -0
  14. package/dist/messages-7b0P8W75.d.ts +1652 -0
  15. package/dist/quasar/index.cjs +3340 -0
  16. package/dist/quasar/index.d.cts +95 -0
  17. package/dist/quasar/index.d.ts +95 -0
  18. package/dist/quasar/index.js +96 -0
  19. package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
  20. package/dist/testing-e2e/admin-pages-suite.js +14 -11
  21. package/dist/use-super-admin-i18n-B3v3-SWZ.d.ts +210 -0
  22. package/dist/use-super-admin-i18n-DMktRVEt.d.cts +210 -0
  23. package/package.json +23 -3
  24. package/src/{action-registry.ts → client/action-registry.ts} +4 -4
  25. package/src/{batch-column-fetcher.ts → client/batch-column-fetcher.ts} +4 -4
  26. package/src/{boot-loader.ts → client/boot-loader.ts} +3 -3
  27. package/src/client/i18n/currency.ts +42 -0
  28. package/src/client/i18n/define.ts +59 -0
  29. package/src/client/i18n/format.ts +12 -0
  30. package/src/client/i18n/index.ts +14 -0
  31. package/src/client/i18n/locale.ts +15 -0
  32. package/src/client/i18n/messages/audit.ts +42 -0
  33. package/src/client/i18n/messages/bundles.ts +459 -0
  34. package/src/client/i18n/messages/common.ts +147 -0
  35. package/src/client/i18n/messages/dashboard.ts +16 -0
  36. package/src/client/i18n/messages/discovery.ts +229 -0
  37. package/src/client/i18n/messages/email.ts +125 -0
  38. package/src/client/i18n/messages/marketing.ts +270 -0
  39. package/src/client/i18n/messages/nav.ts +72 -0
  40. package/src/client/i18n/messages/pilots.ts +192 -0
  41. package/src/client/i18n/messages/plan-detail.ts +266 -0
  42. package/src/client/i18n/messages/plan-editor.ts +177 -0
  43. package/src/client/i18n/messages/plan-versions.ts +251 -0
  44. package/src/client/i18n/messages/plans.ts +503 -0
  45. package/src/client/i18n/messages/promos.ts +202 -0
  46. package/src/client/i18n/messages/shell.ts +145 -0
  47. package/src/client/i18n/messages/tenants.ts +104 -0
  48. package/src/client/i18n/messages/users.ts +78 -0
  49. package/src/client/i18n/messages.ts +84 -0
  50. package/src/client/index.ts +27 -0
  51. package/src/{manifest-loader.ts → client/manifest-loader.ts} +4 -4
  52. package/src/{nav-builder.ts → client/nav-builder.ts} +44 -51
  53. package/src/{plan-versions-catalog.ts → client/plan-versions-catalog.ts} +42 -30
  54. package/src/components/BundleVersionPublishDialog.vue +35 -24
  55. package/src/components/FeatureGate.vue +1 -1
  56. package/src/components/MarketingPromotionsTab.vue +112 -59
  57. package/src/components/MfaPromptDialog.vue +11 -11
  58. package/src/components/TenantActionConfirmDialog.vue +19 -17
  59. package/src/components/VersionDiffPreview.vue +72 -43
  60. package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
  61. package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
  62. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
  63. package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
  64. package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
  65. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
  66. package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
  67. package/src/components/bundle-editor/bundle-version-status.ts +29 -29
  68. package/src/components/dialogs/PilotCreateDialog.vue +58 -63
  69. package/src/components/dialogs/PilotEditDialog.vue +40 -34
  70. package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
  71. package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
  72. package/src/components/dialogs/types.ts +3 -13
  73. package/src/components/plan/PlanCycleToggle.vue +21 -10
  74. package/src/components/plan/PlanGrid.vue +2 -2
  75. package/src/components/plan/PriceSummary.vue +1 -1
  76. package/src/components/plan/PromoCodeInput.vue +1 -1
  77. package/src/components/plan/PublicBundleGrid.vue +1 -1
  78. package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
  79. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
  80. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
  81. package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
  82. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
  83. package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
  84. package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
  85. package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
  86. package/src/components/plan-detail/PlanAuditLog.vue +11 -6
  87. package/src/components/plan-detail/PlanDetail.vue +21 -23
  88. package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
  89. package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
  90. package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
  91. package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
  92. package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
  93. package/src/components/plan-list/PlanList.vue +99 -57
  94. package/src/components/plan-matrix/PlanMatrix.vue +85 -38
  95. package/src/components/plan-review/PlanReview.vue +105 -80
  96. package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
  97. package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
  98. package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
  99. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
  100. package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
  101. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
  102. package/src/index.ts +82 -79
  103. package/src/pages-standard/AdminLayout.vue +43 -18
  104. package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
  105. package/src/pages-standard/AuditPage.vue +58 -19
  106. package/src/pages-standard/BundlesPage.vue +29 -18
  107. package/src/pages-standard/DashboardPage.vue +31 -17
  108. package/src/pages-standard/DiscoveryPage.vue +40 -28
  109. package/src/pages-standard/EmailHistoryPage.vue +98 -69
  110. package/src/pages-standard/MarketingCatalogPage.vue +40 -21
  111. package/src/pages-standard/PilotsPage.vue +64 -52
  112. package/src/pages-standard/PlanVersionsPage.vue +9 -5
  113. package/src/pages-standard/PlansPage.vue +91 -59
  114. package/src/pages-standard/PlatformEmailPage.vue +120 -53
  115. package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
  116. package/src/pages-standard/PromoCodesPage.vue +51 -54
  117. package/src/pages-standard/SubscriptionsPage.vue +14 -9
  118. package/src/pages-standard/SuperAdminLoginPage.vue +19 -16
  119. package/src/pages-standard/SuperAdminSetupWizard.vue +53 -49
  120. package/src/pages-standard/TenantDetailPage.vue +54 -40
  121. package/src/pages-standard/TenantsPage.vue +57 -44
  122. package/src/pages-standard/UsersPage.vue +70 -52
  123. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
  124. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
  125. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
  126. package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
  127. package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
  128. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
  129. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
  130. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
  131. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
  132. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
  133. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
  134. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
  135. package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
  136. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
  137. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
  138. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
  139. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
  140. package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
  141. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +42 -26
  142. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +46 -27
  143. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +8 -6
  144. package/src/pages-standard/plan-versions/PlanVersionsList.vue +40 -17
  145. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +1 -1
  146. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +25 -13
  147. package/src/pages-standard/plan-versions/format.ts +33 -17
  148. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
  149. package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
  150. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
  151. package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
  152. package/src/pages-standard/tenants/format.ts +3 -2
  153. package/src/pages-tenant/MySubscriptionBundlesPage.vue +73 -38
  154. package/src/pages-tenant/OnboardingConfigurator.vue +3 -3
  155. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  156. package/src/pages-tenant/PlanChangeWizard.vue +10 -6
  157. package/src/pages-tenant/TenantPlanSection.vue +20 -16
  158. package/src/pages-tenant/default-i18n.ts +217 -1
  159. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +9 -7
  160. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +11 -12
  161. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
  162. package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +1 -1
  163. package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +1 -1
  164. package/src/quasar/create-super-admin-app.ts +192 -0
  165. package/src/quasar/index.ts +11 -0
  166. package/src/quasar/notify.ts +30 -0
  167. package/src/testing-e2e/admin-pages-suite.ts +14 -11
  168. package/src/{manifest-store-factory.ts → vue/manifest-store-factory.ts} +1 -1
  169. package/src/{platform-loaders.ts → vue/platform-loaders.ts} +4 -4
  170. package/src/{project-page-host.ts → vue/project-page-host.ts} +6 -4
  171. package/src/{create-super-admin-app.ts → vue/super-admin-context.ts} +20 -139
  172. package/src/vue/ui-notify.ts +30 -0
  173. package/src/{use-actions.ts → vue/use-actions.ts} +1 -1
  174. package/src/{use-api-list.ts → vue/use-api-list.ts} +1 -1
  175. package/src/{use-audit-entries.ts → vue/use-audit-entries.ts} +3 -3
  176. package/src/{use-batch-columns.ts → vue/use-batch-columns.ts} +1 -1
  177. package/src/{use-bulk-publish.ts → vue/use-bulk-publish.ts} +7 -5
  178. package/src/{use-bundle-versions-map.ts → vue/use-bundle-versions-map.ts} +7 -5
  179. package/src/{use-bundles.ts → vue/use-bundles.ts} +14 -14
  180. package/src/{use-catalog-entries.ts → vue/use-catalog-entries.ts} +14 -11
  181. package/src/{use-discovery.ts → vue/use-discovery.ts} +9 -6
  182. package/src/{use-entitlement.ts → vue/use-entitlement.ts} +4 -4
  183. package/src/{use-live-plan-versions.ts → vue/use-live-plan-versions.ts} +5 -5
  184. package/src/{use-manifest.ts → vue/use-manifest.ts} +1 -1
  185. package/src/{use-marketing-projections.ts → vue/use-marketing-projections.ts} +10 -7
  186. package/src/{use-nav.ts → vue/use-nav.ts} +9 -3
  187. package/src/{use-plan-editor.ts → vue/use-plan-editor.ts} +2 -2
  188. package/src/{use-plan-versions.ts → vue/use-plan-versions.ts} +3 -3
  189. package/src/{use-plans.ts → vue/use-plans.ts} +13 -13
  190. package/src/{use-platform-tenant-actions.ts → vue/use-platform-tenant-actions.ts} +9 -6
  191. package/src/{use-promotions.ts → vue/use-promotions.ts} +10 -11
  192. package/src/{use-public-boot.ts → vue/use-public-boot.ts} +1 -1
  193. package/src/{use-subscription-draft.ts → vue/use-subscription-draft.ts} +2 -3
  194. package/src/{use-super-admin-context.ts → vue/use-super-admin-context.ts} +5 -5
  195. package/src/vue/use-super-admin-i18n.ts +82 -0
  196. package/src/{use-tenant-action-flow.ts → vue/use-tenant-action-flow.ts} +12 -7
  197. package/src/{use-tenant-billing-catalog.ts → vue/use-tenant-billing-catalog.ts} +2 -2
  198. package/src/{use-tenant-billing.ts → vue/use-tenant-billing.ts} +2 -2
  199. package/src/{use-tenant-manifest.ts → vue/use-tenant-manifest.ts} +2 -4
  200. package/src/{use-tenant-subscription-bundles.ts → vue/use-tenant-subscription-bundles.ts} +5 -5
  201. package/src/{use-tenants.ts → vue/use-tenants.ts} +3 -3
  202. package/dist/testing-e2e/admin-pages-suite.d.cts +0 -37
  203. package/dist/testing-e2e/admin-pages-suite.d.ts +0 -37
  204. package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
  205. package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
  206. package/src/pages-standard/BusinessTypesPage.vue +0 -662
  207. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
  208. package/src/use-business-types.ts +0 -269
  209. /package/src/{http-json.ts → client/http-json.ts} +0 -0
  210. /package/src/{types.ts → client/types.ts} +0 -0
  211. /package/src/{version.ts → client/version.ts} +0 -0
  212. /package/src/{create-admin-routes.ts → vue/create-admin-routes.ts} +0 -0
  213. /package/src/{entitlement-provider.ts → vue/entitlement-provider.ts} +0 -0
  214. /package/src/{feature-router-guard.ts → vue/feature-router-guard.ts} +0 -0
@@ -2,13 +2,10 @@
2
2
  <div class="sa-emh">
3
3
  <header class="sa-emh__head">
4
4
  <div>
5
- <h1 class="sa-emh__title">{{ title }}</h1>
6
- <p class="sa-emh__sub">
7
- Über den Plattform-Absender versendete System-Mails — Empfänger, Status und
8
- Fehler.
9
- </p>
5
+ <h1 class="sa-emh__title">{{ resolvedTitle }}</h1>
6
+ <p class="sa-emh__sub">{{ msg.history.subtitle }}</p>
10
7
  </div>
11
- <q-btn flat icon="refresh" label="Neu laden" @click="applyFilter" />
8
+ <q-btn flat icon="refresh" :label="common.reload" @click="applyFilter" />
12
9
  </header>
13
10
 
14
11
  <div class="sa-emh__body">
@@ -18,7 +15,7 @@
18
15
  outlined
19
16
  dense
20
17
  clearable
21
- label="Suche (Empfänger, Betreff, Inhalt …)"
18
+ :label="msg.history.searchLabel"
22
19
  debounce="350"
23
20
  @keyup.enter="applyFilter"
24
21
  @update:model-value="applyFilter"
@@ -32,7 +29,7 @@
32
29
  clearable
33
30
  emit-value
34
31
  map-options
35
- label="Status"
32
+ :label="common.status"
36
33
  :options="statusOptions"
37
34
  @update:model-value="applyFilter"
38
35
  />
@@ -42,7 +39,7 @@
42
39
  dense
43
40
  clearable
44
41
  type="date"
45
- label="Von"
42
+ :label="common.from"
46
43
  @update:model-value="applyFilter"
47
44
  />
48
45
  <q-input
@@ -51,7 +48,7 @@
51
48
  dense
52
49
  clearable
53
50
  type="date"
54
- label="Bis"
51
+ :label="common.to"
55
52
  @update:model-value="applyFilter"
56
53
  />
57
54
  </div>
@@ -70,7 +67,10 @@
70
67
  >
71
68
  <template #body-cell-status="{ row }">
72
69
  <q-td>
73
- <q-badge :color="statusColor(row.status)" :label="statusLabel(row.status)" />
70
+ <q-badge
71
+ :color="statusColor(row.status)"
72
+ :label="statusLabel(row.status)"
73
+ />
74
74
  </q-td>
75
75
  </template>
76
76
  <template #body-cell-actions="{ row }">
@@ -80,7 +80,7 @@
80
80
  dense
81
81
  icon="send"
82
82
  color="primary"
83
- title="Erneut senden"
83
+ :title="msg.history.resend"
84
84
  @click.stop="onResend(row.id)"
85
85
  />
86
86
  <q-btn
@@ -88,13 +88,13 @@
88
88
  dense
89
89
  icon="delete"
90
90
  color="negative"
91
- title="Aus Verlauf entfernen"
91
+ :title="msg.history.removeFromHistory"
92
92
  @click.stop="askDelete(row.id)"
93
93
  />
94
94
  </q-td>
95
95
  </template>
96
96
  <template #no-data>
97
- <div class="sa-emh__empty">Keine E-Mails im Verlauf.</div>
97
+ <div class="sa-emh__empty">{{ msg.history.empty }}</div>
98
98
  </template>
99
99
  </q-table>
100
100
  </div>
@@ -106,21 +106,36 @@
106
106
  <template v-if="detail">
107
107
  <q-card-section>
108
108
  <div class="row items-center no-wrap q-gutter-sm">
109
- <q-badge :color="statusColor(detail.status)" :label="statusLabel(detail.status)" />
109
+ <q-badge
110
+ :color="statusColor(detail.status)"
111
+ :label="statusLabel(detail.status)"
112
+ />
110
113
  <div class="text-h6 ellipsis">{{ detail.subject }}</div>
111
114
  </div>
112
115
  <div class="text-caption text-grey-7 q-mt-xs">
113
- Von {{ detail.fromEmail }} · An {{ detail.toEmail }}
116
+ {{
117
+ formatMessage(msg.history.detailFromTo, {
118
+ from: detail.fromEmail,
119
+ to: detail.toEmail,
120
+ })
121
+ }}
114
122
  <span v-if="detail.ccEmail"> · Cc {{ detail.ccEmail }}</span>
115
123
  <span v-if="detail.bccEmail"> · Bcc {{ detail.bccEmail }}</span>
116
124
  </div>
117
125
  <div class="text-caption text-grey-7">
118
- Erstellt {{ formatTs(detail.createdAt) }} · Gesendet
119
- {{ formatTs(detail.sentAt) }}
126
+ {{
127
+ formatMessage(msg.history.detailTimestamps, {
128
+ created: formatTs(detail.createdAt),
129
+ sent: formatTs(detail.sentAt),
130
+ })
131
+ }}
120
132
  </div>
121
133
  </q-card-section>
122
134
 
123
- <q-card-section v-if="detail.status === 'FAILED' && detail.errorMessage" class="q-pt-none">
135
+ <q-card-section
136
+ v-if="detail.status === 'FAILED' && detail.errorMessage"
137
+ class="q-pt-none"
138
+ >
124
139
  <q-banner dense class="bg-red-1 text-red-9 sa-emh__error">
125
140
  <template #avatar><q-icon name="error" color="negative" /></template>
126
141
  {{ detail.errorMessage }}
@@ -137,12 +152,14 @@
137
152
  referrerpolicy="no-referrer"
138
153
  class="sa-emh__frame"
139
154
  />
140
- <pre v-else-if="detail.bodyText" class="sa-emh__text">{{ detail.bodyText }}</pre>
141
- <div v-else class="text-grey-6">Kein Inhalt gespeichert.</div>
155
+ <pre v-else-if="detail.bodyText" class="sa-emh__text">{{
156
+ detail.bodyText
157
+ }}</pre>
158
+ <div v-else class="text-grey-6">{{ msg.history.noContent }}</div>
142
159
  </q-card-section>
143
160
 
144
161
  <q-card-section v-if="detail.smtpResponse" class="q-pt-none">
145
- <div class="text-caption text-grey-7">SMTP-Antwort</div>
162
+ <div class="text-caption text-grey-7">{{ msg.history.smtpResponse }}</div>
146
163
  <pre class="sa-emh__smtp">{{ detail.smtpResponse }}</pre>
147
164
  </q-card-section>
148
165
 
@@ -151,17 +168,17 @@
151
168
  flat
152
169
  color="negative"
153
170
  icon="delete"
154
- label="Entfernen"
171
+ :label="common.remove"
155
172
  @click="askDelete(detail.id)"
156
173
  />
157
174
  <q-btn
158
175
  unelevated
159
176
  color="primary"
160
177
  icon="send"
161
- label="Erneut senden"
178
+ :label="msg.history.resend"
162
179
  @click="onResend(detail.id)"
163
180
  />
164
- <q-btn v-close-popup flat label="Schließen" />
181
+ <q-btn v-close-popup flat :label="common.close" />
165
182
  </q-card-actions>
166
183
  </template>
167
184
  </q-card>
@@ -170,15 +187,19 @@
170
187
  <q-dialog v-model="confirmDeleteOpen">
171
188
  <q-card class="sa-emh__confirm">
172
189
  <q-card-section>
173
- <div class="text-h6">E-Mail entfernen</div>
190
+ <div class="text-h6">{{ msg.history.confirmRemoveTitle }}</div>
174
191
  </q-card-section>
175
192
  <q-card-section class="q-pt-none">
176
- Diese E-Mail aus dem Verlauf entfernen? Der Eintrag wird ausgeblendet, bleibt
177
- aber für das Audit erhalten.
193
+ {{ msg.history.confirmRemoveMessage }}
178
194
  </q-card-section>
179
195
  <q-card-actions align="right">
180
- <q-btn v-close-popup flat label="Abbrechen" />
181
- <q-btn unelevated color="negative" label="Entfernen" @click="confirmDelete" />
196
+ <q-btn v-close-popup flat :label="common.cancel" />
197
+ <q-btn
198
+ unelevated
199
+ color="negative"
200
+ :label="common.remove"
201
+ @click="confirmDelete"
202
+ />
182
203
  </q-card-actions>
183
204
  </q-card>
184
205
  </q-dialog>
@@ -196,9 +217,11 @@
196
217
  </template>
197
218
 
198
219
  <script setup lang="ts">
199
- import { reactive, ref } from 'vue';
200
- import { useQuasar } from 'quasar';
220
+ import { computed, reactive, ref } from 'vue';
221
+ import { useSuperAdminNotify } from '../quasar/notify.js';
201
222
  import MfaPromptDialog from '../components/MfaPromptDialog.vue';
223
+ import { formatMessage } from '../client/i18n/format.js';
224
+ import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
202
225
  import type {
203
226
  EmailHistoryRow,
204
227
  EmailHistoryDetail,
@@ -225,13 +248,18 @@ const props = withDefaults(
225
248
  mfaSetupHint?: string;
226
249
  }>(),
227
250
  {
228
- title: 'Plattform-E-Mail-Verlauf',
229
251
  pageSize: 25,
230
252
  requireMfaForWrite: false,
231
253
  },
232
254
  );
233
255
 
234
- const q = useQuasar();
256
+ const notify = useSuperAdminNotify();
257
+ const msg = useSaMessages('email');
258
+ const common = useSaMessages('common');
259
+ const shell = useSaMessages('shell');
260
+ const { intlLocale } = useSuperAdminI18n();
261
+
262
+ const resolvedTitle = computed(() => props.title ?? msg.value.history.title);
235
263
 
236
264
  const rows = ref<EmailHistoryRow[]>([]);
237
265
  const loading = ref(false);
@@ -249,32 +277,37 @@ const pagination = ref({
249
277
  rowsNumber: 0,
250
278
  });
251
279
 
252
- const statusOptions = [
253
- { label: 'Gesendet', value: 'SENT' },
254
- { label: 'Fehlgeschlagen', value: 'FAILED' },
255
- { label: 'Ausstehend', value: 'PENDING' },
256
- { label: 'Bounced', value: 'BOUNCED' },
257
- ];
258
-
259
- const columns = [
260
- { name: 'status', label: 'Status', field: 'status', align: 'left' as const },
261
- { name: 'toEmail', label: 'Empfänger', field: 'toEmail', align: 'left' as const },
262
- { name: 'subject', label: 'Betreff', field: 'subject', align: 'left' as const },
263
- { name: 'fromEmail', label: 'Absender', field: 'fromEmail', align: 'left' as const },
280
+ const statusOptions = computed(() => [
281
+ { label: msg.value.history.statusSent, value: 'SENT' },
282
+ { label: msg.value.history.statusFailed, value: 'FAILED' },
283
+ { label: msg.value.history.statusPending, value: 'PENDING' },
284
+ { label: msg.value.history.statusBounced, value: 'BOUNCED' },
285
+ ]);
286
+
287
+ const columns = computed(() => [
288
+ { name: 'status', label: common.value.status, field: 'status', align: 'left' as const },
289
+ { name: 'toEmail', label: msg.value.recipient, field: 'toEmail', align: 'left' as const },
290
+ {
291
+ name: 'subject',
292
+ label: msg.value.history.columnSubject,
293
+ field: 'subject',
294
+ align: 'left' as const,
295
+ },
296
+ { name: 'fromEmail', label: msg.value.sender, field: 'fromEmail', align: 'left' as const },
264
297
  {
265
298
  name: 'createdAt',
266
- label: 'Erstellt',
299
+ label: msg.value.history.columnCreatedAt,
267
300
  field: (r: EmailHistoryRow) => formatTs(r.createdAt),
268
301
  align: 'left' as const,
269
302
  },
270
303
  {
271
304
  name: 'sentAt',
272
- label: 'Gesendet',
305
+ label: msg.value.history.columnSentAt,
273
306
  field: (r: EmailHistoryRow) => formatTs(r.sentAt),
274
307
  align: 'left' as const,
275
308
  },
276
309
  { name: 'actions', label: '', field: 'id' as never, align: 'right' as const },
277
- ];
310
+ ]);
278
311
 
279
312
  const detailOpen = ref(false);
280
313
  const detailLoading = ref(false);
@@ -312,7 +345,7 @@ async function reload(): Promise<void> {
312
345
  if (seq !== reloadSeq) return;
313
346
  rows.value = [];
314
347
  pagination.value.rowsNumber = 0;
315
- q.notify({ type: 'negative', message: errMsg(err), position: 'top' });
348
+ notify('negative', errMsg(err));
316
349
  } finally {
317
350
  if (seq === reloadSeq) loading.value = false;
318
351
  }
@@ -342,25 +375,21 @@ async function openDetail(row: EmailHistoryRow): Promise<void> {
342
375
  detail.value = await props.loadEmailDetail(row.id);
343
376
  } catch (err) {
344
377
  detailOpen.value = false;
345
- q.notify({ type: 'negative', message: errMsg(err), position: 'top' });
378
+ notify('negative', errMsg(err));
346
379
  } finally {
347
380
  detailLoading.value = false;
348
381
  }
349
382
  }
350
383
 
351
384
  async function onResend(id: string): Promise<void> {
352
- const { ok, result } = await runWrite('Plattform-Mail erneut senden', (code) =>
385
+ const { ok, result } = await runWrite(msg.value.history.mfaResend, (code) =>
353
386
  props.resendEmail(id, code || undefined),
354
387
  );
355
388
  if (!ok) return;
356
389
  if (result && result.success === false) {
357
- q.notify({
358
- type: 'negative',
359
- message: result.message ?? 'Versand fehlgeschlagen',
360
- position: 'top',
361
- });
390
+ notify('negative', result.message ?? msg.value.history.sendFailed);
362
391
  } else {
363
- q.notify({ type: 'positive', message: 'E-Mail erneut versendet', position: 'top' });
392
+ notify('positive', msg.value.history.resendSuccess);
364
393
  }
365
394
  await reload();
366
395
  }
@@ -374,11 +403,11 @@ async function confirmDelete(): Promise<void> {
374
403
  const id = pendingDeleteId.value;
375
404
  confirmDeleteOpen.value = false;
376
405
  if (!id) return;
377
- const { ok } = await runWrite('Plattform-Mail aus Verlauf entfernen', (code) =>
406
+ const { ok } = await runWrite(msg.value.history.mfaRemove, (code) =>
378
407
  props.deleteEmail(id, code || undefined),
379
408
  );
380
409
  if (!ok) return;
381
- q.notify({ type: 'positive', message: 'Aus Verlauf entfernt', position: 'top' });
410
+ notify('positive', msg.value.history.removeSuccess);
382
411
  if (detail.value?.id === id) detailOpen.value = false;
383
412
  await reload();
384
413
  }
@@ -394,7 +423,7 @@ async function runWrite<T>(
394
423
  const result = await invoke('');
395
424
  return { ok: true, result };
396
425
  } catch (err) {
397
- q.notify({ type: 'negative', message: errMsg(err), position: 'top' });
426
+ notify('negative', errMsg(err));
398
427
  return { ok: false };
399
428
  }
400
429
  }
@@ -408,11 +437,11 @@ async function runWrite<T>(
408
437
  } catch (err) {
409
438
  const status = (err as { response?: { status?: number } })?.response?.status;
410
439
  if (status === 401) {
411
- mfaError.value = 'TOTP-Code ungültig oder MFA nicht eingerichtet.';
440
+ mfaError.value = shell.value.mfa.invalidCode;
412
441
  continue;
413
442
  }
414
443
  showMfa.value = false;
415
- q.notify({ type: 'negative', message: errMsg(err), position: 'top' });
444
+ notify('negative', errMsg(err));
416
445
  return { ok: false };
417
446
  }
418
447
  }
@@ -457,13 +486,13 @@ function statusColor(status: EmailHistoryStatus): string {
457
486
  function statusLabel(status: EmailHistoryStatus): string {
458
487
  switch (status) {
459
488
  case 'SENT':
460
- return 'Gesendet';
489
+ return msg.value.history.statusSent;
461
490
  case 'FAILED':
462
- return 'Fehlgeschlagen';
491
+ return msg.value.history.statusFailed;
463
492
  case 'BOUNCED':
464
- return 'Bounced';
493
+ return msg.value.history.statusBounced;
465
494
  default:
466
- return 'Ausstehend';
495
+ return msg.value.history.statusPending;
467
496
  }
468
497
  }
469
498
 
@@ -471,14 +500,14 @@ function errMsg(err: unknown): string {
471
500
  return (
472
501
  (err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
473
502
  (err as Error)?.message ??
474
- 'Aktion fehlgeschlagen'
503
+ msg.value.errorAction
475
504
  );
476
505
  }
477
506
 
478
507
  function formatTs(iso: string | null | undefined): string {
479
508
  if (!iso) return '—';
480
509
  try {
481
- return new Date(iso).toLocaleString('de-DE', {
510
+ return new Date(iso).toLocaleString(intlLocale.value, {
482
511
  day: '2-digit',
483
512
  month: 'short',
484
513
  year: 'numeric',
@@ -32,7 +32,7 @@
32
32
  />
33
33
 
34
34
  <div v-if="pageError" class="mc-banner mc-banner--error" role="alert">
35
- <strong>Fehler:</strong> {{ pageError }}
35
+ <strong>{{ common.error }}:</strong> {{ pageError }}
36
36
  <button class="mc-banner-x" type="button" @click="pageError = null">×</button>
37
37
  </div>
38
38
 
@@ -43,7 +43,7 @@
43
43
  :active-locale="activeLocale"
44
44
  />
45
45
 
46
- <div v-if="loading" class="mc-loading">Lade Marketing-Catalog …</div>
46
+ <div v-if="loading" class="mc-loading">{{ msg.page.loading }}</div>
47
47
 
48
48
  <!-- ─── Tab: Promotions ─── -->
49
49
  <MarketingPromotionsTab
@@ -59,8 +59,8 @@
59
59
  />
60
60
 
61
61
  <div v-else-if="rows.length === 0" class="mc-banner mc-banner--info">
62
- Noch keine Pläne angelegt — der Marketing-Catalog projiziert veröffentlichte Pläne. Lege
63
- zuerst unter <strong>Pläne</strong> einen Plan mit Live-Version an.
62
+ {{ msg.page.emptyPlansBefore }} <strong>{{ msg.page.emptyPlansLink }}</strong>
63
+ {{ msg.page.emptyPlansAfter }}
64
64
  </div>
65
65
 
66
66
  <MarketingCatalogPreview
@@ -122,11 +122,14 @@ import {
122
122
  type PromotionResult,
123
123
  type PromotionRow,
124
124
  } from '@saasicat/types';
125
- import { usePlans } from '../use-plans.js';
126
- import { useMarketingProjections } from '../use-marketing-projections.js';
127
- import { usePromotions } from '../use-promotions.js';
128
- import { useCatalogEntries } from '../use-catalog-entries.js';
129
- import { defaultHttpClient, type HttpClient } from '../types.js';
125
+ import { usePlans } from '../vue/use-plans.js';
126
+ import { useMarketingProjections } from '../vue/use-marketing-projections.js';
127
+ import { usePromotions } from '../vue/use-promotions.js';
128
+ import { useCatalogEntries } from '../vue/use-catalog-entries.js';
129
+ import { formatCurrency } from '../client/i18n/currency.js';
130
+ import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
131
+ import { formatMessage } from '../client/i18n/format.js';
132
+ import { defaultHttpClient, type HttpClient } from '../client/types.js';
130
133
  import MarketingPromotionsTab from '../components/MarketingPromotionsTab.vue';
131
134
  import MarketingCatalogAdmin from './marketing-catalog/MarketingCatalogAdmin.vue';
132
135
  import MarketingCatalogHeader from './marketing-catalog/MarketingCatalogHeader.vue';
@@ -156,6 +159,10 @@ const props = defineProps<{
156
159
  planAccents?: Record<string, string>;
157
160
  }>();
158
161
 
162
+ const msg = useSaMessages('marketing');
163
+ const common = useSaMessages('common');
164
+ const { locale, intlLocale } = useSuperAdminI18n();
165
+
159
166
  const availableLocales = computed(() =>
160
167
  props.availableLocales && props.availableLocales.length > 0 ? props.availableLocales : ['de'],
161
168
  );
@@ -457,15 +464,25 @@ function formatVersionTab(v: PlanVersionRow): string {
457
464
  }
458
465
 
459
466
  function formatVersionTitle(v: PlanVersionRow): string {
460
- const from = v.validFrom ? formatDateLong(v.validFrom) : 'unbekannt';
461
- const until = v.validUntil ? formatDateLong(v.validUntil) : 'unbegrenzt';
467
+ const from = v.validFrom
468
+ ? formatDateLong(v.validFrom)
469
+ : msg.value.admin.versionValidFromUnknown;
470
+ const until = v.validUntil ? formatDateLong(v.validUntil) : common.value.unlimited;
462
471
  const changeNote = v.changeNote ? ` — ${v.changeNote}` : '';
463
- return `Version ${v.version}: gültig ${from} bis ${until}${changeNote}`;
472
+ return formatMessage(msg.value.admin.versionTitle, {
473
+ version: v.version,
474
+ from,
475
+ until,
476
+ changeNote,
477
+ });
464
478
  }
465
479
 
466
480
  function formatDateShort(iso: string): string {
467
481
  try {
468
- return new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
482
+ return new Date(iso).toLocaleDateString(intlLocale.value, {
483
+ day: '2-digit',
484
+ month: '2-digit',
485
+ });
469
486
  } catch {
470
487
  return iso.slice(0, 10);
471
488
  }
@@ -473,7 +490,7 @@ function formatDateShort(iso: string): string {
473
490
 
474
491
  function formatDateLong(iso: string): string {
475
492
  try {
476
- return new Date(iso).toLocaleDateString('de-DE', {
493
+ return new Date(iso).toLocaleDateString(intlLocale.value, {
477
494
  day: '2-digit',
478
495
  month: 'short',
479
496
  year: 'numeric',
@@ -577,9 +594,7 @@ function yearlyOf(row: MarketingRow): number {
577
594
  return row.liveVersion ? Number.parseFloat(row.liveVersion.yearlyNet) || 0 : 0;
578
595
  }
579
596
  function formatEuro(value: number): string {
580
- const rounded = Math.round(value * 100) / 100;
581
- const text = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(2).replace('.', ',');
582
- return `${text} €`;
597
+ return formatCurrency(Math.round(value * 100) / 100, locale.value);
583
598
  }
584
599
 
585
600
  // ─── Promo application in the preview (SPEC_V2 §9a) ───
@@ -601,7 +616,11 @@ function promoResultOf(row: MarketingRow): PromotionResult | null {
601
616
  function promoBadgeOf(row: MarketingRow): string {
602
617
  const promo = promoOf(row);
603
618
  if (!promo) return '';
604
- return promo.i18n?.[activeLocale.value]?.badge || promo.i18n?.de?.badge || 'Aktion';
619
+ return (
620
+ promo.i18n?.[activeLocale.value]?.badge ||
621
+ promo.i18n?.de?.badge ||
622
+ msg.value.preview.promoBadgeFallback
623
+ );
605
624
  }
606
625
  function promoFineprintOf(row: MarketingRow): string {
607
626
  const promo = promoOf(row);
@@ -614,9 +633,9 @@ function promoColorOf(row: MarketingRow): string {
614
633
 
615
634
  // ─── CTA ───
616
635
  function autoCtaText(row: MarketingRow): string {
617
- if (!row.liveVersion) return 'Kontakt aufnehmen';
618
- if (row.m.trialEnabled) return `Jetzt ${row.m.trialDays} Tage testen`;
619
- return 'Plan wählen';
636
+ if (!row.liveVersion) return msg.value.cta.contact;
637
+ if (row.m.trialEnabled) return formatMessage(msg.value.cta.trial, { days: row.m.trialDays });
638
+ return msg.value.cta.choosePlan;
620
639
  }
621
640
  function ctaText(row: MarketingRow): string {
622
641
  return row.m.ctaLabel && row.m.ctaLabel.length > 0 ? row.m.ctaLabel : autoCtaText(row);