@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,16 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-if="!changes || changes.length === 0" class="diff-
|
|
3
|
-
|
|
2
|
+
<div v-if="!changes || changes.length === 0" class="sa-diff-preview__empty">
|
|
3
|
+
{{ msg.diffPreview.empty }}
|
|
4
4
|
</div>
|
|
5
|
-
<div v-else class="diff-
|
|
6
|
-
<div
|
|
7
|
-
|
|
5
|
+
<div v-else class="sa-diff-preview__list">
|
|
6
|
+
<div
|
|
7
|
+
v-for="(c, idx) in changes"
|
|
8
|
+
:key="idx"
|
|
9
|
+
class="sa-diff-preview__row"
|
|
10
|
+
:class="rowClass(c.direction)"
|
|
11
|
+
>
|
|
12
|
+
<div class="sa-diff-preview__direction">
|
|
8
13
|
<q-icon :name="iconFor(c.direction)" size="16px" />
|
|
9
14
|
<span>{{ labelFor(c.direction) }}</span>
|
|
10
15
|
</div>
|
|
11
|
-
<div class="diff-
|
|
12
|
-
<div class="diff-
|
|
13
|
-
<span
|
|
16
|
+
<div class="sa-diff-preview__field">{{ humanFieldLabel(c.field) }}</div>
|
|
17
|
+
<div class="sa-diff-preview__values">
|
|
18
|
+
<span v-if="c.field !== 'features.added'" class="old">{{
|
|
14
19
|
formatValue(c.oldValue)
|
|
15
20
|
}}</span>
|
|
16
21
|
<q-icon
|
|
@@ -18,7 +23,7 @@
|
|
|
18
23
|
name="arrow_forward"
|
|
19
24
|
size="14px"
|
|
20
25
|
/>
|
|
21
|
-
<span
|
|
26
|
+
<span v-if="c.field !== 'features.removed'" class="new">{{
|
|
22
27
|
formatValue(c.newValue)
|
|
23
28
|
}}</span>
|
|
24
29
|
</div>
|
|
@@ -27,27 +32,43 @@
|
|
|
27
32
|
</template>
|
|
28
33
|
|
|
29
34
|
<script setup lang="ts">
|
|
35
|
+
import { computed } from 'vue';
|
|
30
36
|
import type { VersionChange } from '@saasicat/types';
|
|
37
|
+
import { formatCurrency } from '../client/i18n/currency.js';
|
|
38
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
31
39
|
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
const PLATFORM_FIELD_LABELS: Readonly<Record<string, string>> = Object.freeze({
|
|
36
|
-
'features.added': 'Hinzugefügte Features',
|
|
37
|
-
'features.removed': 'Entfernte Features',
|
|
38
|
-
maxUsers: 'Max. Benutzer',
|
|
39
|
-
maxStorageGb: 'Speicher (GB)',
|
|
40
|
-
monthlyNet: 'Preis monatlich (netto)',
|
|
41
|
-
yearlyNet: 'Preis jährlich (netto)',
|
|
42
|
-
unitSize: 'Einheitsgröße',
|
|
43
|
-
});
|
|
40
|
+
// Renders a plan-version diff as one row per changed field. Used by the
|
|
41
|
+
// plan-versions page and available to consumer apps through the
|
|
42
|
+
// `./components/*` subpath export.
|
|
44
43
|
|
|
45
44
|
const props = defineProps<{
|
|
46
45
|
changes?: VersionChange[] | null;
|
|
47
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Optional: field labels (e.g. `{ maxUsers: 'Max. Benutzer' }`).
|
|
48
|
+
* Apps map their own quota fields here; the default covers the generic
|
|
49
|
+
* platform fields.
|
|
50
|
+
*/
|
|
48
51
|
fieldLabels?: Record<string, string>;
|
|
49
52
|
}>();
|
|
50
53
|
|
|
54
|
+
const msg = useSaMessages('planVersions');
|
|
55
|
+
const { locale } = useSuperAdminI18n();
|
|
56
|
+
|
|
57
|
+
const defaultFieldLabels = computed<Record<string, string>>(() => ({
|
|
58
|
+
'features.added': msg.value.diffFields.featuresAdded,
|
|
59
|
+
'features.removed': msg.value.diffFields.featuresRemoved,
|
|
60
|
+
maxUsers: msg.value.diffFields.maxUsers,
|
|
61
|
+
maxStorageGb: msg.value.diffFields.maxStorageGb,
|
|
62
|
+
monthlyNet: msg.value.diffFields.monthlyNet,
|
|
63
|
+
yearlyNet: msg.value.diffFields.yearlyNet,
|
|
64
|
+
unitSize: msg.value.diffFields.unitSize,
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
const mergedFieldLabels = computed(() => ({
|
|
68
|
+
...defaultFieldLabels.value,
|
|
69
|
+
...(props.fieldLabels ?? {}),
|
|
70
|
+
}));
|
|
71
|
+
|
|
51
72
|
function iconFor(direction: VersionChange['direction']): string {
|
|
52
73
|
if (direction === 'IMPROVEMENT') return 'trending_up';
|
|
53
74
|
if (direction === 'REGRESSION') return 'trending_down';
|
|
@@ -55,36 +76,39 @@ function iconFor(direction: VersionChange['direction']): string {
|
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
function labelFor(direction: VersionChange['direction']): string {
|
|
58
|
-
if (direction === 'IMPROVEMENT') return
|
|
59
|
-
if (direction === 'REGRESSION') return
|
|
60
|
-
return
|
|
79
|
+
if (direction === 'IMPROVEMENT') return msg.value.diffPreview.improvement;
|
|
80
|
+
if (direction === 'REGRESSION') return msg.value.diffPreview.regression;
|
|
81
|
+
return msg.value.diffPreview.neutral;
|
|
61
82
|
}
|
|
62
83
|
|
|
63
84
|
function rowClass(direction: VersionChange['direction']): string {
|
|
64
|
-
if (direction === 'IMPROVEMENT') return 'diff-
|
|
65
|
-
if (direction === 'REGRESSION') return 'diff-
|
|
66
|
-
return 'diff-
|
|
85
|
+
if (direction === 'IMPROVEMENT') return 'sa-diff-preview__row--improvement';
|
|
86
|
+
if (direction === 'REGRESSION') return 'sa-diff-preview__row--regression';
|
|
87
|
+
return 'sa-diff-preview__row--neutral';
|
|
67
88
|
}
|
|
68
89
|
|
|
69
90
|
function humanFieldLabel(field: string): string {
|
|
70
|
-
return
|
|
91
|
+
return mergedFieldLabels.value[field] ?? field;
|
|
71
92
|
}
|
|
72
93
|
|
|
94
|
+
/** Decimal strings the API sends for money fields (`"29.90"`). */
|
|
95
|
+
const MONEY_STRING = /^\d+\.\d{2}$/;
|
|
96
|
+
|
|
73
97
|
function formatValue(value: unknown): string {
|
|
74
98
|
if (value === null || value === undefined) return '–';
|
|
75
99
|
if (Array.isArray(value)) {
|
|
76
100
|
if (value.length === 0) return '–';
|
|
77
101
|
return value.join(', ');
|
|
78
102
|
}
|
|
79
|
-
if (typeof value === 'string' &&
|
|
80
|
-
return
|
|
103
|
+
if (typeof value === 'string' && MONEY_STRING.test(value)) {
|
|
104
|
+
return formatCurrency(value, locale.value);
|
|
81
105
|
}
|
|
82
106
|
return String(value);
|
|
83
107
|
}
|
|
84
108
|
</script>
|
|
85
109
|
|
|
86
110
|
<style scoped>
|
|
87
|
-
.diff-
|
|
111
|
+
.sa-diff-preview__empty {
|
|
88
112
|
color: #64748b;
|
|
89
113
|
font-style: italic;
|
|
90
114
|
padding: 12px;
|
|
@@ -92,12 +116,12 @@ function formatValue(value: unknown): string {
|
|
|
92
116
|
border-radius: 8px;
|
|
93
117
|
border: 1px dashed #cbd5e1;
|
|
94
118
|
}
|
|
95
|
-
.diff-
|
|
119
|
+
.sa-diff-preview__list {
|
|
96
120
|
display: flex;
|
|
97
121
|
flex-direction: column;
|
|
98
122
|
gap: 6px;
|
|
99
123
|
}
|
|
100
|
-
.diff-
|
|
124
|
+
.sa-diff-preview__row {
|
|
101
125
|
display: grid;
|
|
102
126
|
grid-template-columns: 160px 200px 1fr;
|
|
103
127
|
gap: 12px;
|
|
@@ -107,22 +131,22 @@ function formatValue(value: unknown): string {
|
|
|
107
131
|
font-size: 13px;
|
|
108
132
|
align-items: center;
|
|
109
133
|
}
|
|
110
|
-
.diff-
|
|
134
|
+
.sa-diff-preview__row--improvement {
|
|
111
135
|
background: #f0fdf4;
|
|
112
136
|
border-color: #bbf7d0;
|
|
113
137
|
color: #166534;
|
|
114
138
|
}
|
|
115
|
-
.diff-
|
|
139
|
+
.sa-diff-preview__row--regression {
|
|
116
140
|
background: #fef2f2;
|
|
117
141
|
border-color: #fecaca;
|
|
118
142
|
color: #991b1b;
|
|
119
143
|
}
|
|
120
|
-
.diff-
|
|
144
|
+
.sa-diff-preview__row--neutral {
|
|
121
145
|
background: #f8fafc;
|
|
122
146
|
border-color: #e2e8f0;
|
|
123
147
|
color: #475569;
|
|
124
148
|
}
|
|
125
|
-
.diff-
|
|
149
|
+
.sa-diff-preview__direction {
|
|
126
150
|
display: flex;
|
|
127
151
|
align-items: center;
|
|
128
152
|
gap: 6px;
|
|
@@ -131,20 +155,25 @@ function formatValue(value: unknown): string {
|
|
|
131
155
|
text-transform: uppercase;
|
|
132
156
|
letter-spacing: 0.04em;
|
|
133
157
|
}
|
|
134
|
-
.diff-
|
|
158
|
+
.sa-diff-preview__field {
|
|
135
159
|
color: #0f172a;
|
|
136
160
|
font-weight: 500;
|
|
137
161
|
}
|
|
138
|
-
.diff-
|
|
162
|
+
.sa-diff-preview__values {
|
|
139
163
|
display: flex;
|
|
140
164
|
align-items: center;
|
|
141
165
|
gap: 8px;
|
|
142
166
|
}
|
|
143
|
-
.diff-
|
|
167
|
+
.sa-diff-preview__values .old {
|
|
168
|
+
color: #94a3b8;
|
|
144
169
|
text-decoration: line-through;
|
|
145
|
-
|
|
170
|
+
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
171
|
+
font-size: 12px;
|
|
146
172
|
}
|
|
147
|
-
.diff-
|
|
173
|
+
.sa-diff-preview__values .new {
|
|
174
|
+
color: inherit;
|
|
148
175
|
font-weight: 600;
|
|
176
|
+
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
177
|
+
font-size: 12px;
|
|
149
178
|
}
|
|
150
179
|
</style>
|
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
<section class="bcp">
|
|
3
3
|
<div class="bcp-head">
|
|
4
4
|
<div>
|
|
5
|
-
<div class="bcp-title">
|
|
6
|
-
<div class="bcp-sub">
|
|
7
|
-
Features & Quotas zu einem Add-On bündeln — bepreist und kompatibel mit
|
|
8
|
-
ausgewählten Plänen.
|
|
9
|
-
</div>
|
|
5
|
+
<div class="bcp-title">{{ msg.create.title }}</div>
|
|
6
|
+
<div class="bcp-sub">{{ msg.create.subtitle }}</div>
|
|
10
7
|
</div>
|
|
11
|
-
<button class="bcp-close" type="button" aria-label="
|
|
8
|
+
<button class="bcp-close" type="button" :aria-label="common.close" @click="close">
|
|
12
9
|
<svg
|
|
13
10
|
width="14"
|
|
14
11
|
height="14"
|
|
@@ -28,26 +25,24 @@
|
|
|
28
25
|
<div class="bcp-section-head">
|
|
29
26
|
<span class="bcp-section-num">1</span>
|
|
30
27
|
<div>
|
|
31
|
-
<div class="bcp-section-title">
|
|
32
|
-
<div class="bcp-section-sub">
|
|
28
|
+
<div class="bcp-section-title">{{ msg.fields.masterData }}</div>
|
|
29
|
+
<div class="bcp-section-sub">{{ msg.create.masterDataHint }}</div>
|
|
33
30
|
</div>
|
|
34
31
|
</div>
|
|
35
32
|
<div class="bcp-grid">
|
|
36
33
|
<label class="bcp-field bcp-col-2">
|
|
37
|
-
<span class="bcp-field-label">
|
|
34
|
+
<span class="bcp-field-label">{{ msg.fields.label }}</span>
|
|
38
35
|
<input
|
|
39
36
|
ref="labelInput"
|
|
40
37
|
v-model="form.label"
|
|
41
38
|
class="bcp-input"
|
|
42
|
-
placeholder="
|
|
39
|
+
:placeholder="msg.create.labelPlaceholder"
|
|
43
40
|
/>
|
|
44
41
|
</label>
|
|
45
42
|
<label class="bcp-field bcp-col-2">
|
|
46
43
|
<span class="bcp-field-label">
|
|
47
|
-
|
|
48
|
-
<span class="bcp-field-hint">
|
|
49
|
-
API-stabil · wird aus dem Label erzeugt
|
|
50
|
-
</span>
|
|
44
|
+
{{ msg.create.bundleKey }}
|
|
45
|
+
<span class="bcp-field-hint">{{ msg.create.bundleKeyHint }}</span>
|
|
51
46
|
</span>
|
|
52
47
|
<input
|
|
53
48
|
v-model="form.bundleKey"
|
|
@@ -60,12 +55,12 @@
|
|
|
60
55
|
</span>
|
|
61
56
|
</label>
|
|
62
57
|
<label class="bcp-field bcp-col-2">
|
|
63
|
-
<span class="bcp-field-label">
|
|
58
|
+
<span class="bcp-field-label">{{ common.description }}</span>
|
|
64
59
|
<textarea
|
|
65
60
|
v-model="form.description"
|
|
66
61
|
rows="2"
|
|
67
62
|
class="bcp-input bcp-textarea"
|
|
68
|
-
placeholder="
|
|
63
|
+
:placeholder="msg.create.descriptionPlaceholder"
|
|
69
64
|
/>
|
|
70
65
|
</label>
|
|
71
66
|
</div>
|
|
@@ -76,15 +71,13 @@
|
|
|
76
71
|
<div class="bcp-section-head">
|
|
77
72
|
<span class="bcp-section-num">2</span>
|
|
78
73
|
<div>
|
|
79
|
-
<div class="bcp-section-title">
|
|
80
|
-
<div class="bcp-section-sub">
|
|
81
|
-
Monats- & Jahrespreis sowie Datum, ab dem das Bundle verkaufbar wird.
|
|
82
|
-
</div>
|
|
74
|
+
<div class="bcp-section-title">{{ msg.create.sectionPricing }}</div>
|
|
75
|
+
<div class="bcp-section-sub">{{ msg.create.pricingHint }}</div>
|
|
83
76
|
</div>
|
|
84
77
|
</div>
|
|
85
78
|
<div class="bcp-grid">
|
|
86
79
|
<label class="bcp-field">
|
|
87
|
-
<span class="bcp-field-label">
|
|
80
|
+
<span class="bcp-field-label">{{ msg.fields.monthlyPrice }}</span>
|
|
88
81
|
<div class="bcp-input-grp">
|
|
89
82
|
<input
|
|
90
83
|
v-model="form.monthlyNet"
|
|
@@ -93,11 +86,11 @@
|
|
|
93
86
|
class="bcp-input"
|
|
94
87
|
placeholder="9.90"
|
|
95
88
|
/>
|
|
96
|
-
<span class="bcp-input-unit"
|
|
89
|
+
<span class="bcp-input-unit">{{ msg.fields.perMonthUnit }}</span>
|
|
97
90
|
</div>
|
|
98
91
|
</label>
|
|
99
92
|
<label class="bcp-field">
|
|
100
|
-
<span class="bcp-field-label">
|
|
93
|
+
<span class="bcp-field-label">{{ msg.fields.yearlyPrice }}</span>
|
|
101
94
|
<div class="bcp-input-grp">
|
|
102
95
|
<input
|
|
103
96
|
v-model="form.yearlyNet"
|
|
@@ -106,23 +99,13 @@
|
|
|
106
99
|
class="bcp-input"
|
|
107
100
|
placeholder="99.00"
|
|
108
101
|
/>
|
|
109
|
-
<span class="bcp-input-unit"
|
|
102
|
+
<span class="bcp-input-unit">{{ msg.fields.perYearUnit }}</span>
|
|
110
103
|
</div>
|
|
111
104
|
</label>
|
|
112
105
|
<label class="bcp-field bcp-col-2">
|
|
113
|
-
<span class="bcp-field-label">
|
|
106
|
+
<span class="bcp-field-label">{{ msg.fields.validFrom }}</span>
|
|
114
107
|
<input v-model="form.validFrom" type="date" class="bcp-input" />
|
|
115
|
-
<span class="bcp-field-hint">
|
|
116
|
-
<template v-if="form.validFrom && form.validFrom <= todayIso">
|
|
117
|
-
✓ Bundle ist nach Anlage sofort live und verkaufbar.
|
|
118
|
-
</template>
|
|
119
|
-
<template v-else-if="form.validFrom">
|
|
120
|
-
⏳ Geplant — wird ab {{ formatDateDE(form.validFrom) }} verkaufbar.
|
|
121
|
-
</template>
|
|
122
|
-
<template v-else>
|
|
123
|
-
Pflicht beim Publish — kann auch nach Anlage gesetzt werden.
|
|
124
|
-
</template>
|
|
125
|
-
</span>
|
|
108
|
+
<span class="bcp-field-hint">{{ validFromHint }}</span>
|
|
126
109
|
</label>
|
|
127
110
|
</div>
|
|
128
111
|
</section>
|
|
@@ -132,11 +115,8 @@
|
|
|
132
115
|
<div class="bcp-section-head">
|
|
133
116
|
<span class="bcp-section-num">3</span>
|
|
134
117
|
<div>
|
|
135
|
-
<div class="bcp-section-title">
|
|
136
|
-
<div class="bcp-section-sub">
|
|
137
|
-
Mit welchen Plänen darf dieses Bundle als Add-On gebucht werden?
|
|
138
|
-
Überschneidungen werden direkt markiert.
|
|
139
|
-
</div>
|
|
118
|
+
<div class="bcp-section-title">{{ msg.fields.planCompat }}</div>
|
|
119
|
+
<div class="bcp-section-sub">{{ msg.create.planCompatHint }}</div>
|
|
140
120
|
</div>
|
|
141
121
|
</div>
|
|
142
122
|
<BundlePlanCompatPicker
|
|
@@ -157,10 +137,8 @@
|
|
|
157
137
|
<div class="bcp-section-head">
|
|
158
138
|
<span class="bcp-section-num">4</span>
|
|
159
139
|
<div>
|
|
160
|
-
<div class="bcp-section-title">
|
|
161
|
-
<div class="bcp-section-sub">
|
|
162
|
-
{{ form.features.length }} von {{ availableFeatures.length }} ausgewählt
|
|
163
|
-
</div>
|
|
140
|
+
<div class="bcp-section-title">{{ msg.create.sectionFeatures }}</div>
|
|
141
|
+
<div class="bcp-section-sub">{{ featureSelectionText }}</div>
|
|
164
142
|
</div>
|
|
165
143
|
</div>
|
|
166
144
|
<BundleFeaturesEditor
|
|
@@ -178,12 +156,12 @@
|
|
|
178
156
|
<span class="bcp-section-num">5</span>
|
|
179
157
|
<div>
|
|
180
158
|
<div class="bcp-section-title">
|
|
181
|
-
|
|
182
|
-
<span class="bcp-field-hint" style="margin-left: 6px">
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
Quotas, die das Bundle on top des Plans setzt.
|
|
159
|
+
{{ msg.fields.quotas }}
|
|
160
|
+
<span class="bcp-field-hint" style="margin-left: 6px">
|
|
161
|
+
{{ msg.create.quotasOptional }}
|
|
162
|
+
</span>
|
|
186
163
|
</div>
|
|
164
|
+
<div class="bcp-section-sub">{{ msg.create.quotasHint }}</div>
|
|
187
165
|
</div>
|
|
188
166
|
</div>
|
|
189
167
|
<BundleQuotasEditor
|
|
@@ -201,10 +179,7 @@
|
|
|
201
179
|
|
|
202
180
|
<div class="bcp-foot">
|
|
203
181
|
<span v-if="overlapPlansCount > 0" class="bcp-foot-hint bcp-foot-hint--warn">
|
|
204
|
-
|
|
205
|
-
overlapPlansCount === 1 ? '' : 'en'
|
|
206
|
-
}}
|
|
207
|
-
— bitte prüfen
|
|
182
|
+
{{ overlapWarningText }}
|
|
208
183
|
</span>
|
|
209
184
|
<span v-else-if="form.features.length > 0" class="bcp-foot-hint">
|
|
210
185
|
<svg
|
|
@@ -218,25 +193,33 @@
|
|
|
218
193
|
<path d="M5 13l4 4L19 7" />
|
|
219
194
|
</svg>
|
|
220
195
|
<span>
|
|
221
|
-
<b>{{ form.features.length }}</b>
|
|
222
|
-
|
|
196
|
+
<b>{{ form.features.length }}</b>
|
|
197
|
+
{{
|
|
198
|
+
form.features.length === 1
|
|
199
|
+
? msg.create.summaryFeatureOne
|
|
200
|
+
: msg.create.summaryFeatureMany
|
|
223
201
|
}}
|
|
224
202
|
<template v-if="quotaCount > 0">
|
|
225
|
-
· <b>{{ quotaCount }}</b>
|
|
203
|
+
· <b>{{ quotaCount }}</b>
|
|
204
|
+
{{
|
|
205
|
+
quotaCount === 1
|
|
206
|
+
? msg.create.summaryQuotaOne
|
|
207
|
+
: msg.create.summaryQuotaMany
|
|
208
|
+
}}
|
|
226
209
|
</template>
|
|
227
210
|
<template v-if="form.planIds.length > 0">
|
|
228
|
-
· <b>{{ form.planIds.length }}</b>
|
|
211
|
+
· <b>{{ form.planIds.length }}</b> {{ msg.create.summaryPlanCompat }}
|
|
229
212
|
</template>
|
|
230
213
|
</span>
|
|
231
214
|
</span>
|
|
232
|
-
<button class="bcp-btn" type="button" @click="close">
|
|
215
|
+
<button class="bcp-btn" type="button" @click="close">{{ common.cancel }}</button>
|
|
233
216
|
<button
|
|
234
217
|
class="bcp-btn bcp-btn--primary"
|
|
235
218
|
type="button"
|
|
236
219
|
:disabled="!canSubmit || submitting"
|
|
237
220
|
@click="submit"
|
|
238
221
|
>
|
|
239
|
-
{{ submitting ?
|
|
222
|
+
{{ submitting ? msg.create.submitting : msg.create.submit }}
|
|
240
223
|
</button>
|
|
241
224
|
</div>
|
|
242
225
|
</section>
|
|
@@ -259,7 +242,9 @@ import BundleFeaturesEditor, { type FeatureMeta } from './BundleFeaturesEditor.v
|
|
|
259
242
|
import BundlePlanCompatPicker from './BundlePlanCompatPicker.vue';
|
|
260
243
|
import BundleQuotasEditor from './BundleQuotasEditor.vue';
|
|
261
244
|
import type { QuotaMeta } from './catalog-i18n.js';
|
|
262
|
-
import { findBundlePlanOverlap,
|
|
245
|
+
import { findBundlePlanOverlap, formatDate } from './bundle-version-status';
|
|
246
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
247
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
263
248
|
|
|
264
249
|
// BundleCreatePanel — expandable inline panel for creating a new
|
|
265
250
|
// bundle (root + v1 draft) at the top of the bundle list. Uses the same
|
|
@@ -306,8 +291,20 @@ const emit = defineEmits<{
|
|
|
306
291
|
(e: 'created', bundle: BundleRow): void;
|
|
307
292
|
}>();
|
|
308
293
|
|
|
294
|
+
const msg = useSaMessages('bundles');
|
|
295
|
+
const common = useSaMessages('common');
|
|
296
|
+
const { locale } = useSuperAdminI18n();
|
|
297
|
+
|
|
309
298
|
const todayIso = computed(() => new Date().toISOString().slice(0, 10));
|
|
310
299
|
|
|
300
|
+
const validFromHint = computed(() => {
|
|
301
|
+
if (!form.validFrom) return msg.value.create.validFromHint;
|
|
302
|
+
if (form.validFrom <= todayIso.value) return msg.value.create.validFromImmediate;
|
|
303
|
+
return formatMessage(msg.value.create.validFromScheduled, {
|
|
304
|
+
date: formatDate(form.validFrom, locale.value),
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
311
308
|
const BUNDLE_KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
312
309
|
const PRICE_RE = /^\d+(\.\d{1,2})?$/;
|
|
313
310
|
|
|
@@ -362,14 +359,21 @@ function onBundleKeyInput(event: Event): void {
|
|
|
362
359
|
const bundleKeyError = computed<string | null>(() => {
|
|
363
360
|
if (!form.bundleKey) return null;
|
|
364
361
|
if (!BUNDLE_KEY_PATTERN.test(form.bundleKey)) {
|
|
365
|
-
return
|
|
362
|
+
return msg.value.create.errorKeyFormat;
|
|
366
363
|
}
|
|
367
364
|
if (props.existingBundleKeys.includes(form.bundleKey)) {
|
|
368
|
-
return
|
|
365
|
+
return msg.value.create.errorKeyExists;
|
|
369
366
|
}
|
|
370
367
|
return null;
|
|
371
368
|
});
|
|
372
369
|
|
|
370
|
+
const featureSelectionText = computed(() =>
|
|
371
|
+
formatMessage(msg.value.create.selectedOfTotal, {
|
|
372
|
+
selected: form.features.length,
|
|
373
|
+
total: props.availableFeatures.length,
|
|
374
|
+
}),
|
|
375
|
+
);
|
|
376
|
+
|
|
373
377
|
const canSubmit = computed(() => {
|
|
374
378
|
if (!form.label.trim()) return false;
|
|
375
379
|
if (!form.bundleKey) return false;
|
|
@@ -409,6 +413,15 @@ const overlapPlansCount = computed(
|
|
|
409
413
|
).length,
|
|
410
414
|
);
|
|
411
415
|
|
|
416
|
+
const overlapWarningText = computed(() =>
|
|
417
|
+
formatMessage(
|
|
418
|
+
overlapPlansCount.value === 1
|
|
419
|
+
? msg.value.create.overlapWarningOne
|
|
420
|
+
: msg.value.create.overlapWarningMany,
|
|
421
|
+
{ count: overlapPlansCount.value },
|
|
422
|
+
),
|
|
423
|
+
);
|
|
424
|
+
|
|
412
425
|
const quotaCount = computed(() => Object.keys(form.quotas).length);
|
|
413
426
|
|
|
414
427
|
// ── Event handlers ────────────────────────────────────────
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
</div>
|
|
49
49
|
</template>
|
|
50
50
|
<div v-if="groupedFeatures.length === 0" class="bd-features-empty">
|
|
51
|
-
|
|
51
|
+
{{ msg.featuresEditor.empty }}
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
</template>
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
<script setup lang="ts">
|
|
57
57
|
import { computed } from 'vue';
|
|
58
58
|
import type { DiscoveredFeature } from '@saasicat/types';
|
|
59
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
59
60
|
|
|
60
61
|
// BundleFeaturesEditor — grouped pills for the feature selection of a
|
|
61
62
|
// bundle version (after plan simulation). The source of truth for the
|
|
@@ -88,6 +89,10 @@ const props = defineProps<{
|
|
|
88
89
|
overlapKeys?: string[];
|
|
89
90
|
}>();
|
|
90
91
|
|
|
92
|
+
const msg = useSaMessages('bundles');
|
|
93
|
+
const common = useSaMessages('common');
|
|
94
|
+
const { intlLocale } = useSuperAdminI18n();
|
|
95
|
+
|
|
91
96
|
const emit = defineEmits<{
|
|
92
97
|
(e: 'toggle', featureKey: string): void;
|
|
93
98
|
}>();
|
|
@@ -97,15 +102,17 @@ function featureLabel(key: string): string {
|
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
function featureGroup(key: string): string {
|
|
100
|
-
return props.featureRegistry?.[key]?.group ??
|
|
105
|
+
return props.featureRegistry?.[key]?.group ?? common.value.general;
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
function pillTitle(key: string): string {
|
|
104
|
-
if (props.locked) return
|
|
109
|
+
if (props.locked) return msg.value.compatPicker.lockedTooltip;
|
|
105
110
|
if (props.overlapKeys?.includes(key)) {
|
|
106
|
-
return
|
|
111
|
+
return msg.value.featuresEditor.overlapTooltip;
|
|
107
112
|
}
|
|
108
|
-
return props.features.includes(key)
|
|
113
|
+
return props.features.includes(key)
|
|
114
|
+
? msg.value.featuresEditor.removeTooltip
|
|
115
|
+
: msg.value.featuresEditor.addTooltip;
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
const overlapKeys = computed(() => props.overlapKeys ?? []);
|
|
@@ -119,7 +126,7 @@ const groupedFeatures = computed(() => {
|
|
|
119
126
|
byGroup.set(g, list);
|
|
120
127
|
}
|
|
121
128
|
return [...byGroup.entries()]
|
|
122
|
-
.sort(([a], [b]) => a.localeCompare(b,
|
|
129
|
+
.sort(([a], [b]) => a.localeCompare(b, intlLocale.value))
|
|
123
130
|
.map(([key, rows]) => ({ key, label: key, rows }));
|
|
124
131
|
});
|
|
125
132
|
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="bv-compat">
|
|
3
|
-
<div class="bv-compat-hint">
|
|
4
|
-
Pläne, mit denen dieses Bundle als Add-On gebucht werden kann. Features/Quotas, die der
|
|
5
|
-
Plan bereits enthält, werden als Überschneidung markiert (Doppel-Berechnung).
|
|
6
|
-
</div>
|
|
3
|
+
<div class="bv-compat-hint">{{ msg.compatPicker.hint }}</div>
|
|
7
4
|
<div class="bv-compat-grid">
|
|
8
5
|
<button
|
|
9
6
|
v-for="entry in entries"
|
|
@@ -17,10 +14,10 @@
|
|
|
17
14
|
:disabled="locked"
|
|
18
15
|
:title="
|
|
19
16
|
locked
|
|
20
|
-
?
|
|
17
|
+
? msg.compatPicker.lockedTooltip
|
|
21
18
|
: selectedKeys.includes(entry.plan.planKey)
|
|
22
|
-
?
|
|
23
|
-
:
|
|
19
|
+
? msg.compatPicker.removeTooltip
|
|
20
|
+
: msg.compatPicker.addTooltip
|
|
24
21
|
"
|
|
25
22
|
@click="onToggle(entry.plan.planKey)"
|
|
26
23
|
>
|
|
@@ -48,9 +45,11 @@
|
|
|
48
45
|
</span>
|
|
49
46
|
</div>
|
|
50
47
|
<div v-if="showOverlap(entry)" class="bv-compat-overlap">
|
|
51
|
-
<div class="bv-compat-overlap-head"
|
|
48
|
+
<div class="bv-compat-overlap-head">{{ msg.compatPicker.overlapHead }}</div>
|
|
52
49
|
<div v-if="entry.overlap.features.length > 0" class="bv-compat-overlap-list">
|
|
53
|
-
<span class="bv-compat-overlap-kind">
|
|
50
|
+
<span class="bv-compat-overlap-kind">
|
|
51
|
+
{{ msg.compatPicker.overlapFeatures }}
|
|
52
|
+
</span>
|
|
54
53
|
<span
|
|
55
54
|
v-for="fk in entry.overlap.features"
|
|
56
55
|
:key="fk"
|
|
@@ -60,7 +59,9 @@
|
|
|
60
59
|
</span>
|
|
61
60
|
</div>
|
|
62
61
|
<div v-if="entry.overlap.quotas.length > 0" class="bv-compat-overlap-list">
|
|
63
|
-
<span class="bv-compat-overlap-kind">
|
|
62
|
+
<span class="bv-compat-overlap-kind">
|
|
63
|
+
{{ msg.compatPicker.overlapQuotas }}
|
|
64
|
+
</span>
|
|
64
65
|
<span
|
|
65
66
|
v-for="qk in entry.overlap.quotas"
|
|
66
67
|
:key="qk"
|
|
@@ -72,15 +73,16 @@
|
|
|
72
73
|
</div>
|
|
73
74
|
</button>
|
|
74
75
|
<div v-if="plans.length === 0" class="bv-compat-empty">
|
|
75
|
-
|
|
76
|
+
{{ msg.compatPicker.empty }}
|
|
76
77
|
</div>
|
|
77
78
|
</div>
|
|
78
79
|
<div v-if="overlapCount > 0" class="bv-compat-summary">
|
|
79
80
|
<span class="bv-compat-summary-ico">⚠</span>
|
|
80
81
|
<span>
|
|
81
|
-
<b>{{ overlapCount }}</b>
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
<b>{{ overlapCount }}</b>
|
|
83
|
+
{{
|
|
84
|
+
overlapCount === 1 ? msg.compatPicker.summaryOne : msg.compatPicker.summaryMany
|
|
85
|
+
}}
|
|
84
86
|
</span>
|
|
85
87
|
</div>
|
|
86
88
|
</div>
|
|
@@ -94,6 +96,7 @@ import { findBundlePlanOverlap, type BundlePlanOverlap } from './bundle-version-
|
|
|
94
96
|
|
|
95
97
|
import type { FeatureMeta } from './BundleFeaturesEditor.vue';
|
|
96
98
|
import type { QuotaMeta } from './catalog-i18n.js';
|
|
99
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
97
100
|
|
|
98
101
|
// BundlePlanCompatPicker — multi-select of the plans this bundle may be
|
|
99
102
|
// booked with as an add-on (plan-bundle visibility, after plan
|
|
@@ -132,6 +135,8 @@ const emit = defineEmits<{
|
|
|
132
135
|
(e: 'toggle', planKey: string): void;
|
|
133
136
|
}>();
|
|
134
137
|
|
|
138
|
+
const msg = useSaMessages('bundles');
|
|
139
|
+
|
|
135
140
|
const entries = computed<CompatEntry[]>(() =>
|
|
136
141
|
props.plans.map((plan) => ({
|
|
137
142
|
plan,
|