@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
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
>
|
|
9
9
|
<div class="pve-col-header pve-col-header--basket">
|
|
10
10
|
<div>
|
|
11
|
-
<div class="pve-col-title">
|
|
12
|
-
<div class="pve-col-sub">
|
|
11
|
+
<div class="pve-col-title">{{ title }}</div>
|
|
12
|
+
<div class="pve-col-sub">{{ msg.basket.subtitle }}</div>
|
|
13
13
|
</div>
|
|
14
|
-
<span class="pve-chip pve-chip--changes">{{
|
|
14
|
+
<span class="pve-chip pve-chip--changes">{{ changeCountLabel }}</span>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
17
|
<div class="pve-basket-settings">
|
|
18
18
|
<div class="pve-bs-row">
|
|
19
|
-
<div class="pve-bs-label">
|
|
19
|
+
<div class="pve-bs-label">{{ msg.basket.validFrom }}</div>
|
|
20
20
|
<input
|
|
21
21
|
class="pve-bs-input"
|
|
22
22
|
:class="{ 'pve-bs-input--error': !!validFromError }"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
placeholder="YYYY-MM-DD"
|
|
27
27
|
@input="emitTextInput('update:validFrom', $event)"
|
|
28
28
|
/>
|
|
29
|
-
<div class="pve-bs-label pve-bs-label--inline">
|
|
29
|
+
<div class="pve-bs-label pve-bs-label--inline">{{ msg.basket.until }}</div>
|
|
30
30
|
<input
|
|
31
31
|
class="pve-bs-input"
|
|
32
32
|
type="date"
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
<span>{{ validFromError }}</span>
|
|
52
52
|
</div>
|
|
53
53
|
<div class="pve-bs-row">
|
|
54
|
-
<div class="pve-bs-label">
|
|
54
|
+
<div class="pve-bs-label">{{ msg.sections.price }}</div>
|
|
55
55
|
<div class="pve-bs-input-grp">
|
|
56
56
|
<span class="pve-bs-prefix">€</span>
|
|
57
57
|
<input
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
inputmode="decimal"
|
|
61
61
|
@input="emitTextInput('update:monthlyNet', $event)"
|
|
62
62
|
/>
|
|
63
|
-
<span class="pve-bs-suffix"
|
|
63
|
+
<span class="pve-bs-suffix">{{ msg.perMonthShort }}</span>
|
|
64
64
|
</div>
|
|
65
65
|
<div class="pve-bs-input-grp pve-bs-input-grp--gap">
|
|
66
66
|
<span class="pve-bs-prefix">€</span>
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
inputmode="decimal"
|
|
71
71
|
@input="emitTextInput('update:yearlyNet', $event)"
|
|
72
72
|
/>
|
|
73
|
-
<span class="pve-bs-suffix"
|
|
73
|
+
<span class="pve-bs-suffix">{{ msg.perYearShort }}</span>
|
|
74
74
|
</div>
|
|
75
75
|
</div>
|
|
76
76
|
<div class="pve-bs-row">
|
|
77
|
-
<div class="pve-bs-label">
|
|
77
|
+
<div class="pve-bs-label">{{ msg.basket.inPublicCatalog }}</div>
|
|
78
78
|
<label class="pve-toggle">
|
|
79
79
|
<input type="checkbox" :checked="form.marketed" @change="emitCheckboxInput" />
|
|
80
80
|
<span />
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
<input
|
|
83
83
|
class="pve-bs-input pve-bs-input--grow"
|
|
84
84
|
:value="form.changeNote"
|
|
85
|
-
placeholder="
|
|
85
|
+
:placeholder="msg.basket.changeNotePlaceholder"
|
|
86
86
|
@input="emitTextInput('update:changeNote', $event)"
|
|
87
87
|
/>
|
|
88
88
|
</div>
|
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
<div class="pve-basket-group">
|
|
92
92
|
<div class="pve-bg-header">
|
|
93
93
|
<span class="pve-bg-dot pve-bg-dot--quota" />
|
|
94
|
-
<span class="pve-bg-title">
|
|
95
|
-
<span class="pve-bg-count">{{ selectedQuotaList.length }}
|
|
94
|
+
<span class="pve-bg-title">{{ msg.sections.quotas }}</span>
|
|
95
|
+
<span class="pve-bg-count">{{ assignedCount(selectedQuotaList.length) }}</span>
|
|
96
96
|
</div>
|
|
97
97
|
<div class="pve-dz">
|
|
98
98
|
<div v-for="row in selectedQuotaList" :key="row.quotaKey" class="pve-sel-row">
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
<button
|
|
114
114
|
class="pve-sel-x"
|
|
115
115
|
type="button"
|
|
116
|
-
aria-label="
|
|
116
|
+
:aria-label="msg.basket.removeQuota"
|
|
117
117
|
@click="$emit('toggle-quota', row.quotaKey, false)"
|
|
118
118
|
>
|
|
119
119
|
<svg
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
</button>
|
|
130
130
|
</div>
|
|
131
131
|
<div v-if="selectedQuotaList.length === 0" class="pve-dz-empty">
|
|
132
|
-
|
|
132
|
+
{{ msg.basket.emptyQuotas }}
|
|
133
133
|
</div>
|
|
134
134
|
</div>
|
|
135
135
|
</div>
|
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
<div class="pve-basket-group">
|
|
138
138
|
<div class="pve-bg-header">
|
|
139
139
|
<span class="pve-bg-dot pve-bg-dot--feature" />
|
|
140
|
-
<span class="pve-bg-title">
|
|
141
|
-
<span class="pve-bg-count">{{ form.features.length }}
|
|
140
|
+
<span class="pve-bg-title">{{ msg.sections.features }}</span>
|
|
141
|
+
<span class="pve-bg-count">{{ assignedCount(form.features.length) }}</span>
|
|
142
142
|
</div>
|
|
143
143
|
<div class="pve-dz">
|
|
144
144
|
<div v-for="key in sortedSelectedFeatures" :key="key" class="pve-sel-row">
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
<button
|
|
151
151
|
class="pve-sel-x"
|
|
152
152
|
type="button"
|
|
153
|
-
aria-label="
|
|
153
|
+
:aria-label="msg.basket.removeFeature"
|
|
154
154
|
@click="$emit('toggle-feature', key, false)"
|
|
155
155
|
>
|
|
156
156
|
<svg
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
</button>
|
|
167
167
|
</div>
|
|
168
168
|
<div v-if="form.features.length === 0" class="pve-dz-empty">
|
|
169
|
-
|
|
169
|
+
{{ msg.basket.emptyFeatures }}
|
|
170
170
|
</div>
|
|
171
171
|
</div>
|
|
172
172
|
</div>
|
|
@@ -174,8 +174,8 @@
|
|
|
174
174
|
<div class="pve-basket-group">
|
|
175
175
|
<div class="pve-bg-header">
|
|
176
176
|
<span class="pve-bg-dot pve-bg-dot--bundle" />
|
|
177
|
-
<span class="pve-bg-title">
|
|
178
|
-
<span class="pve-bg-count">{{ activeBundles.length }}
|
|
177
|
+
<span class="pve-bg-title">{{ msg.sections.bundles }}</span>
|
|
178
|
+
<span class="pve-bg-count">{{ assignedCount(activeBundles.length) }}</span>
|
|
179
179
|
</div>
|
|
180
180
|
<div class="pve-dz">
|
|
181
181
|
<div v-for="b in activeBundles" :key="b.bundleKey" class="pve-sel-row">
|
|
@@ -183,13 +183,13 @@
|
|
|
183
183
|
<div class="pve-sel-body">
|
|
184
184
|
<div class="pve-sel-label">{{ b.label || b.bundleKey }}</div>
|
|
185
185
|
<div class="pve-sel-sub">
|
|
186
|
-
{{ b.bundleKey }} · {{ b.features.length }}
|
|
186
|
+
{{ b.bundleKey }} · {{ bundleFeatureCount(b.features.length) }}
|
|
187
187
|
</div>
|
|
188
188
|
</div>
|
|
189
189
|
<button
|
|
190
190
|
class="pve-sel-x"
|
|
191
191
|
type="button"
|
|
192
|
-
aria-label="
|
|
192
|
+
:aria-label="msg.basket.removeBundle"
|
|
193
193
|
@click="$emit('toggle-bundle', b, false)"
|
|
194
194
|
>
|
|
195
195
|
<svg
|
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
</button>
|
|
206
206
|
</div>
|
|
207
207
|
<div v-if="activeBundles.length === 0" class="pve-dz-empty pve-dz-empty--center">
|
|
208
|
-
|
|
208
|
+
{{ msg.basket.emptyBundles }}
|
|
209
209
|
</div>
|
|
210
210
|
</div>
|
|
211
211
|
</div>
|
|
@@ -213,9 +213,12 @@
|
|
|
213
213
|
</template>
|
|
214
214
|
|
|
215
215
|
<script setup lang="ts">
|
|
216
|
+
import { computed } from 'vue';
|
|
217
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
218
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
216
219
|
import type { BundleEntry, DraftForm, SelectedQuotaRow } from './types.js';
|
|
217
220
|
|
|
218
|
-
defineProps<{
|
|
221
|
+
const props = defineProps<{
|
|
219
222
|
form: DraftForm;
|
|
220
223
|
dragOver: boolean;
|
|
221
224
|
changeCount: number;
|
|
@@ -243,6 +246,24 @@ const emit = defineEmits<{
|
|
|
243
246
|
(e: 'drop'): void;
|
|
244
247
|
}>();
|
|
245
248
|
|
|
249
|
+
const msg = useSaMessages('planEditor');
|
|
250
|
+
|
|
251
|
+
const title = computed(() =>
|
|
252
|
+
formatMessage(msg.value.basket.title, { version: props.form.version }),
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const changeCountLabel = computed(() =>
|
|
256
|
+
formatMessage(msg.value.basket.changeCount, { count: props.changeCount }),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
function assignedCount(count: number): string {
|
|
260
|
+
return formatMessage(msg.value.basket.assignedCount, { count });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function bundleFeatureCount(count: number): string {
|
|
264
|
+
return formatMessage(msg.value.basket.bundleFeatureCount, { count });
|
|
265
|
+
}
|
|
266
|
+
|
|
246
267
|
type TextInputEvent =
|
|
247
268
|
| 'update:validFrom'
|
|
248
269
|
| 'update:validUntil'
|
|
@@ -4,20 +4,17 @@
|
|
|
4
4
|
<div class="pve-diff-head">
|
|
5
5
|
<div>
|
|
6
6
|
<div id="pve-diff-title" class="pve-diff-title">
|
|
7
|
-
|
|
7
|
+
{{ msg.diffDialog.title }}
|
|
8
8
|
<template v-if="predecessorVersion">
|
|
9
9
|
v{{ predecessorVersion.version }} → v{{ version }}
|
|
10
10
|
</template>
|
|
11
11
|
</div>
|
|
12
|
-
<div class="pve-diff-sub">
|
|
13
|
-
Was sich gegenüber der Vorgänger-Version ändert (aktueller, ungespeicherter
|
|
14
|
-
Stand).
|
|
15
|
-
</div>
|
|
12
|
+
<div class="pve-diff-sub">{{ msg.diffDialog.subtitle }}</div>
|
|
16
13
|
</div>
|
|
17
14
|
<button
|
|
18
15
|
class="pve-diff-close"
|
|
19
16
|
type="button"
|
|
20
|
-
aria-label="
|
|
17
|
+
:aria-label="common.close"
|
|
21
18
|
@click="$emit('update:modelValue', false)"
|
|
22
19
|
>
|
|
23
20
|
<svg
|
|
@@ -61,7 +58,7 @@
|
|
|
61
58
|
>
|
|
62
59
|
</div>
|
|
63
60
|
<div v-if="diffRows.length === 0" class="pve-diff-empty">
|
|
64
|
-
|
|
61
|
+
{{ msg.diffDialog.empty }}
|
|
65
62
|
</div>
|
|
66
63
|
</div>
|
|
67
64
|
</div>
|
|
@@ -69,6 +66,7 @@
|
|
|
69
66
|
</template>
|
|
70
67
|
|
|
71
68
|
<script setup lang="ts">
|
|
69
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
72
70
|
import type { EditorDiffRow, PredecessorVersion } from './types.js';
|
|
73
71
|
|
|
74
72
|
defineProps<{
|
|
@@ -81,4 +79,7 @@ defineProps<{
|
|
|
81
79
|
defineEmits<{
|
|
82
80
|
(e: 'update:modelValue', value: boolean): void;
|
|
83
81
|
}>();
|
|
82
|
+
|
|
83
|
+
const msg = useSaMessages('planEditor');
|
|
84
|
+
const common = useSaMessages('common');
|
|
84
85
|
</script>
|
|
@@ -109,6 +109,9 @@
|
|
|
109
109
|
|
|
110
110
|
<script setup lang="ts">
|
|
111
111
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
112
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
113
|
+
import { formatCurrency } from '../../client/i18n/currency.js';
|
|
114
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
112
115
|
import PlanComponentPool from './PlanComponentPool.vue';
|
|
113
116
|
import PlanCatalogPreview from './PlanCatalogPreview.vue';
|
|
114
117
|
import PlanVersionBasket from './PlanVersionBasket.vue';
|
|
@@ -187,6 +190,10 @@ const emit = defineEmits<{
|
|
|
187
190
|
(e: 'cancel'): void;
|
|
188
191
|
}>();
|
|
189
192
|
|
|
193
|
+
const msg = useSaMessages('planEditor');
|
|
194
|
+
const common = useSaMessages('common');
|
|
195
|
+
const { locale, intlLocale } = useSuperAdminI18n();
|
|
196
|
+
|
|
190
197
|
const form = reactive<DraftForm>({
|
|
191
198
|
version: props.initialForm.version,
|
|
192
199
|
features: [...props.initialForm.features],
|
|
@@ -238,13 +245,25 @@ const searchTerm = ref('');
|
|
|
238
245
|
const activeTab = ref<PoolTab>('features');
|
|
239
246
|
|
|
240
247
|
const poolTabs = computed<PoolTabItem[]>(() => [
|
|
241
|
-
{
|
|
242
|
-
|
|
243
|
-
|
|
248
|
+
{
|
|
249
|
+
id: 'features' as PoolTab,
|
|
250
|
+
label: msg.value.sections.features,
|
|
251
|
+
count: filteredFeatureCount.value,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'quotas' as PoolTab,
|
|
255
|
+
label: msg.value.sections.quotas,
|
|
256
|
+
count: filteredQuotas.value.length,
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: 'bundles' as PoolTab,
|
|
260
|
+
label: msg.value.sections.bundles,
|
|
261
|
+
count: filteredBundles.value.length,
|
|
262
|
+
},
|
|
244
263
|
]);
|
|
245
264
|
|
|
246
265
|
function normalize(s: string): string {
|
|
247
|
-
return s.toLocaleLowerCase(
|
|
266
|
+
return s.toLocaleLowerCase(intlLocale.value);
|
|
248
267
|
}
|
|
249
268
|
|
|
250
269
|
function matchesSearch(haystack: string[], needle: string): boolean {
|
|
@@ -258,12 +277,12 @@ function featureLabel(key: string): string {
|
|
|
258
277
|
}
|
|
259
278
|
|
|
260
279
|
function featureGroupLabel(key: string): string {
|
|
261
|
-
return props.featureRegistry?.[key]?.group ??
|
|
280
|
+
return props.featureRegistry?.[key]?.group ?? common.value.general;
|
|
262
281
|
}
|
|
263
282
|
|
|
264
283
|
// Pool lists sorted by display label (not by key/snapshot order),
|
|
265
284
|
// so that planning stays predictable — same order as in the basket.
|
|
266
|
-
const byLabel = (a: string, b: string) => a.localeCompare(b,
|
|
285
|
+
const byLabel = (a: string, b: string) => a.localeCompare(b, intlLocale.value);
|
|
267
286
|
|
|
268
287
|
const filteredFeatures = computed(() =>
|
|
269
288
|
props.availableFeatures
|
|
@@ -282,7 +301,7 @@ const filteredFeatureGroups = computed<FeatureGroup[]>(() => {
|
|
|
282
301
|
byGroup.set(g, list);
|
|
283
302
|
}
|
|
284
303
|
return [...byGroup.entries()]
|
|
285
|
-
.sort(([a], [b]) => a
|
|
304
|
+
.sort(([a], [b]) => byLabel(a, b))
|
|
286
305
|
.map(([key, rows]) => ({ key, label: key, rows }));
|
|
287
306
|
});
|
|
288
307
|
|
|
@@ -414,8 +433,7 @@ function toTitleCase(s: string): string {
|
|
|
414
433
|
function formatMoney(raw: string): string {
|
|
415
434
|
if (!/^\d+(\.\d{1,2})?$/.test(raw)) return raw + ' €';
|
|
416
435
|
const num = Number(raw);
|
|
417
|
-
|
|
418
|
-
return `${num.toFixed(2).replace('.', ',')} €`;
|
|
436
|
+
return formatCurrency(num, locale.value);
|
|
419
437
|
}
|
|
420
438
|
|
|
421
439
|
const formattedMonthly = computed(() => formatMoney(form.monthlyNet));
|
|
@@ -429,7 +447,7 @@ const yearlySavingsLabel = computed(() => {
|
|
|
429
447
|
if (y >= fullYear) return null;
|
|
430
448
|
const pct = Math.round(((fullYear - y) / fullYear) * 100);
|
|
431
449
|
if (pct <= 0) return null;
|
|
432
|
-
return
|
|
450
|
+
return formatMessage(msg.value.editor.yearlySavings, { percent: pct });
|
|
433
451
|
});
|
|
434
452
|
|
|
435
453
|
// ── Validation ──────────────────────────────────────────────────────
|
|
@@ -443,10 +461,11 @@ const validFromError = computed<string | null>(() => {
|
|
|
443
461
|
const draftDay = form.validFrom.slice(0, 10);
|
|
444
462
|
const prevDay = prev.validFrom.slice(0, 10);
|
|
445
463
|
if (draftDay <= prevDay) {
|
|
446
|
-
return (
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
464
|
+
return formatMessage(msg.value.editor.validFromError, {
|
|
465
|
+
draftDay,
|
|
466
|
+
version: prev.version,
|
|
467
|
+
prevDay,
|
|
468
|
+
});
|
|
450
469
|
}
|
|
451
470
|
return null;
|
|
452
471
|
});
|
|
@@ -484,47 +503,52 @@ const canSave = computed(() => {
|
|
|
484
503
|
});
|
|
485
504
|
|
|
486
505
|
const predecessorValidUntilHint = computed(() => {
|
|
487
|
-
|
|
506
|
+
const fallback = msg.value.editor.validUntilHintFallback;
|
|
507
|
+
if (!form.validFrom) return fallback;
|
|
488
508
|
const d = new Date(form.validFrom);
|
|
489
|
-
if (Number.isNaN(d.getTime())) return
|
|
509
|
+
if (Number.isNaN(d.getTime())) return fallback;
|
|
490
510
|
d.setDate(d.getDate() - 1);
|
|
491
511
|
return d.toISOString().slice(0, 10);
|
|
492
512
|
});
|
|
493
513
|
|
|
494
514
|
const checklist = computed<ChecklistItem[]>(() => {
|
|
515
|
+
const texts = msg.value.editor;
|
|
495
516
|
const items: ChecklistItem[] = [
|
|
496
517
|
{
|
|
497
518
|
id: 'prices',
|
|
498
|
-
label:
|
|
519
|
+
label: texts.checklistPrices,
|
|
499
520
|
ok:
|
|
500
521
|
/^\d+(\.\d{1,2})?$/.test(form.monthlyNet) &&
|
|
501
522
|
/^\d+(\.\d{1,2})?$/.test(form.yearlyNet),
|
|
502
523
|
},
|
|
503
524
|
{
|
|
504
525
|
id: 'note',
|
|
505
|
-
label:
|
|
526
|
+
label: texts.checklistChangeNote,
|
|
506
527
|
ok: form.changeNote.trim().length > 0,
|
|
507
528
|
},
|
|
508
529
|
{
|
|
509
530
|
id: 'valid-from',
|
|
510
|
-
label:
|
|
531
|
+
label: texts.checklistValidity,
|
|
511
532
|
ok: !!form.validFrom,
|
|
512
533
|
},
|
|
513
534
|
{
|
|
514
535
|
id: 'valid-from-order',
|
|
515
|
-
label:
|
|
536
|
+
label: texts.checklistValidFromOrder,
|
|
516
537
|
ok: validFromError.value === null,
|
|
517
538
|
},
|
|
518
539
|
{
|
|
519
540
|
id: 'min-feature',
|
|
520
|
-
label:
|
|
541
|
+
label: texts.checklistMinFeature,
|
|
521
542
|
ok: form.features.length > 0,
|
|
522
543
|
},
|
|
523
544
|
];
|
|
524
545
|
if (props.tenantImpactCount > 0) {
|
|
525
546
|
items.push({
|
|
526
547
|
id: 'tenant-impact',
|
|
527
|
-
label:
|
|
548
|
+
label: formatMessage(texts.checklistTenantImpact, {
|
|
549
|
+
count: props.tenantImpactCount,
|
|
550
|
+
version: form.version,
|
|
551
|
+
}),
|
|
528
552
|
ok: false,
|
|
529
553
|
});
|
|
530
554
|
}
|
|
@@ -537,14 +561,22 @@ const checklistOkCount = computed(() => checklist.value.filter((c) => c.ok).leng
|
|
|
537
561
|
const showDiff = ref(false);
|
|
538
562
|
|
|
539
563
|
const DIFF_STYLE = {
|
|
540
|
-
added: { bg: '#ecfdf5', border: '#a7f3d0', color: '#047857', sign: '+'
|
|
541
|
-
removed: { bg: '#fef2f2', border: '#fecaca', color: '#b91c1c', sign: '−'
|
|
542
|
-
changed: { bg: '#fffbeb', border: '#fde68a', color: '#b45309', sign: '~'
|
|
564
|
+
added: { bg: '#ecfdf5', border: '#a7f3d0', color: '#047857', sign: '+' },
|
|
565
|
+
removed: { bg: '#fef2f2', border: '#fecaca', color: '#b91c1c', sign: '−' },
|
|
566
|
+
changed: { bg: '#fffbeb', border: '#fde68a', color: '#b45309', sign: '~' },
|
|
543
567
|
} as const;
|
|
544
568
|
|
|
569
|
+
const diffStyles = computed(() => ({
|
|
570
|
+
added: { ...DIFF_STYLE.added, tag: msg.value.editor.diffTagAdded },
|
|
571
|
+
removed: { ...DIFF_STYLE.removed, tag: msg.value.editor.diffTagRemoved },
|
|
572
|
+
changed: { ...DIFF_STYLE.changed, tag: msg.value.editor.diffTagChanged },
|
|
573
|
+
}));
|
|
574
|
+
|
|
545
575
|
const diffRows = computed<EditorDiffRow[]>(() => {
|
|
546
576
|
const prev = props.predecessorVersion;
|
|
547
577
|
if (!prev) return [];
|
|
578
|
+
const style = diffStyles.value;
|
|
579
|
+
const sections = msg.value.sections;
|
|
548
580
|
const out: EditorDiffRow[] = [];
|
|
549
581
|
|
|
550
582
|
// Features
|
|
@@ -554,10 +586,10 @@ const diffRows = computed<EditorDiffRow[]>(() => {
|
|
|
554
586
|
if (!prevFeatures.has(f)) {
|
|
555
587
|
out.push({
|
|
556
588
|
id: `add-f-${f}`,
|
|
557
|
-
section:
|
|
589
|
+
section: sections.features,
|
|
558
590
|
label: featureLabel(f),
|
|
559
591
|
sub: f,
|
|
560
|
-
...
|
|
592
|
+
...style.added,
|
|
561
593
|
});
|
|
562
594
|
}
|
|
563
595
|
}
|
|
@@ -565,10 +597,10 @@ const diffRows = computed<EditorDiffRow[]>(() => {
|
|
|
565
597
|
if (!curFeatures.has(f)) {
|
|
566
598
|
out.push({
|
|
567
599
|
id: `rem-f-${f}`,
|
|
568
|
-
section:
|
|
600
|
+
section: sections.features,
|
|
569
601
|
label: featureLabel(f),
|
|
570
602
|
sub: f,
|
|
571
|
-
...
|
|
603
|
+
...style.removed,
|
|
572
604
|
});
|
|
573
605
|
}
|
|
574
606
|
}
|
|
@@ -585,43 +617,45 @@ const diffRows = computed<EditorDiffRow[]>(() => {
|
|
|
585
617
|
if (before === undefined && after !== undefined) {
|
|
586
618
|
out.push({
|
|
587
619
|
id: `add-q-${k}`,
|
|
588
|
-
section:
|
|
620
|
+
section: sections.quotas,
|
|
589
621
|
label,
|
|
590
622
|
sub: k,
|
|
591
623
|
to: fmt(after),
|
|
592
|
-
...
|
|
624
|
+
...style.added,
|
|
593
625
|
});
|
|
594
626
|
} else if (before !== undefined && after === undefined) {
|
|
595
627
|
out.push({
|
|
596
628
|
id: `rem-q-${k}`,
|
|
597
|
-
section:
|
|
629
|
+
section: sections.quotas,
|
|
598
630
|
label,
|
|
599
631
|
sub: k,
|
|
600
632
|
from: fmt(before),
|
|
601
|
-
...
|
|
633
|
+
...style.removed,
|
|
602
634
|
});
|
|
603
635
|
} else if (before !== undefined && after !== undefined && before !== after) {
|
|
604
636
|
out.push({
|
|
605
637
|
id: `chg-q-${k}`,
|
|
606
|
-
section:
|
|
638
|
+
section: sections.quotas,
|
|
607
639
|
label,
|
|
608
640
|
sub: k,
|
|
609
641
|
from: fmt(before),
|
|
610
642
|
to: fmt(after),
|
|
611
|
-
...
|
|
643
|
+
...style.changed,
|
|
612
644
|
});
|
|
613
645
|
}
|
|
614
646
|
}
|
|
615
647
|
|
|
616
648
|
// Price
|
|
617
649
|
if (prev.monthlyNet !== form.monthlyNet || prev.yearlyNet !== form.yearlyNet) {
|
|
650
|
+
const perMonth = msg.value.perMonthShort;
|
|
651
|
+
const perYear = msg.value.perYearShort;
|
|
618
652
|
out.push({
|
|
619
653
|
id: 'chg-price',
|
|
620
|
-
section:
|
|
621
|
-
label:
|
|
622
|
-
from: `${formatMoney(prev.monthlyNet)}
|
|
623
|
-
to: `${formattedMonthly.value}
|
|
624
|
-
...
|
|
654
|
+
section: sections.price,
|
|
655
|
+
label: msg.value.editor.diffPriceLabel,
|
|
656
|
+
from: `${formatMoney(prev.monthlyNet)} ${perMonth} · ${formatMoney(prev.yearlyNet)} ${perYear}`,
|
|
657
|
+
to: `${formattedMonthly.value} ${perMonth} · ${formattedYearly.value} ${perYear}`,
|
|
658
|
+
...style.changed,
|
|
625
659
|
});
|
|
626
660
|
}
|
|
627
661
|
|
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
<header class="pve-bar">
|
|
3
3
|
<div class="pve-bar-left">
|
|
4
4
|
<div class="pve-titlechip">
|
|
5
|
-
<span class="pve-titlechip-kicker">
|
|
5
|
+
<span class="pve-titlechip-kicker">{{ msg.header.planKicker }}</span>
|
|
6
6
|
<span class="pve-chip pve-chip--plan">{{ planKey }}</span>
|
|
7
7
|
</div>
|
|
8
|
-
<h2 class="pve-title">
|
|
9
|
-
|
|
10
|
-
</h2>
|
|
11
|
-
<span class="pve-chip pve-chip--draft pve-chip--dot">Draft</span>
|
|
8
|
+
<h2 class="pve-title">{{ title }}</h2>
|
|
9
|
+
<span class="pve-chip pve-chip--draft pve-chip--dot">{{ msg.header.draftChip }}</span>
|
|
12
10
|
<span class="pve-bar-note">
|
|
13
|
-
|
|
14
|
-
<code class="pve-mono">{{ predecessorValidUntilHint }}</code>
|
|
11
|
+
{{ msg.header.supersedeNoteBefore }}
|
|
12
|
+
<code class="pve-mono">{{ predecessorValidUntilHint }}</code>
|
|
13
|
+
{{ msg.header.supersedeNoteAfter }}
|
|
15
14
|
</span>
|
|
16
15
|
</div>
|
|
17
16
|
<div class="pve-bar-right">
|
|
@@ -19,7 +18,7 @@
|
|
|
19
18
|
class="pve-btn"
|
|
20
19
|
type="button"
|
|
21
20
|
:disabled="!hasPredecessor"
|
|
22
|
-
:title="hasPredecessor ? undefined :
|
|
21
|
+
:title="hasPredecessor ? undefined : msg.header.noPredecessorHint"
|
|
23
22
|
@click="$emit('showDiff')"
|
|
24
23
|
>
|
|
25
24
|
<span class="pve-ico" aria-hidden="true">
|
|
@@ -35,7 +34,7 @@
|
|
|
35
34
|
<circle cx="12" cy="12" r="3" />
|
|
36
35
|
</svg>
|
|
37
36
|
</span>
|
|
38
|
-
<span>
|
|
37
|
+
<span>{{ msg.header.diffButton }}</span>
|
|
39
38
|
</button>
|
|
40
39
|
<button class="pve-btn" type="button" @click="$emit('cancel')">
|
|
41
40
|
<span class="pve-ico" style="transform: rotate(180deg)" aria-hidden="true">
|
|
@@ -50,7 +49,7 @@
|
|
|
50
49
|
<path d="M5 12h14M13 5l7 7-7 7" />
|
|
51
50
|
</svg>
|
|
52
51
|
</span>
|
|
53
|
-
<span>
|
|
52
|
+
<span>{{ common.back }}</span>
|
|
54
53
|
</button>
|
|
55
54
|
<button
|
|
56
55
|
class="pve-btn pve-btn--primary"
|
|
@@ -58,7 +57,7 @@
|
|
|
58
57
|
:disabled="!canSave"
|
|
59
58
|
@click="$emit('save')"
|
|
60
59
|
>
|
|
61
|
-
<span>
|
|
60
|
+
<span>{{ msg.header.saveButton }}</span>
|
|
62
61
|
<span class="pve-ico" aria-hidden="true">
|
|
63
62
|
<svg
|
|
64
63
|
width="14"
|
|
@@ -77,7 +76,11 @@
|
|
|
77
76
|
</template>
|
|
78
77
|
|
|
79
78
|
<script setup lang="ts">
|
|
80
|
-
|
|
79
|
+
import { computed } from 'vue';
|
|
80
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
81
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
82
|
+
|
|
83
|
+
const props = defineProps<{
|
|
81
84
|
planKey: string;
|
|
82
85
|
editingId: string | null;
|
|
83
86
|
version: number;
|
|
@@ -91,4 +94,13 @@ defineEmits<{
|
|
|
91
94
|
(e: 'cancel'): void;
|
|
92
95
|
(e: 'save'): void;
|
|
93
96
|
}>();
|
|
97
|
+
|
|
98
|
+
const msg = useSaMessages('planEditor');
|
|
99
|
+
const common = useSaMessages('common');
|
|
100
|
+
|
|
101
|
+
const title = computed(() =>
|
|
102
|
+
formatMessage(props.editingId ? msg.value.header.editDraft : msg.value.header.newVersion, {
|
|
103
|
+
version: props.version,
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
94
106
|
</script>
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export * from './client/index.js';
|
|
|
32
32
|
export * from './vue/super-admin-context.js';
|
|
33
33
|
export * from './vue/ui-notify.js';
|
|
34
34
|
export * from './vue/use-super-admin-context.js';
|
|
35
|
+
export * from './vue/use-super-admin-i18n.js';
|
|
35
36
|
|
|
36
37
|
// ---------------------------------------------------------------------------
|
|
37
38
|
// Vue layer: composables, guards, hosts, store factory.
|
|
@@ -58,7 +59,6 @@ export * from './vue/use-catalog-entries.js';
|
|
|
58
59
|
export * from './vue/use-bundles.js';
|
|
59
60
|
export * from './vue/use-bundle-versions-map.js';
|
|
60
61
|
export * from './vue/use-tenant-subscription-bundles.js';
|
|
61
|
-
export * from './vue/use-business-types.js';
|
|
62
62
|
export * from './vue/use-marketing-projections.js';
|
|
63
63
|
export * from './vue/use-promotions.js';
|
|
64
64
|
export * from './vue/use-plans.js';
|