@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
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
<section class="pc-card">
|
|
3
3
|
<div class="pc-card-head">
|
|
4
4
|
<div class="pc-card-head-text">
|
|
5
|
-
<div class="pc-card-title">
|
|
6
|
-
<template v-if="diffPair">
|
|
7
|
-
Diff v{{ diffPair.from.version }} → v{{ diffPair.to.version }}
|
|
8
|
-
</template>
|
|
9
|
-
<template v-else>Diff</template>
|
|
10
|
-
</div>
|
|
5
|
+
<div class="pc-card-title">{{ panelTitle }}</div>
|
|
11
6
|
<div class="pc-card-sub">
|
|
12
|
-
{{ diffPair ?
|
|
7
|
+
{{ diffPair ? msg.diff.subOnPublish : msg.diff.noComparison }}
|
|
13
8
|
</div>
|
|
14
9
|
</div>
|
|
15
10
|
<div v-if="diffPair" class="pc-pillrow">
|
|
@@ -74,18 +69,32 @@
|
|
|
74
69
|
</span>
|
|
75
70
|
</div>
|
|
76
71
|
<div v-if="diffRows.length === 0" class="pc-empty pc-empty--inline">
|
|
77
|
-
|
|
72
|
+
{{ msg.diff.noChangesDetected }}
|
|
78
73
|
</div>
|
|
79
74
|
</div>
|
|
80
75
|
</section>
|
|
81
76
|
</template>
|
|
82
77
|
|
|
83
78
|
<script setup lang="ts">
|
|
79
|
+
import { computed } from 'vue';
|
|
80
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
81
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
84
82
|
import type { DiffRow, DiffSummary, PlanVersionPair } from './types';
|
|
85
83
|
|
|
86
|
-
defineProps<{
|
|
84
|
+
const props = defineProps<{
|
|
87
85
|
diffPair: PlanVersionPair | null;
|
|
88
86
|
diff: DiffSummary;
|
|
89
87
|
diffRows: DiffRow[];
|
|
90
88
|
}>();
|
|
89
|
+
|
|
90
|
+
const msg = useSaMessages('planDetail');
|
|
91
|
+
|
|
92
|
+
const panelTitle = computed(() => {
|
|
93
|
+
const pair = props.diffPair;
|
|
94
|
+
if (!pair) return msg.value.diff.title;
|
|
95
|
+
return formatMessage(msg.value.diff.titleCompare, {
|
|
96
|
+
from: pair.from.version,
|
|
97
|
+
to: pair.to.version,
|
|
98
|
+
});
|
|
99
|
+
});
|
|
91
100
|
</script>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<path d="M5 12h14M13 5l7 7-7 7" />
|
|
14
14
|
</svg>
|
|
15
15
|
</span>
|
|
16
|
-
<span>
|
|
16
|
+
<span>{{ msg.header.backToList }}</span>
|
|
17
17
|
</button>
|
|
18
18
|
</div>
|
|
19
19
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<div class="pc-header-titles">
|
|
26
26
|
<h2 class="pc-h-title">{{ plan.label }}</h2>
|
|
27
|
-
<p class="pc-h-sub">{{ plan.description ||
|
|
27
|
+
<p class="pc-h-sub">{{ plan.description || msg.header.noDescription }}</p>
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
30
|
<div class="pc-header-right">
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<rect x="9" y="9" width="11" height="11" rx="2" />
|
|
41
41
|
<path d="M5 15V5a2 2 0 012-2h10" />
|
|
42
42
|
</svg>
|
|
43
|
-
<span>
|
|
43
|
+
<span>{{ msg.header.clonePlan }}</span>
|
|
44
44
|
</button>
|
|
45
45
|
<button class="pc-btn" type="button" @click="emit('viewCatalog')">
|
|
46
46
|
<svg
|
|
@@ -54,13 +54,13 @@
|
|
|
54
54
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
|
55
55
|
<circle cx="12" cy="12" r="3" />
|
|
56
56
|
</svg>
|
|
57
|
-
<span>
|
|
57
|
+
<span>{{ msg.header.viewInCatalog }}</span>
|
|
58
58
|
</button>
|
|
59
59
|
<button
|
|
60
60
|
class="pc-btn pc-btn--primary"
|
|
61
61
|
type="button"
|
|
62
62
|
:disabled="hasOpenDraft"
|
|
63
|
-
:title="hasOpenDraft ?
|
|
63
|
+
:title="hasOpenDraft ? msg.header.draftAlreadyOpen : undefined"
|
|
64
64
|
@click="emit('createDraft')"
|
|
65
65
|
>
|
|
66
66
|
<svg
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
>
|
|
74
74
|
<path d="M12 5v14M5 12h14" />
|
|
75
75
|
</svg>
|
|
76
|
-
<span>
|
|
76
|
+
<span>{{ msg.header.newDraftVersion }}</span>
|
|
77
77
|
</button>
|
|
78
78
|
</div>
|
|
79
79
|
</div>
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
|
|
82
82
|
<script setup lang="ts">
|
|
83
83
|
import type { PlanRow } from '@saasicat/types';
|
|
84
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
84
85
|
|
|
85
86
|
defineProps<{
|
|
86
87
|
plan: PlanRow;
|
|
@@ -95,4 +96,6 @@ const emit = defineEmits<{
|
|
|
95
96
|
(e: 'viewCatalog'): void;
|
|
96
97
|
(e: 'createDraft'): void;
|
|
97
98
|
}>();
|
|
99
|
+
|
|
100
|
+
const msg = useSaMessages('planDetail');
|
|
98
101
|
</script>
|
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
<section v-if="tenantImpact" class="pc-card">
|
|
3
3
|
<div class="pc-card-head">
|
|
4
4
|
<div class="pc-card-head-text">
|
|
5
|
-
<div class="pc-card-title">
|
|
6
|
-
<div class="pc-card-sub">
|
|
7
|
-
Wer ist betroffen, wenn die nächste Version published wird
|
|
8
|
-
</div>
|
|
5
|
+
<div class="pc-card-title">{{ msg.kpis.tenantImpact }}</div>
|
|
6
|
+
<div class="pc-card-sub">{{ msg.impact.subtitle }}</div>
|
|
9
7
|
</div>
|
|
10
8
|
</div>
|
|
11
9
|
<div class="pc-impact-strip">
|
|
@@ -19,7 +17,7 @@
|
|
|
19
17
|
background: '#10b981',
|
|
20
18
|
}"
|
|
21
19
|
/>
|
|
22
|
-
<span class="pc-impact-bar-label">{{
|
|
20
|
+
<span class="pc-impact-bar-label">{{ autoLabel }}</span>
|
|
23
21
|
</div>
|
|
24
22
|
<div class="pc-impact-bar">
|
|
25
23
|
<span
|
|
@@ -29,9 +27,7 @@
|
|
|
29
27
|
background: '#f59e0b',
|
|
30
28
|
}"
|
|
31
29
|
/>
|
|
32
|
-
<span class="pc-impact-bar-label"
|
|
33
|
-
>{{ tenantImpact.review }} brauchen Review</span
|
|
34
|
-
>
|
|
30
|
+
<span class="pc-impact-bar-label">{{ reviewLabel }}</span>
|
|
35
31
|
</div>
|
|
36
32
|
<div class="pc-impact-bar">
|
|
37
33
|
<span
|
|
@@ -41,7 +37,7 @@
|
|
|
41
37
|
background: '#ef4444',
|
|
42
38
|
}"
|
|
43
39
|
/>
|
|
44
|
-
<span class="pc-impact-bar-label">{{
|
|
40
|
+
<span class="pc-impact-bar-label">{{ conflictLabel }}</span>
|
|
45
41
|
</div>
|
|
46
42
|
</div>
|
|
47
43
|
</div>
|
|
@@ -64,13 +60,28 @@
|
|
|
64
60
|
</template>
|
|
65
61
|
|
|
66
62
|
<script setup lang="ts">
|
|
63
|
+
import { computed } from 'vue';
|
|
64
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
65
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
67
66
|
import type { ImpactTenant, TenantImpact } from './types';
|
|
68
67
|
|
|
69
|
-
defineProps<{
|
|
68
|
+
const props = defineProps<{
|
|
70
69
|
tenantImpact: TenantImpact | null;
|
|
71
70
|
tenantImpactTotal: number;
|
|
72
71
|
}>();
|
|
73
72
|
|
|
73
|
+
const msg = useSaMessages('planDetail');
|
|
74
|
+
|
|
75
|
+
const autoLabel = computed(() =>
|
|
76
|
+
formatMessage(msg.value.impact.autoMigrate, { count: props.tenantImpact?.auto ?? 0 }),
|
|
77
|
+
);
|
|
78
|
+
const reviewLabel = computed(() =>
|
|
79
|
+
formatMessage(msg.value.impact.needReview, { count: props.tenantImpact?.review ?? 0 }),
|
|
80
|
+
);
|
|
81
|
+
const conflictLabel = computed(() =>
|
|
82
|
+
formatMessage(msg.value.impact.conflict, { count: props.tenantImpact?.conflict ?? 0 }),
|
|
83
|
+
);
|
|
84
|
+
|
|
74
85
|
function pct(part: number, total: number): string {
|
|
75
86
|
if (total <= 0) return '0%';
|
|
76
87
|
return `${Math.max(2, Math.round((part / total) * 100))}%`;
|
|
@@ -86,7 +97,9 @@ function initialsOf(name: string): string {
|
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
function impactStateLabel(state: ImpactTenant['state']): string {
|
|
89
|
-
|
|
100
|
+
if (state === 'auto') return msg.value.impact.stateAuto;
|
|
101
|
+
if (state === 'review') return msg.value.impact.stateReview;
|
|
102
|
+
return msg.value.impact.stateConflict;
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
function impactStateChip(state: ImpactTenant['state']): string {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="pc-kpis">
|
|
3
3
|
<div class="pc-kpi">
|
|
4
|
-
<div class="pc-kpi-label">
|
|
4
|
+
<div class="pc-kpi-label">{{ msg.kpis.activeVersion }}</div>
|
|
5
5
|
<div class="pc-kpi-val">
|
|
6
6
|
<template v-if="liveVersion">
|
|
7
7
|
v{{ liveVersion.version }}
|
|
@@ -9,44 +9,39 @@
|
|
|
9
9
|
</template>
|
|
10
10
|
<template v-else>—</template>
|
|
11
11
|
</div>
|
|
12
|
-
<div class="pc-kpi-sub">
|
|
13
|
-
{{
|
|
14
|
-
liveVersion?.validFrom
|
|
15
|
-
? `seit ${liveVersion.validFrom.slice(0, 10)}`
|
|
16
|
-
: 'keine veröffentlichte Version'
|
|
17
|
-
}}
|
|
18
|
-
</div>
|
|
12
|
+
<div class="pc-kpi-sub">{{ activeVersionSub }}</div>
|
|
19
13
|
</div>
|
|
20
14
|
<div class="pc-kpi">
|
|
21
|
-
<div class="pc-kpi-label">
|
|
15
|
+
<div class="pc-kpi-label">{{ msg.kpis.tenantImpact }}</div>
|
|
22
16
|
<div class="pc-kpi-val">{{ tenantImpactTotal }}</div>
|
|
23
|
-
<div class="pc-kpi-sub">
|
|
17
|
+
<div class="pc-kpi-sub">{{ msg.kpis.tenantsOnPlan }}</div>
|
|
24
18
|
</div>
|
|
25
19
|
<div class="pc-kpi">
|
|
26
|
-
<div class="pc-kpi-label">
|
|
20
|
+
<div class="pc-kpi-label">{{ msg.kpis.versions }}</div>
|
|
27
21
|
<div class="pc-kpi-val">{{ versionsCount }}</div>
|
|
28
|
-
<div class="pc-kpi-sub">
|
|
29
|
-
{{ publishedCount }} live/superseded · {{ draftCount }} Draft
|
|
30
|
-
</div>
|
|
22
|
+
<div class="pc-kpi-sub">{{ versionsSummary }}</div>
|
|
31
23
|
</div>
|
|
32
24
|
<div :class="['pc-kpi', draftVersion ? 'pc-kpi--draft' : '']">
|
|
33
25
|
<div class="pc-kpi-label" :style="draftVersion ? 'color:#b45309' : ''">
|
|
34
|
-
{{ draftVersion ?
|
|
26
|
+
{{ draftVersion ? msg.kpis.openDraft : msg.kpis.noDraft }}
|
|
35
27
|
</div>
|
|
36
28
|
<div class="pc-kpi-val">
|
|
37
29
|
{{ draftVersion ? `v${draftVersion.version}` : '—' }}
|
|
38
30
|
</div>
|
|
39
31
|
<div class="pc-kpi-sub" :style="draftVersion ? 'color:#b45309' : ''">
|
|
40
|
-
{{ draftVersion ?
|
|
32
|
+
{{ draftVersion ? msg.kpis.readyToEdit : msg.kpis.createNewDraft }}
|
|
41
33
|
</div>
|
|
42
34
|
</div>
|
|
43
35
|
</div>
|
|
44
36
|
</template>
|
|
45
37
|
|
|
46
38
|
<script setup lang="ts">
|
|
39
|
+
import { computed } from 'vue';
|
|
47
40
|
import type { PlanVersionRow } from '@saasicat/types';
|
|
41
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
42
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
48
43
|
|
|
49
|
-
defineProps<{
|
|
44
|
+
const props = defineProps<{
|
|
50
45
|
liveVersion: PlanVersionRow | null;
|
|
51
46
|
tenantImpactTotal: number;
|
|
52
47
|
versionsCount: number;
|
|
@@ -54,4 +49,20 @@ defineProps<{
|
|
|
54
49
|
draftCount: number;
|
|
55
50
|
draftVersion: PlanVersionRow | null;
|
|
56
51
|
}>();
|
|
52
|
+
|
|
53
|
+
const msg = useSaMessages('planDetail');
|
|
54
|
+
|
|
55
|
+
const activeVersionSub = computed(() => {
|
|
56
|
+
const validFrom = props.liveVersion?.validFrom;
|
|
57
|
+
return validFrom
|
|
58
|
+
? formatMessage(msg.value.kpis.activeSince, { date: validFrom.slice(0, 10) })
|
|
59
|
+
: msg.value.kpis.noPublishedVersion;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const versionsSummary = computed(() =>
|
|
63
|
+
formatMessage(msg.value.kpis.versionsSummaryWithDrafts, {
|
|
64
|
+
published: props.publishedCount,
|
|
65
|
+
drafts: props.draftCount,
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
57
68
|
</script>
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
<section class="pc-card pc-card--left">
|
|
3
3
|
<div class="pc-card-head">
|
|
4
4
|
<div class="pc-card-head-text">
|
|
5
|
-
<div class="pc-card-title">
|
|
5
|
+
<div class="pc-card-title">{{ msg.versions.title }}</div>
|
|
6
6
|
<div class="pc-card-sub">
|
|
7
|
-
|
|
7
|
+
{{ msg.versions.subtitle }}
|
|
8
8
|
<code class="pc-mono">supersededAt</code>
|
|
9
9
|
</div>
|
|
10
10
|
</div>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
>
|
|
25
25
|
<path d="M12 5v14M5 12h14" />
|
|
26
26
|
</svg>
|
|
27
|
-
<span>
|
|
27
|
+
<span>{{ newDraftButtonLabel }}</span>
|
|
28
28
|
</button>
|
|
29
29
|
</div>
|
|
30
30
|
|
|
@@ -36,7 +36,10 @@
|
|
|
36
36
|
:class="['pc-vtl-seg', `pc-vtl-${seg.status}`]"
|
|
37
37
|
:style="{ flex: seg.flex }"
|
|
38
38
|
>
|
|
39
|
-
v{{ seg.version
|
|
39
|
+
v{{ seg.version
|
|
40
|
+
}}<template v-if="seg.status === 'draft'">{{
|
|
41
|
+
msg.versions.timelineDraftSuffix
|
|
42
|
+
}}</template>
|
|
40
43
|
</div>
|
|
41
44
|
</div>
|
|
42
45
|
<div class="pc-vtl-axis">
|
|
@@ -45,15 +48,19 @@
|
|
|
45
48
|
</div>
|
|
46
49
|
|
|
47
50
|
<div class="pc-vrow pc-vrow--head">
|
|
48
|
-
<div class="pc-vrow-version pc-vrow-headlabel">
|
|
49
|
-
<div class="pc-vrow-cell pc-vrow-headlabel">
|
|
50
|
-
<div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
<div class="pc-vrow-version pc-vrow-headlabel">{{ msg.versions.colVersion }}</div>
|
|
52
|
+
<div class="pc-vrow-cell pc-vrow-headlabel">{{ msg.versions.colValidity }}</div>
|
|
53
|
+
<div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">
|
|
54
|
+
{{ msg.versions.colPricing }}
|
|
55
|
+
</div>
|
|
56
|
+
<div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">
|
|
57
|
+
{{ msg.versions.colImpact }}
|
|
58
|
+
</div>
|
|
59
|
+
<div class="pc-vrow-note pc-vrow-headlabel">{{ msg.versions.colChangeNoteShort }}</div>
|
|
53
60
|
<div class="pc-vrow-actions" />
|
|
54
61
|
</div>
|
|
55
62
|
<div v-if="versions.length === 0" class="pc-empty">
|
|
56
|
-
|
|
63
|
+
{{ msg.versions.empty }}
|
|
57
64
|
</div>
|
|
58
65
|
<div
|
|
59
66
|
v-for="row in sortedVersions"
|
|
@@ -77,9 +84,12 @@
|
|
|
77
84
|
</div>
|
|
78
85
|
<div class="pc-vrow-cell pc-vrow-cell--right">
|
|
79
86
|
<div class="pc-vrow-price">
|
|
80
|
-
{{ formatMoney(row.monthlyNet)
|
|
87
|
+
{{ formatMoney(row.monthlyNet)
|
|
88
|
+
}}<span class="pc-vrow-price-unit">{{ msg.versions.perMonthUnit }}</span>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="pc-vrow-sub">
|
|
91
|
+
{{ formatMoney(row.yearlyNet) }} {{ msg.versions.perYearUnit }}
|
|
81
92
|
</div>
|
|
82
|
-
<div class="pc-vrow-sub">{{ formatMoney(row.yearlyNet) }} / Jahr</div>
|
|
83
93
|
</div>
|
|
84
94
|
<div class="pc-vrow-cell pc-vrow-cell--right">
|
|
85
95
|
<div
|
|
@@ -88,7 +98,7 @@
|
|
|
88
98
|
>
|
|
89
99
|
{{ impactByVersion[row.version] ?? 0 }}
|
|
90
100
|
</div>
|
|
91
|
-
<div class="pc-vrow-sub">
|
|
101
|
+
<div class="pc-vrow-sub">{{ msg.versions.tenants }}</div>
|
|
92
102
|
</div>
|
|
93
103
|
<div class="pc-vrow-note" :title="row.changeNote ?? ''">
|
|
94
104
|
{{ row.changeNote || '—' }}
|
|
@@ -110,7 +120,7 @@
|
|
|
110
120
|
>
|
|
111
121
|
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
|
|
112
122
|
</svg>
|
|
113
|
-
<span>
|
|
123
|
+
<span>{{ msg.versions.publish }}</span>
|
|
114
124
|
</button>
|
|
115
125
|
<button
|
|
116
126
|
v-if="editabilityOf(row).editable"
|
|
@@ -118,8 +128,8 @@
|
|
|
118
128
|
type="button"
|
|
119
129
|
:title="
|
|
120
130
|
editabilityOf(row).reason === 'pre-active'
|
|
121
|
-
?
|
|
122
|
-
:
|
|
131
|
+
? msg.versions.preActiveTitle
|
|
132
|
+
: msg.versions.editDraftTitle
|
|
123
133
|
"
|
|
124
134
|
@click="emit('editDraft', row)"
|
|
125
135
|
>
|
|
@@ -133,7 +143,7 @@
|
|
|
133
143
|
>
|
|
134
144
|
<path d="M12 20h9M16.5 3.5a2.12 2.12 0 113 3L7 19l-4 1 1-4 12.5-12.5z" />
|
|
135
145
|
</svg>
|
|
136
|
-
<span>
|
|
146
|
+
<span>{{ common.edit }}</span>
|
|
137
147
|
</button>
|
|
138
148
|
<button
|
|
139
149
|
v-if="row.publishedAt !== null && row.supersededAt === null && draftVersion"
|
|
@@ -152,7 +162,7 @@
|
|
|
152
162
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
|
153
163
|
<circle cx="12" cy="12" r="3" />
|
|
154
164
|
</svg>
|
|
155
|
-
<span>
|
|
165
|
+
<span>{{ msg.versions.viewDiff }}</span>
|
|
156
166
|
</button>
|
|
157
167
|
</div>
|
|
158
168
|
</div>
|
|
@@ -160,10 +170,14 @@
|
|
|
160
170
|
</template>
|
|
161
171
|
|
|
162
172
|
<script setup lang="ts">
|
|
173
|
+
import { computed } from 'vue';
|
|
163
174
|
import { isVersionEditable, type PlanVersionRow } from '@saasicat/types';
|
|
175
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
176
|
+
import { formatCurrency } from '../../client/i18n/currency.js';
|
|
177
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
164
178
|
import type { TimelineSegment } from './types';
|
|
165
179
|
|
|
166
|
-
defineProps<{
|
|
180
|
+
const props = defineProps<{
|
|
167
181
|
versions: PlanVersionRow[];
|
|
168
182
|
sortedVersions: PlanVersionRow[];
|
|
169
183
|
timelineSegments: TimelineSegment[];
|
|
@@ -181,6 +195,14 @@ const emit = defineEmits<{
|
|
|
181
195
|
(e: 'viewDiff', from: PlanVersionRow, to: PlanVersionRow): void;
|
|
182
196
|
}>();
|
|
183
197
|
|
|
198
|
+
const msg = useSaMessages('planDetail');
|
|
199
|
+
const { locale } = useSuperAdminI18n();
|
|
200
|
+
const common = useSaMessages('common');
|
|
201
|
+
|
|
202
|
+
const newDraftButtonLabel = computed(() =>
|
|
203
|
+
formatMessage(msg.value.versions.newDraftButton, { version: props.nextDraftVersion }),
|
|
204
|
+
);
|
|
205
|
+
|
|
184
206
|
function editabilityOf(version: PlanVersionRow): {
|
|
185
207
|
editable: boolean;
|
|
186
208
|
reason: 'draft' | 'pre-active' | null;
|
|
@@ -203,8 +225,7 @@ function statusLabel(row: PlanVersionRow): string {
|
|
|
203
225
|
function formatMoney(raw: string | number): string {
|
|
204
226
|
const num = typeof raw === 'string' ? Number(raw) : raw;
|
|
205
227
|
if (!Number.isFinite(num)) return String(raw);
|
|
206
|
-
|
|
207
|
-
return `${num.toFixed(2).replace('.', ',')} €`;
|
|
228
|
+
return formatCurrency(num, locale.value);
|
|
208
229
|
}
|
|
209
230
|
|
|
210
231
|
function updatedLabel(row: PlanVersionRow): string {
|
|
@@ -218,9 +239,13 @@ function updatedLabel(row: PlanVersionRow): string {
|
|
|
218
239
|
if (Number.isNaN(d.getTime())) return '';
|
|
219
240
|
const diffMs = Date.now() - d.getTime();
|
|
220
241
|
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
221
|
-
if (days <= 0) return
|
|
222
|
-
if (days < 30) return
|
|
223
|
-
if (days < 365)
|
|
224
|
-
|
|
242
|
+
if (days <= 0) return msg.value.versions.updatedToday;
|
|
243
|
+
if (days < 30) return formatMessage(msg.value.versions.updatedDaysAgo, { days });
|
|
244
|
+
if (days < 365) {
|
|
245
|
+
return formatMessage(msg.value.versions.updatedMonthsAgo, {
|
|
246
|
+
months: Math.floor(days / 30),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return formatMessage(msg.value.versions.updatedYearsAgo, { years: Math.floor(days / 365) });
|
|
225
250
|
}
|
|
226
251
|
</script>
|
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
<div class="pcd-modal" role="dialog" aria-labelledby="pcd-title">
|
|
9
9
|
<div class="pcd-head">
|
|
10
10
|
<div class="pcd-head-text">
|
|
11
|
-
<div id="pcd-title" class="pcd-title">
|
|
12
|
-
<div class="pcd-sub">
|
|
13
|
-
Plan-Stamm anlegen. Im nächsten Schritt weist du Features, Quotas und
|
|
14
|
-
Bundles zu.
|
|
15
|
-
</div>
|
|
11
|
+
<div id="pcd-title" class="pcd-title">{{ msg.createDialog.title }}</div>
|
|
12
|
+
<div class="pcd-sub">{{ msg.createDialog.subtitle }}</div>
|
|
16
13
|
</div>
|
|
17
|
-
<button
|
|
14
|
+
<button
|
|
15
|
+
class="pcd-close"
|
|
16
|
+
type="button"
|
|
17
|
+
:aria-label="common.close"
|
|
18
|
+
@click="onCancel"
|
|
19
|
+
>
|
|
18
20
|
<svg
|
|
19
21
|
width="14"
|
|
20
22
|
height="14"
|
|
@@ -32,41 +34,43 @@
|
|
|
32
34
|
<div class="pcd-row pcd-row--2col">
|
|
33
35
|
<div class="pcd-field">
|
|
34
36
|
<div class="pcd-field-label">
|
|
35
|
-
|
|
37
|
+
{{ msg.createDialog.labelPlanKey }}
|
|
36
38
|
<span class="pcd-kbd">UNIQUE</span>
|
|
37
39
|
</div>
|
|
38
40
|
<input
|
|
39
41
|
ref="keyInput"
|
|
40
42
|
class="pcd-input"
|
|
41
43
|
:class="{ 'pcd-input--error': keyError }"
|
|
42
|
-
placeholder="
|
|
44
|
+
:placeholder="msg.createDialog.placeholderPlanKey"
|
|
43
45
|
:value="form.planKey"
|
|
44
46
|
@input="onPlanKeyInput"
|
|
45
47
|
/>
|
|
46
48
|
<div v-if="keyError" class="pcd-hint pcd-hint--error">{{ keyError }}</div>
|
|
47
|
-
<div v-else class="pcd-hint">
|
|
48
|
-
Großbuchstaben + Unterstriche · API-stabil, nicht änderbar nach Publish
|
|
49
|
-
</div>
|
|
49
|
+
<div v-else class="pcd-hint">{{ msg.createDialog.hintPlanKey }}</div>
|
|
50
50
|
</div>
|
|
51
51
|
<div class="pcd-field">
|
|
52
|
-
<div class="pcd-field-label">
|
|
53
|
-
<input
|
|
54
|
-
|
|
52
|
+
<div class="pcd-field-label">{{ msg.createDialog.labelDisplayName }}</div>
|
|
53
|
+
<input
|
|
54
|
+
class="pcd-input"
|
|
55
|
+
:placeholder="msg.createDialog.placeholderDisplayName"
|
|
56
|
+
v-model="form.label"
|
|
57
|
+
/>
|
|
58
|
+
<div class="pcd-hint">{{ msg.createDialog.hintDisplayName }}</div>
|
|
55
59
|
</div>
|
|
56
60
|
</div>
|
|
57
61
|
|
|
58
62
|
<div class="pcd-field">
|
|
59
|
-
<div class="pcd-field-label">
|
|
63
|
+
<div class="pcd-field-label">{{ common.description }}</div>
|
|
60
64
|
<textarea
|
|
61
65
|
class="pcd-input pcd-input--textarea"
|
|
62
66
|
rows="2"
|
|
63
|
-
placeholder="
|
|
67
|
+
:placeholder="msg.createDialog.placeholderDescription"
|
|
64
68
|
v-model="form.description"
|
|
65
69
|
/>
|
|
66
70
|
</div>
|
|
67
71
|
|
|
68
72
|
<div class="pcd-field">
|
|
69
|
-
<div class="pcd-field-label">
|
|
73
|
+
<div class="pcd-field-label">{{ msg.createDialog.labelBasis }}</div>
|
|
70
74
|
<div class="pcd-choice-grid">
|
|
71
75
|
<button
|
|
72
76
|
v-for="opt in choiceOptions"
|
|
@@ -87,7 +91,7 @@
|
|
|
87
91
|
|
|
88
92
|
<div class="pcd-foot">
|
|
89
93
|
<button class="pcd-btn pcd-btn--ghost" type="button" @click="onCancel">
|
|
90
|
-
|
|
94
|
+
{{ common.cancel }}
|
|
91
95
|
</button>
|
|
92
96
|
<button
|
|
93
97
|
class="pcd-btn pcd-btn--primary"
|
|
@@ -95,7 +99,9 @@
|
|
|
95
99
|
:disabled="!canSubmit || submitting"
|
|
96
100
|
@click="onSubmit"
|
|
97
101
|
>
|
|
98
|
-
<span>{{
|
|
102
|
+
<span>{{
|
|
103
|
+
submitting ? msg.createDialog.submitting : msg.createDialog.submit
|
|
104
|
+
}}</span>
|
|
99
105
|
<span class="pcd-ico" aria-hidden="true">
|
|
100
106
|
<svg
|
|
101
107
|
width="14"
|
|
@@ -116,6 +122,8 @@
|
|
|
116
122
|
|
|
117
123
|
<script setup lang="ts">
|
|
118
124
|
import { computed, reactive, ref, watch } from 'vue';
|
|
125
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
126
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
119
127
|
|
|
120
128
|
// PlanCreateDialog — step 1 of the "new plan" flow from the plan
|
|
121
129
|
// simulation. Collects the plan master data (key, label, description) plus the
|
|
@@ -168,13 +176,16 @@ const emit = defineEmits<{
|
|
|
168
176
|
(e: 'cancel'): void;
|
|
169
177
|
}>();
|
|
170
178
|
|
|
171
|
-
const
|
|
179
|
+
const msg = useSaMessages('plans');
|
|
180
|
+
const common = useSaMessages('common');
|
|
181
|
+
|
|
182
|
+
const emptyBasis = computed<TemplateOption>(() => ({
|
|
172
183
|
key: 'empty',
|
|
173
|
-
label:
|
|
184
|
+
label: msg.value.createDialog.emptyPlan,
|
|
174
185
|
features: [],
|
|
175
186
|
quotas: {},
|
|
176
187
|
bundles: [],
|
|
177
|
-
};
|
|
188
|
+
}));
|
|
178
189
|
|
|
179
190
|
const form = reactive({
|
|
180
191
|
planKey: '',
|
|
@@ -206,10 +217,10 @@ function onPlanKeyInput(e: Event): void {
|
|
|
206
217
|
const keyError = computed<string | null>(() => {
|
|
207
218
|
if (!form.planKey) return null;
|
|
208
219
|
if (!/^[A-Z][A-Z0-9_]*$/.test(form.planKey)) {
|
|
209
|
-
return
|
|
220
|
+
return msg.value.createDialog.errorKeyFormat;
|
|
210
221
|
}
|
|
211
222
|
if (props.existingPlanKeys.includes(form.planKey)) {
|
|
212
|
-
return
|
|
223
|
+
return formatMessage(msg.value.createDialog.errorKeyExists, { key: form.planKey });
|
|
213
224
|
}
|
|
214
225
|
return null;
|
|
215
226
|
});
|
|
@@ -222,12 +233,12 @@ const choiceOptions = computed(() => {
|
|
|
222
233
|
const options = [
|
|
223
234
|
{
|
|
224
235
|
key: 'empty',
|
|
225
|
-
title:
|
|
226
|
-
subtitle:
|
|
236
|
+
title: msg.value.createDialog.emptyPlan,
|
|
237
|
+
subtitle: msg.value.createDialog.emptyPlanHint,
|
|
227
238
|
},
|
|
228
239
|
...props.availableTemplates.map((t) => ({
|
|
229
240
|
key: t.key,
|
|
230
|
-
title:
|
|
241
|
+
title: formatMessage(msg.value.createDialog.cloneOf, { label: t.label }),
|
|
231
242
|
subtitle: countsLabel(t),
|
|
232
243
|
})),
|
|
233
244
|
];
|
|
@@ -235,17 +246,16 @@ const choiceOptions = computed(() => {
|
|
|
235
246
|
});
|
|
236
247
|
|
|
237
248
|
function countsLabel(t: TemplateOption): string {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return parts.join(' · ');
|
|
249
|
+
return formatMessage(msg.value.createDialog.counts, {
|
|
250
|
+
features: t.features.length,
|
|
251
|
+
quotas: Object.keys(t.quotas).length,
|
|
252
|
+
bundles: t.bundles.length,
|
|
253
|
+
});
|
|
244
254
|
}
|
|
245
255
|
|
|
246
256
|
function selectedTemplate(): TemplateOption {
|
|
247
|
-
if (form.basis === 'empty') return
|
|
248
|
-
return props.availableTemplates.find((t) => t.key === form.basis) ??
|
|
257
|
+
if (form.basis === 'empty') return emptyBasis.value;
|
|
258
|
+
return props.availableTemplates.find((t) => t.key === form.basis) ?? emptyBasis.value;
|
|
249
259
|
}
|
|
250
260
|
|
|
251
261
|
function onSubmit(): void {
|