@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.
- package/README.md +17 -0
- package/dist/{chunk-EH7TOD4H.js → chunk-A3W2N26K.js} +73 -77
- package/dist/{chunk-3YP2ZAOD.js → chunk-QZOSDCNP.js} +36 -1
- package/dist/chunk-X5SQ5HMB.js +3231 -0
- package/dist/client/index.cjs +3279 -76
- package/dist/client/index.d.cts +31 -5
- package/dist/client/index.d.ts +31 -5
- package/dist/client/index.js +23 -5
- package/dist/index.cjs +3709 -465
- package/dist/index.d.cts +37 -54
- package/dist/index.d.ts +37 -54
- package/dist/index.js +347 -311
- package/dist/messages-7b0P8W75.d.cts +1652 -0
- package/dist/messages-7b0P8W75.d.ts +1652 -0
- package/dist/quasar/index.cjs +3185 -4
- package/dist/quasar/index.d.cts +11 -2
- package/dist/quasar/index.d.ts +11 -2
- package/dist/quasar/index.js +8 -3
- package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
- package/dist/testing-e2e/admin-pages-suite.js +14 -11
- package/dist/{ui-notify-COUzXEQ4.d.ts → use-super-admin-i18n-B3v3-SWZ.d.ts} +35 -3
- package/dist/{ui-notify-D_eAAwRh.d.cts → use-super-admin-i18n-DMktRVEt.d.cts} +35 -3
- package/package.json +2 -2
- package/src/client/action-registry.ts +4 -4
- package/src/client/batch-column-fetcher.ts +4 -4
- package/src/client/boot-loader.ts +3 -3
- package/src/client/i18n/currency.ts +42 -0
- package/src/client/i18n/define.ts +59 -0
- package/src/client/i18n/format.ts +12 -0
- package/src/client/i18n/index.ts +14 -0
- package/src/client/i18n/locale.ts +15 -0
- package/src/client/i18n/messages/audit.ts +42 -0
- package/src/client/i18n/messages/bundles.ts +459 -0
- package/src/client/i18n/messages/common.ts +147 -0
- package/src/client/i18n/messages/dashboard.ts +16 -0
- package/src/client/i18n/messages/discovery.ts +229 -0
- package/src/client/i18n/messages/email.ts +125 -0
- package/src/client/i18n/messages/marketing.ts +270 -0
- package/src/client/i18n/messages/nav.ts +72 -0
- package/src/client/i18n/messages/pilots.ts +192 -0
- package/src/client/i18n/messages/plan-detail.ts +266 -0
- package/src/client/i18n/messages/plan-editor.ts +177 -0
- package/src/client/i18n/messages/plan-versions.ts +251 -0
- package/src/client/i18n/messages/plans.ts +503 -0
- package/src/client/i18n/messages/promos.ts +202 -0
- package/src/client/i18n/messages/shell.ts +145 -0
- package/src/client/i18n/messages/tenants.ts +104 -0
- package/src/client/i18n/messages/users.ts +78 -0
- package/src/client/i18n/messages.ts +84 -0
- package/src/client/index.ts +2 -0
- package/src/client/manifest-loader.ts +4 -4
- package/src/client/nav-builder.ts +44 -51
- package/src/client/plan-versions-catalog.ts +42 -30
- package/src/components/BundleVersionPublishDialog.vue +35 -24
- package/src/components/MarketingPromotionsTab.vue +112 -59
- package/src/components/MfaPromptDialog.vue +11 -11
- package/src/components/TenantActionConfirmDialog.vue +19 -17
- package/src/components/VersionDiffPreview.vue +72 -43
- package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
- package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
- package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
- package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
- package/src/components/bundle-editor/bundle-version-status.ts +29 -29
- package/src/components/dialogs/PilotCreateDialog.vue +58 -63
- package/src/components/dialogs/PilotEditDialog.vue +40 -34
- package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
- package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
- package/src/components/dialogs/types.ts +3 -13
- package/src/components/plan/PlanCycleToggle.vue +20 -9
- package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
- package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
- package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
- package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
- package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
- package/src/components/plan-detail/PlanAuditLog.vue +11 -6
- package/src/components/plan-detail/PlanDetail.vue +21 -23
- package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
- package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
- package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
- package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
- package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
- package/src/components/plan-list/PlanList.vue +99 -57
- package/src/components/plan-matrix/PlanMatrix.vue +85 -38
- package/src/components/plan-review/PlanReview.vue +105 -80
- package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
- package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
- package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
- package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
- package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
- package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
- package/src/index.ts +1 -1
- package/src/pages-standard/AdminLayout.vue +42 -17
- package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
- package/src/pages-standard/AuditPage.vue +58 -19
- package/src/pages-standard/BundlesPage.vue +29 -18
- package/src/pages-standard/DashboardPage.vue +29 -15
- package/src/pages-standard/DiscoveryPage.vue +40 -28
- package/src/pages-standard/EmailHistoryPage.vue +92 -59
- package/src/pages-standard/MarketingCatalogPage.vue +35 -16
- package/src/pages-standard/PilotsPage.vue +57 -41
- package/src/pages-standard/PlanVersionsPage.vue +7 -3
- package/src/pages-standard/PlansPage.vue +89 -57
- package/src/pages-standard/PlatformEmailPage.vue +115 -50
- package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
- package/src/pages-standard/PromoCodesPage.vue +46 -35
- package/src/pages-standard/SubscriptionsPage.vue +14 -9
- package/src/pages-standard/SuperAdminLoginPage.vue +16 -13
- package/src/pages-standard/SuperAdminSetupWizard.vue +51 -47
- package/src/pages-standard/TenantDetailPage.vue +50 -36
- package/src/pages-standard/TenantsPage.vue +54 -41
- package/src/pages-standard/UsersPage.vue +66 -42
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
- package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
- package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
- package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
- package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
- package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
- package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
- package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
- package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
- package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
- package/src/pages-standard/plan-versions/PlanVersionHeader.vue +41 -25
- package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +45 -26
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +7 -5
- package/src/pages-standard/plan-versions/PlanVersionsList.vue +39 -16
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +24 -12
- package/src/pages-standard/plan-versions/format.ts +33 -17
- package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
- package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
- package/src/pages-standard/tenants/format.ts +3 -2
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +72 -37
- package/src/pages-tenant/PlanChangeWizard.vue +8 -4
- package/src/pages-tenant/TenantPlanSection.vue +17 -13
- package/src/pages-tenant/default-i18n.ts +217 -1
- package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +8 -6
- package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +9 -10
- package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
- package/src/quasar/create-super-admin-app.ts +19 -0
- package/src/testing-e2e/admin-pages-suite.ts +14 -11
- package/src/vue/project-page-host.ts +5 -3
- package/src/vue/use-audit-entries.ts +3 -3
- package/src/vue/use-bulk-publish.ts +6 -4
- package/src/vue/use-bundle-versions-map.ts +6 -4
- package/src/vue/use-bundles.ts +13 -13
- package/src/vue/use-catalog-entries.ts +13 -10
- package/src/vue/use-discovery.ts +8 -5
- package/src/vue/use-entitlement.ts +3 -3
- package/src/vue/use-live-plan-versions.ts +4 -4
- package/src/vue/use-marketing-projections.ts +9 -6
- package/src/vue/use-nav.ts +8 -2
- package/src/vue/use-plan-editor.ts +2 -2
- package/src/vue/use-plan-versions.ts +3 -3
- package/src/vue/use-plans.ts +12 -12
- package/src/vue/use-platform-tenant-actions.ts +9 -6
- package/src/vue/use-promotions.ts +9 -10
- package/src/vue/use-subscription-draft.ts +2 -3
- package/src/vue/use-super-admin-context.ts +3 -3
- package/src/vue/use-super-admin-i18n.ts +82 -0
- package/src/vue/use-tenant-action-flow.ts +11 -6
- package/src/vue/use-tenant-manifest.ts +1 -3
- package/src/vue/use-tenant-subscription-bundles.ts +4 -4
- package/src/vue/use-tenants.ts +3 -3
- package/dist/action-registry-CQ2wL3R-.d.cts +0 -85
- package/dist/action-registry-CQ2wL3R-.d.ts +0 -85
- package/dist/chunk-LVGUSD3T.js +0 -26
- package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
- package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
- package/src/pages-standard/BusinessTypesPage.vue +0 -662
- package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
- package/src/vue/use-business-types.ts +0 -269
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
<q-banner v-if="error" class="sa-discovery__error" rounded>
|
|
13
13
|
<template #avatar><q-icon name="warning" color="negative" /></template>
|
|
14
|
-
|
|
14
|
+
{{ common.error }}: {{ error.message }}
|
|
15
15
|
</q-banner>
|
|
16
16
|
|
|
17
17
|
<DiscoveryKpis
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
/>
|
|
26
26
|
|
|
27
27
|
<q-tabs v-model="activeTab" align="left" dense class="sa-discovery__tabs">
|
|
28
|
-
<q-tab name="features" :label="
|
|
29
|
-
<q-tab name="quotas" :label="
|
|
28
|
+
<q-tab name="features" :label="featuresTabLabel" />
|
|
29
|
+
<q-tab name="quotas" :label="quotasTabLabel" />
|
|
30
30
|
</q-tabs>
|
|
31
31
|
|
|
32
32
|
<q-tab-panels v-model="activeTab" animated class="sa-discovery__panels">
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
dense
|
|
38
38
|
outlined
|
|
39
39
|
clearable
|
|
40
|
-
placeholder="
|
|
40
|
+
:placeholder="msg.searchPlaceholder"
|
|
41
41
|
class="sa-discovery__search"
|
|
42
42
|
>
|
|
43
43
|
<template #prepend><q-icon name="search" /></template>
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
outlined
|
|
49
49
|
emit-value
|
|
50
50
|
map-options
|
|
51
|
-
:options="
|
|
51
|
+
:options="statusFilterOptions"
|
|
52
52
|
class="sa-discovery__filter"
|
|
53
53
|
/>
|
|
54
54
|
</div>
|
|
@@ -76,21 +76,20 @@
|
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
<div v-if="filteredFeatures.length === 0" class="sa-discovery__empty-row">
|
|
79
|
-
|
|
79
|
+
{{ msg.noFeaturesMatchFilters }}
|
|
80
80
|
</div>
|
|
81
81
|
|
|
82
82
|
<div v-if="orphanCaps.length" class="sa-discovery__group">
|
|
83
83
|
<div class="sa-discovery__group-head">
|
|
84
84
|
<span class="sa-discovery__group-title sa-discovery__group-title--orphan">
|
|
85
85
|
<q-icon name="warning" size="14px" />
|
|
86
|
-
|
|
86
|
+
{{ msg.orphansTitle }}
|
|
87
87
|
</span>
|
|
88
88
|
<span class="sa-discovery__group-count">{{ orphanCaps.length }}</span>
|
|
89
89
|
</div>
|
|
90
90
|
<p class="sa-discovery__orphan-hint">
|
|
91
|
-
|
|
92
|
-
<code>@ImplementsCapability</code
|
|
93
|
-
nachziehen, sonst sind sie nicht verkaufbar.
|
|
91
|
+
{{ msg.orphanHint.before }} <code>feature:</code>{{ msg.orphanHint.middle }}
|
|
92
|
+
<code>@ImplementsCapability</code>{{ msg.orphanHint.after }}
|
|
94
93
|
</p>
|
|
95
94
|
<DiscoveryCapList
|
|
96
95
|
:capabilities="orphanCaps"
|
|
@@ -113,7 +112,7 @@
|
|
|
113
112
|
@quota-locale="onQuotaLocale"
|
|
114
113
|
/>
|
|
115
114
|
<div v-if="quotas.length === 0" class="sa-discovery__empty-row">
|
|
116
|
-
|
|
115
|
+
{{ msg.noQuotasDeclared }}
|
|
117
116
|
</div>
|
|
118
117
|
</div>
|
|
119
118
|
</q-tab-panel>
|
|
@@ -140,7 +139,9 @@ import DiscoveryHeader from './discovery-page/DiscoveryHeader.vue';
|
|
|
140
139
|
import DiscoveryKpis from './discovery-page/DiscoveryKpis.vue';
|
|
141
140
|
import DiscoveryMetaBanner from './discovery-page/DiscoveryMetaBanner.vue';
|
|
142
141
|
import DiscoveryQuotaCard from './discovery-page/DiscoveryQuotaCard.vue';
|
|
143
|
-
import {
|
|
142
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
143
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
144
|
+
import { statusLabel } from './discovery-page/discovery-ui.js';
|
|
144
145
|
|
|
145
146
|
// Platform standard page: Discovery review, feature-centric (#20).
|
|
146
147
|
// Two tabs (Features + Quotas); each entry is an expandable card with
|
|
@@ -174,18 +175,31 @@ const props = defineProps<{
|
|
|
174
175
|
}>();
|
|
175
176
|
|
|
176
177
|
const activeTab = ref<'features' | 'quotas'>('features');
|
|
178
|
+
|
|
179
|
+
const featuresTabLabel = computed(() =>
|
|
180
|
+
formatMessage(msg.value.tabFeatures, { count: props.features.length }),
|
|
181
|
+
);
|
|
182
|
+
const quotasTabLabel = computed(() =>
|
|
183
|
+
formatMessage(msg.value.tabQuotas, { count: props.quotas.length }),
|
|
184
|
+
);
|
|
177
185
|
const featureQuery = ref('');
|
|
178
186
|
const statusFilter = ref<DiscoveryStatus | 'all'>('all');
|
|
179
187
|
const expandedFeature = ref<string | null>(null);
|
|
180
188
|
const expandedQuota = ref<string | null>(null);
|
|
181
189
|
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
190
|
+
const msg = useSaMessages('discovery');
|
|
191
|
+
const common = useSaMessages('common');
|
|
192
|
+
const { locale, intlLocale } = useSuperAdminI18n();
|
|
193
|
+
|
|
194
|
+
const statusFilterOptions = computed<Array<{ label: string; value: DiscoveryStatus | 'all' }>>(
|
|
195
|
+
() => [
|
|
196
|
+
{ label: msg.value.statusFilterAll, value: 'all' },
|
|
197
|
+
...(['pending', 'approved', 'outdated', 'obsolete'] as const).map((status) => ({
|
|
198
|
+
label: statusLabel(status, locale.value),
|
|
199
|
+
value: status,
|
|
200
|
+
})),
|
|
201
|
+
],
|
|
202
|
+
);
|
|
189
203
|
|
|
190
204
|
const appKey = computed(() => props.snapshot?.app.key ?? '—');
|
|
191
205
|
const appLabel = computed(() => {
|
|
@@ -194,9 +208,9 @@ const appLabel = computed(() => {
|
|
|
194
208
|
});
|
|
195
209
|
const appVersion = computed(() => props.snapshot?.app.version ?? '0.0.0');
|
|
196
210
|
const scanLabel = computed(() => {
|
|
197
|
-
if (!props.snapshot?.scannedAt) return
|
|
211
|
+
if (!props.snapshot?.scannedAt) return msg.value.notScannedYet;
|
|
198
212
|
try {
|
|
199
|
-
return new Date(props.snapshot.scannedAt).toLocaleString(
|
|
213
|
+
return new Date(props.snapshot.scannedAt).toLocaleString(intlLocale.value);
|
|
200
214
|
} catch {
|
|
201
215
|
return props.snapshot.scannedAt;
|
|
202
216
|
}
|
|
@@ -277,21 +291,19 @@ const filteredFeatures = computed(() => {
|
|
|
277
291
|
});
|
|
278
292
|
});
|
|
279
293
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
/** Grouping by primary owner (#14); "Ohne Owner" last. */
|
|
294
|
+
/** Grouping by primary owner (#14); the ownerless group goes last. */
|
|
283
295
|
const featureGroups = computed<Array<{ label: string; features: FeatureCatalogEntryRow[] }>>(() => {
|
|
284
296
|
const groups = new Map<string, FeatureCatalogEntryRow[]>();
|
|
285
297
|
for (const f of filteredFeatures.value) {
|
|
286
|
-
const owner = ownersByFeature.value.get(f.featureKey)?.[0] ??
|
|
298
|
+
const owner = ownersByFeature.value.get(f.featureKey)?.[0] ?? msg.value.noOwner;
|
|
287
299
|
const list = groups.get(owner);
|
|
288
300
|
if (list) list.push(f);
|
|
289
301
|
else groups.set(owner, [f]);
|
|
290
302
|
}
|
|
291
303
|
return [...groups.entries()]
|
|
292
304
|
.sort((a, b) => {
|
|
293
|
-
if (a[0] ===
|
|
294
|
-
if (b[0] ===
|
|
305
|
+
if (a[0] === msg.value.noOwner) return 1;
|
|
306
|
+
if (b[0] === msg.value.noOwner) return -1;
|
|
295
307
|
return a[0].localeCompare(b[0]);
|
|
296
308
|
})
|
|
297
309
|
.map(([label, features]) => ({ label, features }));
|
|
@@ -340,7 +352,7 @@ function persist(p: Promise<unknown>): void {
|
|
|
340
352
|
p.catch((err) => {
|
|
341
353
|
// Auth renewal/redirect is handled by the HTTP client; here just log
|
|
342
354
|
// so a failed save stays visible.
|
|
343
|
-
console.error('
|
|
355
|
+
console.error('Failed to save catalog entry', err);
|
|
344
356
|
});
|
|
345
357
|
}
|
|
346
358
|
|
|
@@ -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">{{
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
|
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="
|
|
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="
|
|
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">
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
|
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">{{
|
|
141
|
-
|
|
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">
|
|
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="
|
|
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="
|
|
178
|
+
:label="msg.history.resend"
|
|
162
179
|
@click="onResend(detail.id)"
|
|
163
180
|
/>
|
|
164
|
-
<q-btn v-close-popup flat label="
|
|
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">
|
|
190
|
+
<div class="text-h6">{{ msg.history.confirmRemoveTitle }}</div>
|
|
174
191
|
</q-card-section>
|
|
175
192
|
<q-card-section class="q-pt-none">
|
|
176
|
-
|
|
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="
|
|
181
|
-
<q-btn
|
|
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';
|
|
220
|
+
import { computed, reactive, ref } from 'vue';
|
|
200
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
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:
|
|
254
|
-
{ label:
|
|
255
|
-
{ label:
|
|
256
|
-
{ label:
|
|
257
|
-
];
|
|
258
|
-
|
|
259
|
-
const columns = [
|
|
260
|
-
{ name: 'status', label:
|
|
261
|
-
{ name: 'toEmail', label:
|
|
262
|
-
{
|
|
263
|
-
|
|
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:
|
|
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:
|
|
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);
|
|
@@ -349,14 +382,14 @@ async function openDetail(row: EmailHistoryRow): Promise<void> {
|
|
|
349
382
|
}
|
|
350
383
|
|
|
351
384
|
async function onResend(id: string): Promise<void> {
|
|
352
|
-
const { ok, result } = await runWrite(
|
|
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
|
-
notify('negative', result.message ??
|
|
390
|
+
notify('negative', result.message ?? msg.value.history.sendFailed);
|
|
358
391
|
} else {
|
|
359
|
-
notify('positive',
|
|
392
|
+
notify('positive', msg.value.history.resendSuccess);
|
|
360
393
|
}
|
|
361
394
|
await reload();
|
|
362
395
|
}
|
|
@@ -370,11 +403,11 @@ async function confirmDelete(): Promise<void> {
|
|
|
370
403
|
const id = pendingDeleteId.value;
|
|
371
404
|
confirmDeleteOpen.value = false;
|
|
372
405
|
if (!id) return;
|
|
373
|
-
const { ok } = await runWrite(
|
|
406
|
+
const { ok } = await runWrite(msg.value.history.mfaRemove, (code) =>
|
|
374
407
|
props.deleteEmail(id, code || undefined),
|
|
375
408
|
);
|
|
376
409
|
if (!ok) return;
|
|
377
|
-
notify('positive',
|
|
410
|
+
notify('positive', msg.value.history.removeSuccess);
|
|
378
411
|
if (detail.value?.id === id) detailOpen.value = false;
|
|
379
412
|
await reload();
|
|
380
413
|
}
|
|
@@ -404,7 +437,7 @@ async function runWrite<T>(
|
|
|
404
437
|
} catch (err) {
|
|
405
438
|
const status = (err as { response?: { status?: number } })?.response?.status;
|
|
406
439
|
if (status === 401) {
|
|
407
|
-
mfaError.value =
|
|
440
|
+
mfaError.value = shell.value.mfa.invalidCode;
|
|
408
441
|
continue;
|
|
409
442
|
}
|
|
410
443
|
showMfa.value = false;
|
|
@@ -453,13 +486,13 @@ function statusColor(status: EmailHistoryStatus): string {
|
|
|
453
486
|
function statusLabel(status: EmailHistoryStatus): string {
|
|
454
487
|
switch (status) {
|
|
455
488
|
case 'SENT':
|
|
456
|
-
return
|
|
489
|
+
return msg.value.history.statusSent;
|
|
457
490
|
case 'FAILED':
|
|
458
|
-
return
|
|
491
|
+
return msg.value.history.statusFailed;
|
|
459
492
|
case 'BOUNCED':
|
|
460
|
-
return
|
|
493
|
+
return msg.value.history.statusBounced;
|
|
461
494
|
default:
|
|
462
|
-
return
|
|
495
|
+
return msg.value.history.statusPending;
|
|
463
496
|
}
|
|
464
497
|
}
|
|
465
498
|
|
|
@@ -467,14 +500,14 @@ function errMsg(err: unknown): string {
|
|
|
467
500
|
return (
|
|
468
501
|
(err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
|
|
469
502
|
(err as Error)?.message ??
|
|
470
|
-
|
|
503
|
+
msg.value.errorAction
|
|
471
504
|
);
|
|
472
505
|
}
|
|
473
506
|
|
|
474
507
|
function formatTs(iso: string | null | undefined): string {
|
|
475
508
|
if (!iso) return '—';
|
|
476
509
|
try {
|
|
477
|
-
return new Date(iso).toLocaleString(
|
|
510
|
+
return new Date(iso).toLocaleString(intlLocale.value, {
|
|
478
511
|
day: '2-digit',
|
|
479
512
|
month: 'short',
|
|
480
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>
|
|
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">
|
|
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
|
-
|
|
63
|
-
|
|
62
|
+
{{ msg.page.emptyPlansBefore }} <strong>{{ msg.page.emptyPlansLink }}</strong>
|
|
63
|
+
{{ msg.page.emptyPlansAfter }}
|
|
64
64
|
</div>
|
|
65
65
|
|
|
66
66
|
<MarketingCatalogPreview
|
|
@@ -126,6 +126,9 @@ import { usePlans } from '../vue/use-plans.js';
|
|
|
126
126
|
import { useMarketingProjections } from '../vue/use-marketing-projections.js';
|
|
127
127
|
import { usePromotions } from '../vue/use-promotions.js';
|
|
128
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';
|
|
129
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';
|
|
@@ -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
|
|
461
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
618
|
-
if (row.m.trialEnabled) return
|
|
619
|
-
return
|
|
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);
|