@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
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
:disabled="locked"
|
|
16
16
|
:title="
|
|
17
17
|
locked
|
|
18
|
-
?
|
|
18
|
+
? msg.compatPicker.lockedTooltip
|
|
19
19
|
: q.quotaKey in quotas
|
|
20
|
-
?
|
|
21
|
-
:
|
|
20
|
+
? msg.quotasEditor.removeTooltip
|
|
21
|
+
: msg.quotasEditor.addTooltip
|
|
22
22
|
"
|
|
23
23
|
@click="$emit('toggle', q.quotaKey)"
|
|
24
24
|
>
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
</div>
|
|
71
71
|
</div>
|
|
72
72
|
<div v-if="availableQuotas.length === 0" class="bd-quotas-empty">
|
|
73
|
-
|
|
73
|
+
{{ msg.quotasEditor.empty }}
|
|
74
74
|
</div>
|
|
75
75
|
</div>
|
|
76
76
|
</template>
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
import { computed } from 'vue';
|
|
80
80
|
import type { DiscoveredQuota } from '@saasicat/types';
|
|
81
81
|
import type { QuotaMeta } from './catalog-i18n.js';
|
|
82
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
82
83
|
|
|
83
84
|
// BundleQuotasEditor — toggle + numeric per Quota, against the discovery
|
|
84
85
|
// snapshot as a library (after plan simulation). `quotas[key]` is the
|
|
@@ -98,6 +99,8 @@ const props = defineProps<{
|
|
|
98
99
|
quotaRegistry?: Record<string, QuotaMeta>;
|
|
99
100
|
}>();
|
|
100
101
|
|
|
102
|
+
const msg = useSaMessages('bundles');
|
|
103
|
+
|
|
101
104
|
defineEmits<{
|
|
102
105
|
(e: 'toggle', quotaKey: string): void;
|
|
103
106
|
(e: 'setValue', quotaKey: string, value: number): void;
|
|
@@ -51,35 +51,29 @@
|
|
|
51
51
|
</span>
|
|
52
52
|
<div class="bv-status-text">
|
|
53
53
|
<template v-if="status === 'live'">
|
|
54
|
-
<b>v{{ version.version }}</b>
|
|
55
|
-
{{
|
|
56
|
-
<span class="bv-status-warn">
|
|
57
|
-
Inhalt & Preis sind read-only (laufende Verträge). Für Änderungen eine neue
|
|
58
|
-
Version anlegen.
|
|
59
|
-
</span>
|
|
54
|
+
<b>v{{ version.version }}</b> {{ msg.statusBanner.is }}
|
|
55
|
+
<b>{{ msg.statusBanner.live }}</b> {{ liveTail }}
|
|
56
|
+
<span class="bv-status-warn">{{ msg.statusBanner.liveWarning }}</span>
|
|
60
57
|
</template>
|
|
61
58
|
<template v-else-if="status === 'scheduled'">
|
|
62
|
-
<b>v{{ version.version }}</b>
|
|
63
|
-
{{
|
|
64
|
-
<span class="bv-status-ok">
|
|
59
|
+
<b>v{{ version.version }}</b> {{ msg.statusBanner.is }}
|
|
60
|
+
<b>{{ msg.statusBanner.scheduled }}</b> {{ scheduledTail }}
|
|
61
|
+
<span class="bv-status-ok">{{ msg.statusBanner.scheduledOk }}</span>
|
|
65
62
|
</template>
|
|
66
63
|
<template v-else-if="status === 'superseded'">
|
|
67
|
-
<b>v{{ version.version }}</b>
|
|
68
|
-
|
|
69
|
-
}}
|
|
70
|
-
– {{ formatDateDE(version.validUntil) }}) — wird nicht mehr angeboten, Bestand
|
|
71
|
-
bleibt für Abrechnung erhalten.
|
|
64
|
+
<b>v{{ version.version }}</b> {{ msg.statusBanner.is }}
|
|
65
|
+
<b>{{ msg.statusBanner.superseded }}</b> {{ supersededTail }}
|
|
72
66
|
</template>
|
|
73
67
|
<template v-else>
|
|
74
|
-
<b>v{{ version.version }}</b>
|
|
75
|
-
|
|
68
|
+
<b>v{{ version.version }}</b> {{ msg.statusBanner.is }}
|
|
69
|
+
<b>{{ msg.statusBanner.draft }}</b> {{ msg.statusBanner.draftTail }}
|
|
76
70
|
</template>
|
|
77
71
|
</div>
|
|
78
72
|
<button
|
|
79
73
|
v-if="status === 'scheduled' || status === 'draft'"
|
|
80
74
|
class="bv-status-discard"
|
|
81
75
|
type="button"
|
|
82
|
-
title="
|
|
76
|
+
:title="msg.statusBanner.discardTooltip"
|
|
83
77
|
@click="$emit('discard')"
|
|
84
78
|
>
|
|
85
79
|
<svg
|
|
@@ -94,7 +88,7 @@
|
|
|
94
88
|
d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"
|
|
95
89
|
/>
|
|
96
90
|
</svg>
|
|
97
|
-
<span>
|
|
91
|
+
<span>{{ common.discard }}</span>
|
|
98
92
|
</button>
|
|
99
93
|
</div>
|
|
100
94
|
</template>
|
|
@@ -103,7 +97,9 @@
|
|
|
103
97
|
import { computed } from 'vue';
|
|
104
98
|
import type { BundleVersionRow } from '@saasicat/types';
|
|
105
99
|
|
|
106
|
-
import { bundleVersionStatus,
|
|
100
|
+
import { bundleVersionStatus, formatDate } from './bundle-version-status';
|
|
101
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
102
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
107
103
|
|
|
108
104
|
// BundleStatusBanner — inline hint per bundle version with plain-text info on
|
|
109
105
|
// editability (after plan simulation). Shows the status + the business
|
|
@@ -119,7 +115,28 @@ defineEmits<{
|
|
|
119
115
|
(e: 'discard'): void;
|
|
120
116
|
}>();
|
|
121
117
|
|
|
118
|
+
const msg = useSaMessages('bundles');
|
|
119
|
+
const common = useSaMessages('common');
|
|
120
|
+
const { locale } = useSuperAdminI18n();
|
|
121
|
+
|
|
122
122
|
const status = computed(() => bundleVersionStatus(props.version, props.now));
|
|
123
|
+
|
|
124
|
+
const liveTail = computed(() =>
|
|
125
|
+
formatMessage(msg.value.statusBanner.liveTail, {
|
|
126
|
+
date: formatDate(props.version.validFrom, locale.value),
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
const scheduledTail = computed(() =>
|
|
130
|
+
formatMessage(msg.value.statusBanner.scheduledTail, {
|
|
131
|
+
date: formatDate(props.version.validFrom, locale.value),
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
const supersededTail = computed(() =>
|
|
135
|
+
formatMessage(msg.value.statusBanner.supersededTail, {
|
|
136
|
+
from: formatDate(props.version.validFrom, locale.value),
|
|
137
|
+
until: formatDate(props.version.validUntil, locale.value),
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
123
140
|
</script>
|
|
124
141
|
|
|
125
142
|
<style scoped>
|
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
<div v-if="hasOverlap" class="bve-overlap-banner" role="alert">
|
|
6
6
|
<span class="bve-overlap-ico">⚠</span>
|
|
7
7
|
<span>
|
|
8
|
-
<b>{{ overlapPlansCount }}</b>
|
|
9
|
-
|
|
10
|
-
Bitte vor Publish bereinigen.
|
|
8
|
+
<b>{{ overlapPlansCount }}</b>
|
|
9
|
+
{{ overlapPlansCount === 1 ? msg.editor.overlapOne : msg.editor.overlapMany }}
|
|
11
10
|
</span>
|
|
12
11
|
</div>
|
|
13
12
|
|
|
@@ -15,7 +14,7 @@
|
|
|
15
14
|
<!-- LEFT: Features + Quotas -->
|
|
16
15
|
<section class="bve-col">
|
|
17
16
|
<div class="bve-section-label">
|
|
18
|
-
<span>
|
|
17
|
+
<span>{{ msg.editor.sectionFeatures }}</span>
|
|
19
18
|
<span class="bve-section-count">
|
|
20
19
|
{{ form.features.length }} / {{ availableFeatures.length }}
|
|
21
20
|
</span>
|
|
@@ -30,7 +29,7 @@
|
|
|
30
29
|
/>
|
|
31
30
|
|
|
32
31
|
<div class="bve-section-label bve-section-label--top">
|
|
33
|
-
<span>
|
|
32
|
+
<span>{{ msg.fields.quotas }}</span>
|
|
34
33
|
<span class="bve-section-count">
|
|
35
34
|
{{ Object.keys(form.quotas).length }} / {{ availableQuotas.length }}
|
|
36
35
|
</span>
|
|
@@ -49,12 +48,12 @@
|
|
|
49
48
|
<!-- RIGHT: Pricing · Validity · Compat -->
|
|
50
49
|
<section class="bve-col">
|
|
51
50
|
<div class="bve-section-label">
|
|
52
|
-
<span>
|
|
53
|
-
<span class="bve-section-count">
|
|
51
|
+
<span>{{ msg.editor.sectionPricing }}</span>
|
|
52
|
+
<span class="bve-section-count">{{ msg.editor.pricingHint }}</span>
|
|
54
53
|
</div>
|
|
55
54
|
<div class="bve-row bve-row--pricing">
|
|
56
55
|
<label class="bve-field">
|
|
57
|
-
<span class="bve-field-label">
|
|
56
|
+
<span class="bve-field-label">{{ msg.fields.monthlyPrice }}</span>
|
|
58
57
|
<div class="bve-input-grp">
|
|
59
58
|
<input
|
|
60
59
|
type="text"
|
|
@@ -64,11 +63,11 @@
|
|
|
64
63
|
:disabled="locked"
|
|
65
64
|
@input="onPriceInput('monthlyNet', $event)"
|
|
66
65
|
/>
|
|
67
|
-
<span class="bve-input-unit"
|
|
66
|
+
<span class="bve-input-unit">{{ msg.fields.perMonthUnit }}</span>
|
|
68
67
|
</div>
|
|
69
68
|
</label>
|
|
70
69
|
<label class="bve-field">
|
|
71
|
-
<span class="bve-field-label">
|
|
70
|
+
<span class="bve-field-label">{{ msg.fields.yearlyPrice }}</span>
|
|
72
71
|
<div class="bve-input-grp">
|
|
73
72
|
<input
|
|
74
73
|
type="text"
|
|
@@ -78,26 +77,26 @@
|
|
|
78
77
|
:disabled="locked"
|
|
79
78
|
@input="onPriceInput('yearlyNet', $event)"
|
|
80
79
|
/>
|
|
81
|
-
<span class="bve-input-unit"
|
|
80
|
+
<span class="bve-input-unit">{{ msg.fields.perYearUnit }}</span>
|
|
82
81
|
</div>
|
|
83
82
|
</label>
|
|
84
83
|
<div class="bve-savings">
|
|
85
84
|
<template v-if="savingsPercent !== null">
|
|
86
|
-
|
|
85
|
+
{{ yearlyEquivalentText }}
|
|
87
86
|
<span v-if="savingsPercent > 0" class="bve-savings-pill">
|
|
88
|
-
{{
|
|
87
|
+
{{ savingsText }}
|
|
89
88
|
</span>
|
|
90
89
|
</template>
|
|
91
90
|
</div>
|
|
92
91
|
</div>
|
|
93
92
|
|
|
94
93
|
<div class="bve-section-label bve-section-label--top">
|
|
95
|
-
<span>
|
|
96
|
-
<span class="bve-section-count">
|
|
94
|
+
<span>{{ common.validity }}</span>
|
|
95
|
+
<span class="bve-section-count">{{ msg.editor.validityHint }}</span>
|
|
97
96
|
</div>
|
|
98
97
|
<div class="bve-row bve-row--validity">
|
|
99
98
|
<label class="bve-field">
|
|
100
|
-
<span class="bve-field-label">
|
|
99
|
+
<span class="bve-field-label">{{ msg.fields.validFrom }}</span>
|
|
101
100
|
<input
|
|
102
101
|
type="date"
|
|
103
102
|
class="bve-input"
|
|
@@ -107,21 +106,19 @@
|
|
|
107
106
|
/>
|
|
108
107
|
</label>
|
|
109
108
|
<label class="bve-field">
|
|
110
|
-
<span class="bve-field-label">
|
|
109
|
+
<span class="bve-field-label">{{ msg.fields.validUntil }}</span>
|
|
111
110
|
<input
|
|
112
111
|
type="text"
|
|
113
112
|
class="bve-input bve-input--readonly"
|
|
114
|
-
:value="
|
|
113
|
+
:value="validUntilDisplay"
|
|
115
114
|
disabled
|
|
116
115
|
/>
|
|
117
|
-
<span class="bve-field-hint">
|
|
118
|
-
wird beim Publish einer Nachfolge-Version automatisch gesetzt
|
|
119
|
-
</span>
|
|
116
|
+
<span class="bve-field-hint">{{ msg.editor.validUntilHint }}</span>
|
|
120
117
|
</label>
|
|
121
118
|
</div>
|
|
122
119
|
|
|
123
120
|
<div class="bve-section-label bve-section-label--top">
|
|
124
|
-
<span>
|
|
121
|
+
<span>{{ msg.editor.sectionMarketing }}</span>
|
|
125
122
|
</div>
|
|
126
123
|
<label class="bve-toggle-row">
|
|
127
124
|
<input
|
|
@@ -130,25 +127,25 @@
|
|
|
130
127
|
:disabled="locked"
|
|
131
128
|
@change="onMarketedToggle"
|
|
132
129
|
/>
|
|
133
|
-
<span class="bve-toggle-label">
|
|
130
|
+
<span class="bve-toggle-label">{{ msg.editor.marketed }}</span>
|
|
134
131
|
</label>
|
|
135
132
|
|
|
136
133
|
<div class="bve-section-label bve-section-label--top">
|
|
137
|
-
<span>
|
|
138
|
-
<span class="bve-section-count">
|
|
134
|
+
<span>{{ msg.editor.sectionChangeNote }}</span>
|
|
135
|
+
<span class="bve-section-count">{{ msg.editor.changeNoteRequired }}</span>
|
|
139
136
|
</div>
|
|
140
137
|
<textarea
|
|
141
138
|
class="bve-textarea"
|
|
142
139
|
rows="2"
|
|
143
140
|
:value="form.changeNote"
|
|
144
141
|
:disabled="locked"
|
|
145
|
-
placeholder="
|
|
142
|
+
:placeholder="msg.editor.changeNotePlaceholder"
|
|
146
143
|
@input="onChangeNoteInput"
|
|
147
144
|
/>
|
|
148
145
|
|
|
149
146
|
<div class="bve-section-label bve-section-label--top">
|
|
150
|
-
<span>
|
|
151
|
-
<span class="bve-section-count">{{
|
|
147
|
+
<span>{{ msg.fields.planCompat }}</span>
|
|
148
|
+
<span class="bve-section-count">{{ selectedCountText }}</span>
|
|
152
149
|
</div>
|
|
153
150
|
<BundlePlanCompatPicker
|
|
154
151
|
:plans="plans"
|
|
@@ -172,16 +169,16 @@
|
|
|
172
169
|
:disabled="saving || !hasChanges"
|
|
173
170
|
@click="onReset"
|
|
174
171
|
>
|
|
175
|
-
|
|
172
|
+
{{ common.reset }}
|
|
176
173
|
</button>
|
|
177
174
|
<button
|
|
178
175
|
type="button"
|
|
179
176
|
class="bve-btn bve-btn--primary"
|
|
180
177
|
:disabled="!canSave || saving"
|
|
181
|
-
:title="!canSave ?
|
|
178
|
+
:title="!canSave ? msg.editor.saveDisabledTooltip : msg.editor.saveTooltip"
|
|
182
179
|
@click="onSave"
|
|
183
180
|
>
|
|
184
|
-
{{ saving ?
|
|
181
|
+
{{ saving ? common.saving : common.save }}
|
|
185
182
|
</button>
|
|
186
183
|
</div>
|
|
187
184
|
<div v-if="saveError" class="bve-error">
|
|
@@ -206,7 +203,9 @@ import BundlePlanCompatPicker from './BundlePlanCompatPicker.vue';
|
|
|
206
203
|
import BundleQuotasEditor from './BundleQuotasEditor.vue';
|
|
207
204
|
import BundleStatusBanner from './BundleStatusBanner.vue';
|
|
208
205
|
import type { QuotaMeta } from './catalog-i18n.js';
|
|
209
|
-
import { bundleVersionStatus, findBundlePlanOverlap,
|
|
206
|
+
import { bundleVersionStatus, findBundlePlanOverlap, formatDate } from './bundle-version-status';
|
|
207
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
208
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
210
209
|
|
|
211
210
|
// BundleVersionInlineEditor — orchestrates the five sub-components (status
|
|
212
211
|
// banner + features editor + quotas editor + pricing/validity block +
|
|
@@ -262,6 +261,10 @@ const emit = defineEmits<{
|
|
|
262
261
|
|
|
263
262
|
const PRICE_RE = /^\d+(\.\d{1,2})?$/;
|
|
264
263
|
|
|
264
|
+
const msg = useSaMessages('bundles');
|
|
265
|
+
const common = useSaMessages('common');
|
|
266
|
+
const { locale } = useSuperAdminI18n();
|
|
267
|
+
|
|
265
268
|
function buildForm(v: BundleVersionRow): Form {
|
|
266
269
|
return {
|
|
267
270
|
features: [...v.features],
|
|
@@ -307,20 +310,33 @@ const monthlyEquivalent = computed(() => {
|
|
|
307
310
|
return (y / 12).toFixed(2);
|
|
308
311
|
});
|
|
309
312
|
|
|
313
|
+
const yearlyEquivalentText = computed(() =>
|
|
314
|
+
formatMessage(msg.value.editor.yearlyEquivalent, { amount: monthlyEquivalent.value }),
|
|
315
|
+
);
|
|
316
|
+
const savingsText = computed(() =>
|
|
317
|
+
formatMessage(msg.value.editor.savings, { percent: savingsPercent.value ?? 0 }),
|
|
318
|
+
);
|
|
319
|
+
const selectedCountText = computed(() =>
|
|
320
|
+
formatMessage(msg.value.editor.selectedCount, { count: form.planIds.length }),
|
|
321
|
+
);
|
|
322
|
+
const validUntilDisplay = computed(
|
|
323
|
+
() => formatDate(form.validUntil, locale.value) || msg.value.fields.validUntilOpen,
|
|
324
|
+
);
|
|
325
|
+
|
|
310
326
|
// ── Validation ─────────────────────────────────────────────
|
|
311
327
|
const validFromError = computed<string | null>(() => {
|
|
312
328
|
if (locked.value) return null;
|
|
313
|
-
if (!form.validFrom) return
|
|
329
|
+
if (!form.validFrom) return msg.value.validation.validFromRequired;
|
|
314
330
|
return null;
|
|
315
331
|
});
|
|
316
332
|
|
|
317
333
|
const priceError = computed<string | null>(() => {
|
|
318
334
|
if (locked.value) return null;
|
|
319
335
|
if (form.monthlyNet && !PRICE_RE.test(form.monthlyNet)) {
|
|
320
|
-
return
|
|
336
|
+
return msg.value.validation.monthlyPriceFormat;
|
|
321
337
|
}
|
|
322
338
|
if (form.yearlyNet && !PRICE_RE.test(form.yearlyNet)) {
|
|
323
|
-
return
|
|
339
|
+
return msg.value.validation.yearlyPriceFormat;
|
|
324
340
|
}
|
|
325
341
|
return null;
|
|
326
342
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="bv-vstrip">
|
|
3
|
-
<div class="bv-vstrip-label">
|
|
3
|
+
<div class="bv-vstrip-label">{{ msg.versionStrip.label }}</div>
|
|
4
4
|
<div class="bv-vstrip-tabs">
|
|
5
5
|
<button
|
|
6
6
|
v-for="v in sortedVersions"
|
|
@@ -8,20 +8,23 @@
|
|
|
8
8
|
type="button"
|
|
9
9
|
class="bv-vtab"
|
|
10
10
|
:class="[`bv-vtab-${statusOf(v)}`, { 'bv-vtab-current': v.id === modelValue }]"
|
|
11
|
-
:title="
|
|
11
|
+
:title="statusMetaOf(v).tooltip"
|
|
12
12
|
@click="$emit('update:modelValue', v.id)"
|
|
13
13
|
>
|
|
14
14
|
<span class="bv-vtab-name">v{{ v.version }}</span>
|
|
15
15
|
<span :class="['bv-vtab-status', statusOf(v)]">
|
|
16
|
-
{{
|
|
16
|
+
{{ statusMetaOf(v).label }}
|
|
17
17
|
</span>
|
|
18
18
|
<span class="bv-vtab-dates">
|
|
19
|
-
{{
|
|
20
|
-
<template v-if="v.validUntil">
|
|
21
|
-
|
|
19
|
+
{{ formatDate(v.validFrom, locale) }}
|
|
20
|
+
<template v-if="v.validUntil">
|
|
21
|
+
– {{ formatDate(v.validUntil, locale) }}</template
|
|
22
|
+
>
|
|
23
|
+
<template v-else> – {{ msg.fields.validUntilOpen }}</template>
|
|
22
24
|
</span>
|
|
23
25
|
<span class="bv-vtab-price">
|
|
24
|
-
{{ v.monthlyNet ?? '—' }}<template v-if="v.monthlyNet"> €</template>
|
|
26
|
+
{{ v.monthlyNet ?? '—' }}<template v-if="v.monthlyNet"> €</template>
|
|
27
|
+
{{ msg.versionStrip.perMonth }}
|
|
25
28
|
</span>
|
|
26
29
|
</button>
|
|
27
30
|
<button
|
|
@@ -30,8 +33,8 @@
|
|
|
30
33
|
:disabled="!canAddVersion"
|
|
31
34
|
:title="
|
|
32
35
|
canAddVersion
|
|
33
|
-
?
|
|
34
|
-
:
|
|
36
|
+
? msg.versionStrip.addTooltip
|
|
37
|
+
: msg.versionStrip.addDisabledTooltip
|
|
35
38
|
"
|
|
36
39
|
@click="$emit('addVersion')"
|
|
37
40
|
>
|
|
@@ -45,7 +48,7 @@
|
|
|
45
48
|
>
|
|
46
49
|
<path d="M12 5v14M5 12h14" />
|
|
47
50
|
</svg>
|
|
48
|
-
<span>
|
|
51
|
+
<span>{{ msg.versionStrip.addVersion }}</span>
|
|
49
52
|
</button>
|
|
50
53
|
</div>
|
|
51
54
|
</div>
|
|
@@ -56,11 +59,13 @@ import { computed } from 'vue';
|
|
|
56
59
|
import type { BundleVersionRow } from '@saasicat/types';
|
|
57
60
|
|
|
58
61
|
import {
|
|
59
|
-
|
|
62
|
+
bundleStatusMeta,
|
|
60
63
|
bundleVersionStatus,
|
|
61
64
|
bundleVersionsSorted,
|
|
62
|
-
|
|
65
|
+
formatDate,
|
|
66
|
+
type BundleStatusMeta,
|
|
63
67
|
} from './bundle-version-status';
|
|
68
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
64
69
|
|
|
65
70
|
// BundleVersionStrip — tab bar across all versions of a bundle, modeled on
|
|
66
71
|
// the plan simulation (saasadminui/project/bundles.jsx → BundleVersionStrip).
|
|
@@ -82,6 +87,9 @@ defineEmits<{
|
|
|
82
87
|
(e: 'addVersion'): void;
|
|
83
88
|
}>();
|
|
84
89
|
|
|
90
|
+
const msg = useSaMessages('bundles');
|
|
91
|
+
const { locale } = useSuperAdminI18n();
|
|
92
|
+
|
|
85
93
|
const sortedVersions = computed(() => bundleVersionsSorted(props.versions));
|
|
86
94
|
|
|
87
95
|
const canAddVersion = computed(() => !props.versions.some((v) => v.publishedAt === null));
|
|
@@ -89,6 +97,10 @@ const canAddVersion = computed(() => !props.versions.some((v) => v.publishedAt =
|
|
|
89
97
|
function statusOf(v: BundleVersionRow) {
|
|
90
98
|
return bundleVersionStatus(v, props.now);
|
|
91
99
|
}
|
|
100
|
+
|
|
101
|
+
function statusMetaOf(v: BundleVersionRow): BundleStatusMeta {
|
|
102
|
+
return bundleStatusMeta(statusOf(v), locale.value);
|
|
103
|
+
}
|
|
92
104
|
</script>
|
|
93
105
|
|
|
94
106
|
<style scoped>
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import type { BundleVersionRow, PlanVersionRow } from '@saasicat/types';
|
|
8
8
|
|
|
9
|
+
import { SA_INTL_LOCALES, type SaLocale } from '../../client/i18n/locale.js';
|
|
10
|
+
import { bundlesMessages } from '../../client/i18n/messages/bundles.js';
|
|
11
|
+
|
|
9
12
|
/** UI lifecycle status of a single BundleVersion. */
|
|
10
13
|
export type BundleVersionUiStatus = 'draft' | 'live' | 'scheduled' | 'superseded';
|
|
11
14
|
|
|
@@ -51,34 +54,23 @@ export interface BundleStatusMeta {
|
|
|
51
54
|
tooltip: string;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
draft:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
live: {
|
|
61
|
-
label: 'Live',
|
|
62
|
-
cls: 'live',
|
|
63
|
-
tooltip: 'Aktive Version · verkaufbar · read-only (laufende Verträge)',
|
|
64
|
-
},
|
|
65
|
-
scheduled: {
|
|
66
|
-
label: 'Geplant',
|
|
67
|
-
cls: 'scheduled',
|
|
68
|
-
tooltip: 'Zukünftig aktiv · noch nicht verkaufbar · frei editierbar',
|
|
69
|
-
},
|
|
70
|
-
superseded: {
|
|
71
|
-
label: 'Abgelöst',
|
|
72
|
-
cls: 'supersed',
|
|
73
|
-
tooltip: 'Durch neue Version ersetzt · Bestand bleibt',
|
|
74
|
-
},
|
|
75
|
-
retired: {
|
|
76
|
-
label: 'Retired',
|
|
77
|
-
cls: 'supersed',
|
|
78
|
-
tooltip: 'Bundle-Stamm wurde soft-deleted',
|
|
79
|
-
},
|
|
57
|
+
const BUNDLE_STATUS_CLASS: Record<BundleAggregateStatus, BundleStatusMeta['cls']> = {
|
|
58
|
+
draft: 'draft',
|
|
59
|
+
live: 'live',
|
|
60
|
+
scheduled: 'scheduled',
|
|
61
|
+
superseded: 'supersed',
|
|
62
|
+
retired: 'supersed',
|
|
80
63
|
};
|
|
81
64
|
|
|
65
|
+
/** Label, chip class and tooltip for a status in the given UI locale. */
|
|
66
|
+
export function bundleStatusMeta(
|
|
67
|
+
status: BundleAggregateStatus,
|
|
68
|
+
locale: SaLocale,
|
|
69
|
+
): BundleStatusMeta {
|
|
70
|
+
const texts = bundlesMessages[locale].status[status];
|
|
71
|
+
return { label: texts.label, cls: BUNDLE_STATUS_CLASS[status], tooltip: texts.tooltip };
|
|
72
|
+
}
|
|
73
|
+
|
|
82
74
|
/**
|
|
83
75
|
* Sorts the versions of a bundle line ascending by
|
|
84
76
|
* `validFrom` (drafts without validFrom go to the end).
|
|
@@ -154,11 +146,19 @@ export function findBundlePlanOverlap(
|
|
|
154
146
|
return { features, quotas, hasAny: features.length > 0 || quotas.length > 0 };
|
|
155
147
|
}
|
|
156
148
|
|
|
157
|
-
/**
|
|
158
|
-
|
|
149
|
+
/**
|
|
150
|
+
* ISO `YYYY-MM-DD` → numeric date in the UI locale; null/empty → '—'.
|
|
151
|
+
* Formatted in UTC so a calendar day never shifts across time zones.
|
|
152
|
+
*/
|
|
153
|
+
export function formatDate(iso: string | null | undefined, locale: SaLocale): string {
|
|
159
154
|
if (!iso) return '—';
|
|
160
155
|
const day = iso.slice(0, 10);
|
|
161
156
|
const [y, m, d] = day.split('-');
|
|
162
157
|
if (!y || !m || !d) return iso;
|
|
163
|
-
return `${
|
|
158
|
+
return new Date(`${day}T00:00:00Z`).toLocaleDateString(SA_INTL_LOCALES[locale], {
|
|
159
|
+
day: '2-digit',
|
|
160
|
+
month: '2-digit',
|
|
161
|
+
year: 'numeric',
|
|
162
|
+
timeZone: 'UTC',
|
|
163
|
+
});
|
|
164
164
|
}
|