@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.
- package/README.md +64 -4
- package/dist/chunk-A3W2N26K.js +714 -0
- package/dist/chunk-QZOSDCNP.js +98 -0
- package/dist/chunk-X5SQ5HMB.js +3231 -0
- package/dist/client/index.cjs +3988 -0
- package/dist/client/index.d.cts +321 -0
- package/dist/client/index.d.ts +321 -0
- package/dist/client/index.js +70 -0
- package/dist/index.cjs +4446 -1249
- package/dist/index.d.cts +297 -888
- package/dist/index.d.ts +297 -888
- package/dist/index.js +609 -1348
- package/dist/messages-7b0P8W75.d.cts +1652 -0
- package/dist/messages-7b0P8W75.d.ts +1652 -0
- package/dist/quasar/index.cjs +3340 -0
- package/dist/quasar/index.d.cts +95 -0
- package/dist/quasar/index.d.ts +95 -0
- package/dist/quasar/index.js +96 -0
- package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
- package/dist/testing-e2e/admin-pages-suite.js +14 -11
- package/dist/use-super-admin-i18n-B3v3-SWZ.d.ts +210 -0
- package/dist/use-super-admin-i18n-DMktRVEt.d.cts +210 -0
- package/package.json +23 -3
- package/src/{action-registry.ts → client/action-registry.ts} +4 -4
- package/src/{batch-column-fetcher.ts → client/batch-column-fetcher.ts} +4 -4
- package/src/{boot-loader.ts → 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 +27 -0
- package/src/{manifest-loader.ts → client/manifest-loader.ts} +4 -4
- package/src/{nav-builder.ts → client/nav-builder.ts} +44 -51
- package/src/{plan-versions-catalog.ts → client/plan-versions-catalog.ts} +42 -30
- package/src/components/BundleVersionPublishDialog.vue +35 -24
- package/src/components/FeatureGate.vue +1 -1
- 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 +21 -10
- package/src/components/plan/PlanGrid.vue +2 -2
- package/src/components/plan/PriceSummary.vue +1 -1
- package/src/components/plan/PromoCodeInput.vue +1 -1
- package/src/components/plan/PublicBundleGrid.vue +1 -1
- 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 +82 -79
- package/src/pages-standard/AdminLayout.vue +43 -18
- 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 +31 -17
- package/src/pages-standard/DiscoveryPage.vue +40 -28
- package/src/pages-standard/EmailHistoryPage.vue +98 -69
- package/src/pages-standard/MarketingCatalogPage.vue +40 -21
- package/src/pages-standard/PilotsPage.vue +64 -52
- package/src/pages-standard/PlanVersionsPage.vue +9 -5
- package/src/pages-standard/PlansPage.vue +91 -59
- package/src/pages-standard/PlatformEmailPage.vue +120 -53
- package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
- package/src/pages-standard/PromoCodesPage.vue +51 -54
- package/src/pages-standard/SubscriptionsPage.vue +14 -9
- package/src/pages-standard/SuperAdminLoginPage.vue +19 -16
- package/src/pages-standard/SuperAdminSetupWizard.vue +53 -49
- package/src/pages-standard/TenantDetailPage.vue +54 -40
- package/src/pages-standard/TenantsPage.vue +57 -44
- package/src/pages-standard/UsersPage.vue +70 -52
- 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 +42 -26
- package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +46 -27
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +8 -6
- package/src/pages-standard/plan-versions/PlanVersionsList.vue +40 -17
- package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +1 -1
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +25 -13
- 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 +73 -38
- package/src/pages-tenant/OnboardingConfigurator.vue +3 -3
- package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
- package/src/pages-tenant/PlanChangeWizard.vue +10 -6
- package/src/pages-tenant/TenantPlanSection.vue +20 -16
- package/src/pages-tenant/default-i18n.ts +217 -1
- package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +9 -7
- package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +11 -12
- package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
- package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +1 -1
- package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +1 -1
- package/src/quasar/create-super-admin-app.ts +192 -0
- package/src/quasar/index.ts +11 -0
- package/src/quasar/notify.ts +30 -0
- package/src/testing-e2e/admin-pages-suite.ts +14 -11
- package/src/{manifest-store-factory.ts → vue/manifest-store-factory.ts} +1 -1
- package/src/{platform-loaders.ts → vue/platform-loaders.ts} +4 -4
- package/src/{project-page-host.ts → vue/project-page-host.ts} +6 -4
- package/src/{create-super-admin-app.ts → vue/super-admin-context.ts} +20 -139
- package/src/vue/ui-notify.ts +30 -0
- package/src/{use-actions.ts → vue/use-actions.ts} +1 -1
- package/src/{use-api-list.ts → vue/use-api-list.ts} +1 -1
- package/src/{use-audit-entries.ts → vue/use-audit-entries.ts} +3 -3
- package/src/{use-batch-columns.ts → vue/use-batch-columns.ts} +1 -1
- package/src/{use-bulk-publish.ts → vue/use-bulk-publish.ts} +7 -5
- package/src/{use-bundle-versions-map.ts → vue/use-bundle-versions-map.ts} +7 -5
- package/src/{use-bundles.ts → vue/use-bundles.ts} +14 -14
- package/src/{use-catalog-entries.ts → vue/use-catalog-entries.ts} +14 -11
- package/src/{use-discovery.ts → vue/use-discovery.ts} +9 -6
- package/src/{use-entitlement.ts → vue/use-entitlement.ts} +4 -4
- package/src/{use-live-plan-versions.ts → vue/use-live-plan-versions.ts} +5 -5
- package/src/{use-manifest.ts → vue/use-manifest.ts} +1 -1
- package/src/{use-marketing-projections.ts → vue/use-marketing-projections.ts} +10 -7
- package/src/{use-nav.ts → vue/use-nav.ts} +9 -3
- package/src/{use-plan-editor.ts → vue/use-plan-editor.ts} +2 -2
- package/src/{use-plan-versions.ts → vue/use-plan-versions.ts} +3 -3
- package/src/{use-plans.ts → vue/use-plans.ts} +13 -13
- package/src/{use-platform-tenant-actions.ts → vue/use-platform-tenant-actions.ts} +9 -6
- package/src/{use-promotions.ts → vue/use-promotions.ts} +10 -11
- package/src/{use-public-boot.ts → vue/use-public-boot.ts} +1 -1
- package/src/{use-subscription-draft.ts → vue/use-subscription-draft.ts} +2 -3
- package/src/{use-super-admin-context.ts → vue/use-super-admin-context.ts} +5 -5
- package/src/vue/use-super-admin-i18n.ts +82 -0
- package/src/{use-tenant-action-flow.ts → vue/use-tenant-action-flow.ts} +12 -7
- package/src/{use-tenant-billing-catalog.ts → vue/use-tenant-billing-catalog.ts} +2 -2
- package/src/{use-tenant-billing.ts → vue/use-tenant-billing.ts} +2 -2
- package/src/{use-tenant-manifest.ts → vue/use-tenant-manifest.ts} +2 -4
- package/src/{use-tenant-subscription-bundles.ts → vue/use-tenant-subscription-bundles.ts} +5 -5
- package/src/{use-tenants.ts → vue/use-tenants.ts} +3 -3
- package/dist/testing-e2e/admin-pages-suite.d.cts +0 -37
- package/dist/testing-e2e/admin-pages-suite.d.ts +0 -37
- 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/use-business-types.ts +0 -269
- /package/src/{http-json.ts → client/http-json.ts} +0 -0
- /package/src/{types.ts → client/types.ts} +0 -0
- /package/src/{version.ts → client/version.ts} +0 -0
- /package/src/{create-admin-routes.ts → vue/create-admin-routes.ts} +0 -0
- /package/src/{entitlement-provider.ts → vue/entitlement-provider.ts} +0 -0
- /package/src/{feature-router-guard.ts → vue/feature-router-guard.ts} +0 -0
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="sa-pv-list">
|
|
3
3
|
<div class="sa-pv-list__kpis">
|
|
4
|
-
<PlanVersionsKpi label="Pakete" :value="snapshot.plans.length" icon="inventory_2" />
|
|
5
4
|
<PlanVersionsKpi
|
|
6
|
-
label="
|
|
5
|
+
:label="msg.list.kpiPlans"
|
|
6
|
+
:value="snapshot.plans.length"
|
|
7
|
+
icon="inventory_2"
|
|
8
|
+
/>
|
|
9
|
+
<PlanVersionsKpi
|
|
10
|
+
:label="msg.list.kpiSelfService"
|
|
7
11
|
:value="marketedCount"
|
|
8
12
|
icon="storefront"
|
|
9
|
-
:sub="
|
|
13
|
+
:sub="totalPlansLabel"
|
|
14
|
+
/>
|
|
15
|
+
<PlanVersionsKpi
|
|
16
|
+
:label="msg.list.kpiFeaturesTotal"
|
|
17
|
+
:value="totalFeatures"
|
|
18
|
+
icon="extension"
|
|
10
19
|
/>
|
|
11
|
-
<PlanVersionsKpi label="Features gesamt" :value="totalFeatures" icon="extension" />
|
|
12
20
|
</div>
|
|
13
21
|
|
|
14
22
|
<div v-if="missingPlans.length > 0" class="sa-pv-list__missing">
|
|
15
23
|
<q-icon name="info" size="16px" />
|
|
16
24
|
<div>
|
|
17
|
-
<strong>
|
|
18
|
-
|
|
25
|
+
<strong>{{ msg.list.missingPlansLabel }}</strong> {{ missingPlans.join(', ') }}
|
|
26
|
+
{{ msg.list.missingPlansHint }}
|
|
19
27
|
<template v-if="canEdit">
|
|
20
28
|
<button
|
|
21
29
|
v-for="planId in missingPlans"
|
|
@@ -24,7 +32,7 @@
|
|
|
24
32
|
class="sa-pv-list__missing-btn"
|
|
25
33
|
@click="emit('createPlanDraft', planId)"
|
|
26
34
|
>
|
|
27
|
-
|
|
35
|
+
{{ createDraftLabel(planId) }}
|
|
28
36
|
</button>
|
|
29
37
|
</template>
|
|
30
38
|
</div>
|
|
@@ -36,8 +44,8 @@
|
|
|
36
44
|
class="sa-pv-list__head sa-pv-list__row sa-pv-list__row--default"
|
|
37
45
|
:style="rowGridStyle"
|
|
38
46
|
>
|
|
39
|
-
<div>
|
|
40
|
-
<div class="sa-pv-list__th--num">
|
|
47
|
+
<div>{{ msg.list.colPlan }}</div>
|
|
48
|
+
<div class="sa-pv-list__th--num">{{ msg.list.colPriceNet }}</div>
|
|
41
49
|
<div
|
|
42
50
|
v-for="col in quotaColumns"
|
|
43
51
|
:key="`hd-${col.key}`"
|
|
@@ -45,7 +53,7 @@
|
|
|
45
53
|
>
|
|
46
54
|
{{ col.label }}
|
|
47
55
|
</div>
|
|
48
|
-
<div class="sa-pv-list__th--num">
|
|
56
|
+
<div class="sa-pv-list__th--num">{{ msg.list.colFeatures }}</div>
|
|
49
57
|
</div>
|
|
50
58
|
</slot>
|
|
51
59
|
|
|
@@ -65,7 +73,9 @@
|
|
|
65
73
|
<div class="sa-pv-list__default-name">
|
|
66
74
|
<strong>{{ plan.planId }}</strong>
|
|
67
75
|
<span class="sa-pv-list__default-version">v{{ plan.version }}</span>
|
|
68
|
-
<span v-if="plan.isDraft" class="sa-pv-list__default-chip">
|
|
76
|
+
<span v-if="plan.isDraft" class="sa-pv-list__default-chip">{{
|
|
77
|
+
msg.status.draft
|
|
78
|
+
}}</span>
|
|
69
79
|
</div>
|
|
70
80
|
<div class="sa-pv-list__th--num">
|
|
71
81
|
{{ formatEuro(plan.monthlyNet) }}
|
|
@@ -86,10 +96,10 @@
|
|
|
86
96
|
<div v-if="snapshot.kind === 'drafts'" class="sa-pv-list__draft-hint">
|
|
87
97
|
<q-icon name="info" size="18px" />
|
|
88
98
|
<div>
|
|
89
|
-
<strong>
|
|
90
|
-
|
|
91
|
-
<strong>
|
|
92
|
-
|
|
99
|
+
<strong>{{ msg.list.draftHintLabel }}</strong> {{ msg.list.draftHintBefore }}
|
|
100
|
+
<em>{{ msg.list.draftHintEditButton }}</em> {{ msg.list.draftHintMiddle }}
|
|
101
|
+
<strong>{{ msg.list.draftHintPublishFlow }}</strong>
|
|
102
|
+
{{ msg.list.draftHintAfter }}
|
|
93
103
|
</div>
|
|
94
104
|
</div>
|
|
95
105
|
</div>
|
|
@@ -97,7 +107,9 @@
|
|
|
97
107
|
|
|
98
108
|
<script setup lang="ts">
|
|
99
109
|
import { computed, ref } from 'vue';
|
|
100
|
-
import type { CatalogSnapshot } from '../../plan-versions-catalog.js';
|
|
110
|
+
import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
|
|
111
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
112
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
101
113
|
import PlanVersionsKpi from './PlanVersionsKpi.vue';
|
|
102
114
|
import { fmtEuro } from './format.js';
|
|
103
115
|
|
|
@@ -136,9 +148,16 @@ const emit = defineEmits<{
|
|
|
136
148
|
(e: 'createPlanDraft', planId: string): void;
|
|
137
149
|
}>();
|
|
138
150
|
|
|
151
|
+
const msg = useSaMessages('planVersions');
|
|
152
|
+
const { locale } = useSuperAdminI18n();
|
|
153
|
+
|
|
139
154
|
const openId = ref<string | null>(null);
|
|
140
155
|
const canEdit = computed(() => props.snapshot.kind === 'drafts');
|
|
141
156
|
|
|
157
|
+
const totalPlansLabel = computed(() =>
|
|
158
|
+
formatMessage(msg.value.list.kpiOfTotal, { count: props.snapshot.plans.length }),
|
|
159
|
+
);
|
|
160
|
+
|
|
142
161
|
const marketedCount = computed(() => props.snapshot.plans.filter((p) => p.marketed).length);
|
|
143
162
|
const totalFeatures = computed(() => new Set(props.snapshot.plans.flatMap((p) => p.features)).size);
|
|
144
163
|
|
|
@@ -163,8 +182,12 @@ function onToggle(id: string): void {
|
|
|
163
182
|
openId.value = openId.value === id ? null : id;
|
|
164
183
|
}
|
|
165
184
|
|
|
185
|
+
function createDraftLabel(planId: string): string {
|
|
186
|
+
return formatMessage(msg.value.list.createDraftFor, { planId });
|
|
187
|
+
}
|
|
188
|
+
|
|
166
189
|
function formatEuro(n: number): string {
|
|
167
|
-
return fmtEuro(n);
|
|
190
|
+
return fmtEuro(n, locale.value);
|
|
168
191
|
}
|
|
169
192
|
|
|
170
193
|
function formatQuotaValue(value: number | undefined, col: QuotaColumnConfig): string {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
|
|
53
53
|
<script setup lang="ts">
|
|
54
54
|
import { computed } from 'vue';
|
|
55
|
-
import type { CatalogSnapshot } from '../../plan-versions-catalog.js';
|
|
55
|
+
import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
|
|
56
56
|
import { fmtEuro } from './format.js';
|
|
57
57
|
|
|
58
58
|
// Generic feature matrix. Apps pass `featureRegistry` for label/icon/
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
<aside class="sa-pv-timeline">
|
|
3
3
|
<div class="sa-pv-timeline__head">
|
|
4
4
|
<div class="sa-pv-timeline__row">
|
|
5
|
-
<div class="sa-pv-timeline__title">{{ title ??
|
|
5
|
+
<div class="sa-pv-timeline__title">{{ title ?? msg.timeline.title }}</div>
|
|
6
6
|
</div>
|
|
7
|
-
<div class="sa-pv-timeline__count">{{
|
|
7
|
+
<div class="sa-pv-timeline__count">{{ snapshotCountLabel }}</div>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<div class="sa-pv-timeline__list">
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
<div class="sa-pv-timeline__foot">
|
|
57
57
|
<div class="sa-pv-timeline__hint">
|
|
58
58
|
<q-icon name="info" size="13px" />
|
|
59
|
-
<span>
|
|
59
|
+
<span>{{ msg.timeline.compareHint }}</span>
|
|
60
60
|
</div>
|
|
61
61
|
<button
|
|
62
62
|
v-if="compareId"
|
|
@@ -64,17 +64,20 @@
|
|
|
64
64
|
class="sa-pv-timeline__clear-btn"
|
|
65
65
|
@click="emit('clearCompare')"
|
|
66
66
|
>
|
|
67
|
-
|
|
67
|
+
{{ msg.compareEnd }}
|
|
68
68
|
</button>
|
|
69
69
|
</div>
|
|
70
70
|
</aside>
|
|
71
71
|
</template>
|
|
72
72
|
|
|
73
73
|
<script setup lang="ts">
|
|
74
|
-
import
|
|
75
|
-
import {
|
|
74
|
+
import { computed } from 'vue';
|
|
75
|
+
import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
|
|
76
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
77
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
78
|
+
import { formatRelative } from './format.js';
|
|
76
79
|
|
|
77
|
-
defineProps<{
|
|
80
|
+
const props = defineProps<{
|
|
78
81
|
snapshots: CatalogSnapshot[];
|
|
79
82
|
selectedId: string;
|
|
80
83
|
compareId: string | null;
|
|
@@ -87,18 +90,27 @@ const emit = defineEmits<{
|
|
|
87
90
|
(e: 'clearCompare'): void;
|
|
88
91
|
}>();
|
|
89
92
|
|
|
93
|
+
const msg = useSaMessages('planVersions');
|
|
94
|
+
const { locale } = useSuperAdminI18n();
|
|
95
|
+
|
|
96
|
+
const snapshotCountLabel = computed(() =>
|
|
97
|
+
formatMessage(msg.value.timeline.snapshotCount, { count: props.snapshots.length }),
|
|
98
|
+
);
|
|
99
|
+
|
|
90
100
|
function statusLabel(status: CatalogSnapshot['status']): string {
|
|
91
|
-
if (status === 'DRAFT') return
|
|
92
|
-
if (status === 'ACTIVE') return
|
|
93
|
-
return
|
|
101
|
+
if (status === 'DRAFT') return msg.value.status.draft;
|
|
102
|
+
if (status === 'ACTIVE') return msg.value.status.active;
|
|
103
|
+
return msg.value.status.archived;
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
function metaText(s: CatalogSnapshot): string {
|
|
97
107
|
if (s.kind === 'drafts') {
|
|
98
|
-
return s.draftCount === 0
|
|
108
|
+
return s.draftCount === 0
|
|
109
|
+
? msg.value.timeline.noOpenDrafts
|
|
110
|
+
: formatMessage(msg.value.timeline.openCount, { count: s.draftCount });
|
|
99
111
|
}
|
|
100
|
-
if (s.publishedAt) return
|
|
101
|
-
return
|
|
112
|
+
if (s.publishedAt) return formatRelative(s.publishedAt, locale.value);
|
|
113
|
+
return msg.value.timeline.unknown;
|
|
102
114
|
}
|
|
103
115
|
</script>
|
|
104
116
|
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
// Formatting helpers for the plan-versions views.
|
|
2
2
|
//
|
|
3
3
|
// Phase 2c: Ported from a consumer admin.
|
|
4
|
-
// The time reference point for `
|
|
4
|
+
// The time reference point for `formatRelative` is the moment of the call,
|
|
5
5
|
// not a hard-coded demo date (the ported template had `2026-05-04` —
|
|
6
6
|
// the platform must work in production).
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { formatCurrency } from '../../client/i18n/currency.js';
|
|
9
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
10
|
+
import { DEFAULT_SA_LOCALE, SA_INTL_LOCALES, type SaLocale } from '../../client/i18n/locale.js';
|
|
11
|
+
import { planVersionsMessages } from '../../client/i18n/messages/plan-versions.js';
|
|
12
|
+
|
|
13
|
+
const EMPTY_VALUE = '—';
|
|
14
|
+
|
|
15
|
+
function texts(locale: SaLocale) {
|
|
16
|
+
return planVersionsMessages[locale].format;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function fmtEuro(n: number, locale: SaLocale = DEFAULT_SA_LOCALE): string {
|
|
20
|
+
if (n === 0) return texts(locale).priceIndividual;
|
|
21
|
+
return formatCurrency(n, locale);
|
|
11
22
|
}
|
|
12
23
|
|
|
13
24
|
export function fmtStorage(gb: number): string {
|
|
@@ -16,34 +27,39 @@ export function fmtStorage(gb: number): string {
|
|
|
16
27
|
return `${gb} GB`;
|
|
17
28
|
}
|
|
18
29
|
|
|
19
|
-
export function
|
|
20
|
-
|
|
30
|
+
export function formatRelative(
|
|
31
|
+
iso: string | undefined,
|
|
32
|
+
locale: SaLocale = DEFAULT_SA_LOCALE,
|
|
33
|
+
now: Date = new Date(),
|
|
34
|
+
): string {
|
|
35
|
+
if (!iso) return EMPTY_VALUE;
|
|
36
|
+
const msg = texts(locale);
|
|
21
37
|
const d = new Date(iso);
|
|
22
38
|
const diffMs = now.getTime() - d.getTime();
|
|
23
39
|
const days = Math.floor(diffMs / 86_400_000);
|
|
24
|
-
if (days === 0) return
|
|
25
|
-
if (days === 1) return
|
|
26
|
-
if (days < 7) return
|
|
40
|
+
if (days === 0) return msg.today;
|
|
41
|
+
if (days === 1) return msg.yesterday;
|
|
42
|
+
if (days < 7) return formatMessage(msg.daysAgo, { count: days });
|
|
27
43
|
if (days < 30) {
|
|
28
44
|
const w = Math.floor(days / 7);
|
|
29
|
-
return
|
|
45
|
+
return formatMessage(w > 1 ? msg.weeksAgoMany : msg.weeksAgoOne, { count: w });
|
|
30
46
|
}
|
|
31
|
-
if (days < 365) return
|
|
47
|
+
if (days < 365) return formatMessage(msg.monthsAgo, { count: Math.floor(days / 30) });
|
|
32
48
|
const y = Math.floor(days / 365);
|
|
33
|
-
return
|
|
49
|
+
return formatMessage(y > 1 ? msg.yearsAgoMany : msg.yearsAgoOne, { count: y });
|
|
34
50
|
}
|
|
35
51
|
|
|
36
|
-
export function
|
|
37
|
-
if (!iso) return
|
|
38
|
-
return new Date(iso).toLocaleDateString(
|
|
52
|
+
export function formatDate(iso: string | undefined, locale: SaLocale = DEFAULT_SA_LOCALE): string {
|
|
53
|
+
if (!iso) return EMPTY_VALUE;
|
|
54
|
+
return new Date(iso).toLocaleDateString(SA_INTL_LOCALES[locale], {
|
|
39
55
|
day: '2-digit',
|
|
40
56
|
month: 'short',
|
|
41
57
|
year: 'numeric',
|
|
42
58
|
});
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
export function
|
|
46
|
-
return new Date(iso).toLocaleString(
|
|
61
|
+
export function formatTimestamp(iso: string, locale: SaLocale = DEFAULT_SA_LOCALE): string {
|
|
62
|
+
return new Date(iso).toLocaleString(SA_INTL_LOCALES[locale], {
|
|
47
63
|
day: '2-digit',
|
|
48
64
|
month: 'short',
|
|
49
65
|
year: 'numeric',
|
|
@@ -6,25 +6,25 @@
|
|
|
6
6
|
>
|
|
7
7
|
<q-card style="min-width: 440px">
|
|
8
8
|
<q-card-section>
|
|
9
|
-
<div class="text-h6">
|
|
9
|
+
<div class="text-h6">{{ msg.archiveDialog.title }}</div>
|
|
10
10
|
<div class="text-body2 q-mt-sm">
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
{{ msg.archiveDialog.bodyLead }} <code>{{ target?.plan.planKey }}</code>
|
|
12
|
+
{{ msg.archiveDialog.bodyVerb }}
|
|
13
|
+
<b>{{ msg.archiveDialog.bodyEmphasis }}</b
|
|
14
|
+
>{{ msg.archiveDialog.bodyTail }}
|
|
14
15
|
</div>
|
|
15
16
|
<div class="text-caption text-grey-7 q-mt-sm">
|
|
16
|
-
|
|
17
|
-
gelöscht werden — das Backend würde 422 antworten.
|
|
17
|
+
{{ msg.archiveDialog.contractProtectionNote }}
|
|
18
18
|
</div>
|
|
19
19
|
</q-card-section>
|
|
20
20
|
<q-banner v-if="error" class="bg-warning q-mx-md q-mb-md" rounded>
|
|
21
21
|
{{ error }}
|
|
22
22
|
</q-banner>
|
|
23
23
|
<q-card-actions align="right">
|
|
24
|
-
<q-btn flat label="
|
|
24
|
+
<q-btn flat :label="common.cancel" @click="$emit('update:modelValue', false)" />
|
|
25
25
|
<q-btn
|
|
26
26
|
color="negative"
|
|
27
|
-
label="
|
|
27
|
+
:label="common.delete"
|
|
28
28
|
:loading="archiving"
|
|
29
29
|
@click="$emit('execute')"
|
|
30
30
|
/>
|
|
@@ -34,8 +34,12 @@
|
|
|
34
34
|
</template>
|
|
35
35
|
|
|
36
36
|
<script setup lang="ts">
|
|
37
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
37
38
|
import type { PlanArchiveTarget } from './types.js';
|
|
38
39
|
|
|
40
|
+
const msg = useSaMessages('plans');
|
|
41
|
+
const common = useSaMessages('common');
|
|
42
|
+
|
|
39
43
|
defineProps<{
|
|
40
44
|
modelValue: boolean;
|
|
41
45
|
target: PlanArchiveTarget | null;
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<section class="sa-bundles">
|
|
3
3
|
<div class="sa-bundles__head">
|
|
4
|
-
<h2 class="sa-bundles__title">
|
|
4
|
+
<h2 class="sa-bundles__title">{{ msg.bundleOverview.title }}</h2>
|
|
5
5
|
<span class="sa-bundles__count">{{ bundles.length }}</span>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<div v-if="bundles.length === 0" class="sa-bundles__empty">
|
|
9
|
-
|
|
9
|
+
{{ msg.bundleOverview.empty }}
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
12
|
<div v-else class="sa-bundles__grid">
|
|
13
|
-
<q-card
|
|
13
|
+
<q-card
|
|
14
|
+
v-for="bundle in bundles"
|
|
15
|
+
:key="bundle.bundleKey"
|
|
16
|
+
flat
|
|
17
|
+
bordered
|
|
18
|
+
class="sa-bundle"
|
|
19
|
+
>
|
|
14
20
|
<q-card-section class="sa-bundle__header">
|
|
15
21
|
<div class="sa-bundle__label">{{ bundle.label ?? bundle.bundleKey }}</div>
|
|
16
22
|
<div class="sa-bundle__key">{{ bundle.bundleKey }}</div>
|
|
17
23
|
</q-card-section>
|
|
18
24
|
<q-separator />
|
|
19
25
|
<q-card-section>
|
|
20
|
-
<div class="sa-bundle__caption">
|
|
26
|
+
<div class="sa-bundle__caption">{{ msg.bundleOverview.contains }}</div>
|
|
21
27
|
<div class="sa-bundle__chips">
|
|
22
28
|
<q-chip
|
|
23
29
|
v-for="feature in bundle.features"
|
|
@@ -33,7 +39,7 @@
|
|
|
33
39
|
</div>
|
|
34
40
|
</q-card-section>
|
|
35
41
|
<q-card-section class="sa-bundle__compat">
|
|
36
|
-
<div class="sa-bundle__caption">
|
|
42
|
+
<div class="sa-bundle__caption">{{ msg.bundleOverview.compatibleWith }}</div>
|
|
37
43
|
<div class="sa-bundle__plans">{{ compatLabel(bundle) }}</div>
|
|
38
44
|
</q-card-section>
|
|
39
45
|
</q-card>
|
|
@@ -43,6 +49,7 @@
|
|
|
43
49
|
|
|
44
50
|
<script setup lang="ts">
|
|
45
51
|
import type { PlanRow } from '@saasicat/types';
|
|
52
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
46
53
|
|
|
47
54
|
interface BundleEntry {
|
|
48
55
|
bundleKey: string;
|
|
@@ -57,6 +64,8 @@ const props = defineProps<{
|
|
|
57
64
|
featureRegistry: Record<string, { label?: string; group?: string }>;
|
|
58
65
|
}>();
|
|
59
66
|
|
|
67
|
+
const msg = useSaMessages('plans');
|
|
68
|
+
|
|
60
69
|
function featureLabel(featureKey: string): string {
|
|
61
70
|
return props.featureRegistry[featureKey]?.label ?? featureKey;
|
|
62
71
|
}
|
|
@@ -68,7 +77,7 @@ function planLabel(planKey: string): string {
|
|
|
68
77
|
// Empty compatibility list = bundle applies to all plans (cf. PlanMatrix.hasBundle).
|
|
69
78
|
function compatLabel(bundle: BundleEntry): string {
|
|
70
79
|
const keys = bundle.compatiblePlanKeys ?? [];
|
|
71
|
-
if (keys.length === 0) return
|
|
80
|
+
if (keys.length === 0) return msg.value.bundleOverview.allPlans;
|
|
72
81
|
return keys.map(planLabel).join(', ');
|
|
73
82
|
}
|
|
74
83
|
</script>
|
|
@@ -6,21 +6,20 @@
|
|
|
6
6
|
>
|
|
7
7
|
<q-card style="min-width: 400px">
|
|
8
8
|
<q-card-section>
|
|
9
|
-
<div class="text-h6">
|
|
9
|
+
<div class="text-h6">{{ msg.discardDialog.title }}</div>
|
|
10
10
|
<div class="text-body2 q-mt-sm">
|
|
11
|
-
|
|
12
|
-
<code>{{ target?.plan.planKey }}</code>
|
|
13
|
-
Versions bleiben unverändert.
|
|
11
|
+
{{ bodyLead }}
|
|
12
|
+
<code>{{ target?.plan.planKey }}</code> {{ msg.discardDialog.bodyTail }}
|
|
14
13
|
</div>
|
|
15
14
|
</q-card-section>
|
|
16
15
|
<q-banner v-if="error" class="bg-warning q-mx-md q-mb-md" rounded>
|
|
17
16
|
{{ error }}
|
|
18
17
|
</q-banner>
|
|
19
18
|
<q-card-actions align="right">
|
|
20
|
-
<q-btn flat label="
|
|
19
|
+
<q-btn flat :label="common.cancel" @click="$emit('update:modelValue', false)" />
|
|
21
20
|
<q-btn
|
|
22
21
|
color="negative"
|
|
23
|
-
label="
|
|
22
|
+
:label="common.discard"
|
|
24
23
|
:loading="discarding"
|
|
25
24
|
@click="$emit('execute')"
|
|
26
25
|
/>
|
|
@@ -30,15 +29,27 @@
|
|
|
30
29
|
</template>
|
|
31
30
|
|
|
32
31
|
<script setup lang="ts">
|
|
32
|
+
import { computed } from 'vue';
|
|
33
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
34
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
33
35
|
import type { PlanDiscardTarget } from './types.js';
|
|
34
36
|
|
|
35
|
-
defineProps<{
|
|
37
|
+
const props = defineProps<{
|
|
36
38
|
modelValue: boolean;
|
|
37
39
|
target: PlanDiscardTarget | null;
|
|
38
40
|
error: string | null;
|
|
39
41
|
discarding: boolean;
|
|
40
42
|
}>();
|
|
41
43
|
|
|
44
|
+
const msg = useSaMessages('plans');
|
|
45
|
+
const common = useSaMessages('common');
|
|
46
|
+
|
|
47
|
+
const bodyLead = computed(() =>
|
|
48
|
+
formatMessage(msg.value.discardDialog.bodyLead, {
|
|
49
|
+
version: props.target?.draft.version ?? '',
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
|
|
42
53
|
defineEmits<{
|
|
43
54
|
(e: 'update:modelValue', value: boolean): void;
|
|
44
55
|
(e: 'execute'): void;
|
|
@@ -6,25 +6,24 @@
|
|
|
6
6
|
>
|
|
7
7
|
<q-card style="min-width: 420px">
|
|
8
8
|
<q-card-section>
|
|
9
|
-
<div class="text-h6">
|
|
9
|
+
<div class="text-h6">{{ msg.publishDialog.title }}</div>
|
|
10
10
|
<div class="text-body2 q-mt-sm">
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
{{ msg.publishDialog.bodyLead }} <code>{{ selectedPlan?.planKey }}</code
|
|
12
|
+
>{{ bodyTail }}
|
|
13
13
|
</div>
|
|
14
14
|
<div class="text-caption text-grey-7 q-mt-sm">
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
nächsten Renewal).
|
|
15
|
+
{{ msg.publishDialog.supersededNoteLead }} <code>supersededAt</code>
|
|
16
|
+
{{ msg.publishDialog.supersededNoteTail }}
|
|
18
17
|
</div>
|
|
19
18
|
<q-checkbox
|
|
20
19
|
:model-value="forceRegressive"
|
|
21
|
-
label="
|
|
20
|
+
:label="msg.publishDialog.forceRegressive"
|
|
22
21
|
class="q-mt-md"
|
|
23
22
|
@update:model-value="$emit('update:forceRegressive', Boolean($event))"
|
|
24
23
|
/>
|
|
25
24
|
<q-checkbox
|
|
26
25
|
:model-value="allowZeroPrice"
|
|
27
|
-
label="
|
|
26
|
+
:label="msg.publishDialog.allowZeroPrice"
|
|
28
27
|
class="q-mt-sm"
|
|
29
28
|
@update:model-value="$emit('update:allowZeroPrice', Boolean($event))"
|
|
30
29
|
/>
|
|
@@ -33,7 +32,7 @@
|
|
|
33
32
|
{{ publishError }}
|
|
34
33
|
</q-banner>
|
|
35
34
|
<q-card-section v-if="regressionChanges.length > 0" class="q-pt-none">
|
|
36
|
-
<div class="text-subtitle2 q-mb-xs">
|
|
35
|
+
<div class="text-subtitle2 q-mb-xs">{{ msg.publishDialog.regressiveChanges }}</div>
|
|
37
36
|
<ul class="sa-publish-regression">
|
|
38
37
|
<li v-for="c in regressionChanges" :key="c.field">
|
|
39
38
|
<strong>{{ fieldLabel(c.field) }}:</strong>
|
|
@@ -48,10 +47,10 @@
|
|
|
48
47
|
</ul>
|
|
49
48
|
</q-card-section>
|
|
50
49
|
<q-card-actions align="right">
|
|
51
|
-
<q-btn flat label="
|
|
50
|
+
<q-btn flat :label="common.cancel" @click="$emit('update:modelValue', false)" />
|
|
52
51
|
<q-btn
|
|
53
52
|
color="primary"
|
|
54
|
-
label="
|
|
53
|
+
:label="msg.publishDialog.confirm"
|
|
55
54
|
:loading="publishing"
|
|
56
55
|
@click="$emit('execute')"
|
|
57
56
|
/>
|
|
@@ -61,10 +60,13 @@
|
|
|
61
60
|
</template>
|
|
62
61
|
|
|
63
62
|
<script setup lang="ts">
|
|
63
|
+
import { computed } from 'vue';
|
|
64
64
|
import type { PlanRow, PlanVersionRow } from '@saasicat/types';
|
|
65
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
66
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
65
67
|
import type { RegressionChange } from './types.js';
|
|
66
68
|
|
|
67
|
-
defineProps<{
|
|
69
|
+
const props = defineProps<{
|
|
68
70
|
modelValue: boolean;
|
|
69
71
|
selectedPlan: PlanRow | null;
|
|
70
72
|
publishTarget: PlanVersionRow | null;
|
|
@@ -77,6 +79,15 @@ defineProps<{
|
|
|
77
79
|
formatChangeValue: (value: unknown) => string;
|
|
78
80
|
}>();
|
|
79
81
|
|
|
82
|
+
const msg = useSaMessages('plans');
|
|
83
|
+
const common = useSaMessages('common');
|
|
84
|
+
|
|
85
|
+
const bodyTail = computed(() =>
|
|
86
|
+
formatMessage(msg.value.publishDialog.bodyTail, {
|
|
87
|
+
version: props.publishTarget?.version ?? '',
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
|
|
80
91
|
defineEmits<{
|
|
81
92
|
(e: 'update:modelValue', value: boolean): void;
|
|
82
93
|
(e: 'update:forceRegressive', value: boolean): void;
|
|
@@ -28,9 +28,10 @@ export function planAccent(
|
|
|
28
28
|
return accents[planId] ?? '#3f6bff';
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
/** `locale` is a BCP-47 tag — pages pass `intlLocale.value` of the active UI locale. */
|
|
32
|
+
export function formatDate(iso: string | null | undefined, locale: string): string {
|
|
32
33
|
if (!iso) return '—';
|
|
33
|
-
return new Date(iso).toLocaleDateString(
|
|
34
|
+
return new Date(iso).toLocaleDateString(locale, {
|
|
34
35
|
day: '2-digit',
|
|
35
36
|
month: 'short',
|
|
36
37
|
year: 'numeric',
|