@saasicat/ui-vue 0.19.0 → 0.22.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.
Files changed (145) hide show
  1. package/dist/{catalog-JXdKdIqG.d.cts → catalog-DJcfm5N2.d.cts} +21 -7
  2. package/dist/{catalog-JXdKdIqG.d.ts → catalog-DJcfm5N2.d.ts} +21 -7
  3. package/dist/{chunk-DPTIOQXX.js → chunk-F7NRM6KI.js} +56 -28
  4. package/dist/{chunk-RVG6LQHL.js → chunk-LY52T4N4.js} +21 -2
  5. package/dist/{chunk-ORX3ZG47.js → chunk-XMMFN2WJ.js} +74 -3
  6. package/dist/client/index.cjs +134 -29
  7. package/dist/client/index.d.cts +53 -3
  8. package/dist/client/index.d.ts +53 -3
  9. package/dist/client/index.js +14 -2
  10. package/dist/index.cjs +321 -162
  11. package/dist/index.d.cts +30 -8
  12. package/dist/index.d.ts +30 -8
  13. package/dist/index.js +176 -133
  14. package/dist/quasar/index.cjs +78 -29
  15. package/dist/quasar/index.d.cts +2 -2
  16. package/dist/quasar/index.d.ts +2 -2
  17. package/dist/quasar/index.js +6 -2
  18. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  19. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  20. package/dist/{use-super-admin-i18n-BlqcD0N7.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
  21. package/dist/{use-super-admin-i18n-1nCOkvgi.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
  22. package/package.json +18 -8
  23. package/src/client/i18n/messages/bundles.ts +0 -2
  24. package/src/client/i18n/messages/common.ts +24 -0
  25. package/src/client/i18n/messages/discovery.ts +2 -2
  26. package/src/client/i18n/messages/marketing.ts +2 -4
  27. package/src/client/i18n/messages/plan-detail.ts +0 -4
  28. package/src/client/i18n/messages/plans.ts +20 -8
  29. package/src/client/i18n/messages/tenants.ts +0 -2
  30. package/src/client/index.ts +1 -0
  31. package/src/client/manifest-loader.ts +1 -1
  32. package/src/client/resolve-plans.ts +144 -0
  33. package/src/components/BundleVersionPublishDialog.vue +6 -6
  34. package/src/components/KvBlock.vue +4 -4
  35. package/src/components/MarketingPromotionsTab.vue +34 -48
  36. package/src/components/MfaPromptDialog.vue +8 -2
  37. package/src/components/TenantActionConfirmDialog.vue +4 -4
  38. package/src/components/VersionDiffPreview.vue +7 -7
  39. package/src/components/admin-page/AdminBody.vue +40 -0
  40. package/src/components/admin-page/AdminFilters.vue +12 -0
  41. package/src/components/admin-page/AdminHero.vue +49 -0
  42. package/src/components/admin-page/AdminKpi.vue +84 -0
  43. package/src/components/admin-page/AdminPage.vue +16 -0
  44. package/src/components/admin-page/AdminPaginator.vue +195 -0
  45. package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
  46. package/src/components/admin-page/AdminSection.vue +53 -0
  47. package/src/components/admin-page/AdminStatistics.vue +40 -0
  48. package/src/components/admin-page/AdminTable.vue +175 -0
  49. package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
  50. package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
  51. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
  52. package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
  53. package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
  54. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
  55. package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
  56. package/src/components/dialogs/PilotCreateDialog.vue +12 -12
  57. package/src/components/dialogs/PilotEditDialog.vue +7 -7
  58. package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
  59. package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
  60. package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
  61. package/src/components/plan/PlanGrid.vue +1 -1
  62. package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
  63. package/src/components/plan-detail/PlanAuditLog.vue +7 -8
  64. package/src/components/plan-detail/PlanDetail.vue +95 -292
  65. package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
  66. package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
  67. package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
  68. package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
  69. package/src/components/plan-list/PlanList.vue +206 -457
  70. package/src/components/plan-matrix/PlanMatrix.vue +66 -183
  71. package/src/components/plan-review/PlanReview.vue +191 -275
  72. package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
  73. package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
  74. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
  75. package/src/index.ts +1 -0
  76. package/src/pages-standard/AdminLayout.vue +38 -12
  77. package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
  78. package/src/pages-standard/AuditPage.vue +65 -109
  79. package/src/pages-standard/BundlesPage.vue +146 -179
  80. package/src/pages-standard/DashboardPage.vue +59 -154
  81. package/src/pages-standard/DiscoveryPage.vue +172 -227
  82. package/src/pages-standard/EmailHistoryPage.vue +118 -159
  83. package/src/pages-standard/MarketingCatalogPage.vue +399 -440
  84. package/src/pages-standard/PilotsPage.vue +69 -117
  85. package/src/pages-standard/PlansPage.vue +240 -112
  86. package/src/pages-standard/PlatformEmailPage.vue +56 -91
  87. package/src/pages-standard/PromoCodeDetailPage.vue +49 -74
  88. package/src/pages-standard/PromoCodesPage.vue +95 -128
  89. package/src/pages-standard/SubscriptionsPage.vue +19 -52
  90. package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
  91. package/src/pages-standard/SuperAdminSetupWizard.vue +22 -18
  92. package/src/pages-standard/TenantDetailPage.vue +100 -191
  93. package/src/pages-standard/TenantsPage.vue +190 -295
  94. package/src/pages-standard/UsersPage.vue +89 -157
  95. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
  96. package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
  97. package/src/pages-standard/bundles-page/BundlesToolbar.vue +63 -0
  98. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
  99. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
  100. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
  101. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
  102. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
  103. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +111 -73
  104. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +43 -62
  105. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
  106. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
  107. package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
  108. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
  109. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
  110. package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
  111. package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
  112. package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
  113. package/src/pages-standard/sa-theme.css +552 -86
  114. package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
  115. package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
  116. package/src/pages-standard/tenant-detail/TenantUsers.vue +15 -0
  117. package/src/pages-standard/tenant-detail/types.ts +38 -0
  118. package/src/pages-standard/tenants/StatusPill.vue +2 -2
  119. package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
  120. package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
  121. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  122. package/src/pages-tenant/TenantPlanSection.vue +12 -20
  123. package/src/quasar/create-super-admin-app.ts +4 -0
  124. package/src/vue/entitlement-provider.ts +0 -1
  125. package/src/vue/feature-router-guard.ts +0 -1
  126. package/src/vue/super-admin-context.ts +106 -1
  127. package/src/vue/use-api-list.ts +2 -1
  128. package/src/vue/use-mfa-prompt.ts +72 -0
  129. package/src/vue/use-pagination.ts +63 -0
  130. package/src/vue/use-plans.ts +1 -2
  131. package/src/vue/use-sign-out.ts +90 -0
  132. package/src/vue/use-tenant-billing.ts +1 -1
  133. package/src/vue/use-tenant-manifest.ts +0 -1
  134. package/src/components/KpiCard.vue +0 -53
  135. package/src/components/plan-cockpit/PlanCockpit.vue +0 -1012
  136. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
  137. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -100
  138. package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
  139. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
  140. package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
  141. package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -251
  142. package/src/components/plan-cockpit/types.ts +0 -72
  143. package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
  144. package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
  145. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +0 -34
@@ -1,72 +0,0 @@
1
- <template>
2
- <section v-if="auditRows.length > 0 || loadingAudit" class="pc-card pc-audit-card">
3
- <div class="pc-card-head pc-card-head--audit">
4
- <div class="pc-card-head-text">
5
- <div class="pc-card-title">{{ msg.auditLog.title }}</div>
6
- <div class="pc-card-sub">{{ msg.auditLog.subtitle }}</div>
7
- </div>
8
- </div>
9
- <div class="pc-audit">
10
- <div v-if="loadingAudit" class="pc-empty pc-empty--inline">
11
- {{ msg.auditLog.loading }}
12
- </div>
13
- <div v-for="a in auditRows" :key="a.id" class="pc-audit-row">
14
- <span :class="['pc-audit-dot', `pc-audit-${auditKind(a.action)}`]" />
15
- <span class="pc-audit-when">{{ formatAuditDate(a.createdAt) }}</span>
16
- <span class="pc-audit-avatar">{{ auditInitial(a) }}</span>
17
- <span class="pc-audit-who">{{ auditWho(a) }}</span>
18
- <span class="pc-audit-what">{{ auditLabel(a) }}</span>
19
- </div>
20
- </div>
21
- </section>
22
- </template>
23
-
24
- <script setup lang="ts">
25
- import { formatMessage } from '../../client/i18n/format.js';
26
- import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
27
- import type { AuditRow } from './types';
28
-
29
- defineProps<{
30
- auditRows: AuditRow[];
31
- loadingAudit: boolean;
32
- }>();
33
-
34
- const msg = useSaMessages('planDetail');
35
- const { intlLocale } = useSuperAdminI18n();
36
-
37
- function auditKind(action: string): string {
38
- const a = action.toLowerCase();
39
- if (a.includes('publish')) return 'publish';
40
- if (a.includes('create') && a.includes('draft')) return 'draft';
41
- if (a.includes('create')) return 'add';
42
- if (a.includes('update')) return 'change';
43
- if (a.includes('delete')) return 'remove';
44
- return 'change';
45
- }
46
-
47
- function auditWho(a: AuditRow): string {
48
- if (a.user?.firstName || a.user?.lastName) {
49
- return `${a.user.firstName ?? ''} ${a.user.lastName ?? ''}`.trim();
50
- }
51
- return a.user?.email ?? a.userEmail ?? 'system';
52
- }
53
-
54
- function auditInitial(a: AuditRow): string {
55
- const who = auditWho(a);
56
- return who[0]?.toUpperCase() ?? '?';
57
- }
58
-
59
- function auditLabel(a: AuditRow): string {
60
- return a.action;
61
- }
62
-
63
- function formatAuditDate(iso: string): string {
64
- const d = new Date(iso);
65
- if (Number.isNaN(d.getTime())) return iso;
66
- const today = new Date();
67
- const isToday = d.toDateString() === today.toDateString();
68
- const time = d.toLocaleTimeString(intlLocale.value, { hour: '2-digit', minute: '2-digit' });
69
- if (isToday) return formatMessage(msg.value.auditLog.today, { time });
70
- return `${d.toLocaleDateString(intlLocale.value, { day: '2-digit', month: 'short' })} · ${time}`;
71
- }
72
- </script>
@@ -1,100 +0,0 @@
1
- <template>
2
- <section class="pc-card">
3
- <div class="pc-card-head">
4
- <div class="pc-card-head-text">
5
- <div class="pc-card-title">{{ panelTitle }}</div>
6
- <div class="pc-card-sub">
7
- {{ diffPair ? msg.diff.subOnPublish : msg.diff.noComparison }}
8
- </div>
9
- </div>
10
- <div v-if="diffPair" class="pc-pillrow">
11
- <span class="pc-pill" style="background: #ecfdf5; color: #047857">
12
- +{{ diff.addedFeatures.length }} F
13
- </span>
14
- <span class="pc-pill" style="background: #fef2f2; color: #b91c1c">
15
- −{{ diff.removedFeatures.length }} F
16
- </span>
17
- <span class="pc-pill" style="background: #fffbeb; color: #b45309">
18
- {{
19
- diff.changedQuotas.length +
20
- diff.addedQuotas.length +
21
- diff.removedQuotas.length
22
- }}
23
- Q
24
- </span>
25
- </div>
26
- </div>
27
- <div class="pc-diff-body">
28
- <div
29
- v-for="d in diffRows"
30
- :key="d.id"
31
- class="pc-diff-row"
32
- :style="{ background: d.bg, borderColor: d.border }"
33
- >
34
- <span class="pc-diff-sign" :style="{ background: d.color }">{{ d.sign }}</span>
35
- <div class="pc-diff-main">
36
- <div class="pc-diff-headline">
37
- <span class="pc-diff-section" :style="{ color: d.color }">{{
38
- d.section
39
- }}</span>
40
- <span class="pc-diff-label">{{ d.label }}</span>
41
- <code v-if="d.sub" class="pc-mono pc-mono--xs">{{ d.sub }}</code>
42
- </div>
43
- <div v-if="d.from !== undefined" class="pc-diff-change">
44
- <span class="pc-diff-from">{{ d.from }}</span>
45
- <span class="pc-diff-arrow" :style="{ color: d.color }">
46
- <svg
47
- width="12"
48
- height="12"
49
- viewBox="0 0 24 24"
50
- fill="none"
51
- stroke="currentColor"
52
- stroke-width="2"
53
- >
54
- <path d="M5 12h14M13 5l7 7-7 7" />
55
- </svg>
56
- </span>
57
- <span class="pc-diff-to">{{ d.to }}</span>
58
- </div>
59
- </div>
60
- <span
61
- class="pc-chip pc-diff-tag"
62
- :style="{
63
- background: '#fff',
64
- color: d.color,
65
- borderColor: d.border,
66
- }"
67
- >
68
- {{ d.tag }}
69
- </span>
70
- </div>
71
- <div v-if="diffRows.length === 0" class="pc-empty pc-empty--inline">
72
- {{ msg.diff.noChangesDetected }}
73
- </div>
74
- </div>
75
- </section>
76
- </template>
77
-
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';
82
- import type { DiffRow, DiffSummary, PlanVersionPair } from './types';
83
-
84
- const props = defineProps<{
85
- diffPair: PlanVersionPair | null;
86
- diff: DiffSummary;
87
- diffRows: DiffRow[];
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
- });
100
- </script>
@@ -1,101 +0,0 @@
1
- <template>
2
- <div class="pc-backbar">
3
- <button class="pc-back" type="button" @click="emit('back')">
4
- <span class="pc-back-ico" aria-hidden="true">
5
- <svg
6
- width="14"
7
- height="14"
8
- viewBox="0 0 24 24"
9
- fill="none"
10
- stroke="currentColor"
11
- stroke-width="2"
12
- >
13
- <path d="M5 12h14M13 5l7 7-7 7" />
14
- </svg>
15
- </span>
16
- <span>{{ msg.header.backToList }}</span>
17
- </button>
18
- </div>
19
-
20
- <div class="pc-header">
21
- <div class="pc-header-left">
22
- <div class="pc-bigchip" :style="{ background: accentBg, color: accentFg }">
23
- {{ plan.planKey }}
24
- </div>
25
- <div class="pc-header-titles">
26
- <h2 class="pc-h-title">{{ plan.label }}</h2>
27
- <p class="pc-h-sub">{{ plan.description || msg.header.noDescription }}</p>
28
- </div>
29
- </div>
30
- <div class="pc-header-right">
31
- <button class="pc-btn" type="button" @click="emit('clonePlan')">
32
- <svg
33
- width="14"
34
- height="14"
35
- viewBox="0 0 24 24"
36
- fill="none"
37
- stroke="currentColor"
38
- stroke-width="2"
39
- >
40
- <rect x="9" y="9" width="11" height="11" rx="2" />
41
- <path d="M5 15V5a2 2 0 012-2h10" />
42
- </svg>
43
- <span>{{ msg.header.clonePlan }}</span>
44
- </button>
45
- <button class="pc-btn" type="button" @click="emit('viewCatalog')">
46
- <svg
47
- width="14"
48
- height="14"
49
- viewBox="0 0 24 24"
50
- fill="none"
51
- stroke="currentColor"
52
- stroke-width="2"
53
- >
54
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
55
- <circle cx="12" cy="12" r="3" />
56
- </svg>
57
- <span>{{ msg.header.viewInCatalog }}</span>
58
- </button>
59
- <button
60
- class="pc-btn pc-btn--primary"
61
- type="button"
62
- :disabled="hasOpenDraft"
63
- :title="hasOpenDraft ? msg.header.draftAlreadyOpen : undefined"
64
- @click="emit('createDraft')"
65
- >
66
- <svg
67
- width="14"
68
- height="14"
69
- viewBox="0 0 24 24"
70
- fill="none"
71
- stroke="currentColor"
72
- stroke-width="2.5"
73
- >
74
- <path d="M12 5v14M5 12h14" />
75
- </svg>
76
- <span>{{ msg.header.newDraftVersion }}</span>
77
- </button>
78
- </div>
79
- </div>
80
- </template>
81
-
82
- <script setup lang="ts">
83
- import type { PlanRow } from '@saasicat/types';
84
- import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
85
-
86
- defineProps<{
87
- plan: PlanRow;
88
- accentBg: string;
89
- accentFg: string;
90
- hasOpenDraft: boolean;
91
- }>();
92
-
93
- const emit = defineEmits<{
94
- (e: 'back'): void;
95
- (e: 'clonePlan'): void;
96
- (e: 'viewCatalog'): void;
97
- (e: 'createDraft'): void;
98
- }>();
99
-
100
- const msg = useSaMessages('planDetail');
101
- </script>
@@ -1,112 +0,0 @@
1
- <template>
2
- <section v-if="tenantImpact" class="pc-card">
3
- <div class="pc-card-head">
4
- <div class="pc-card-head-text">
5
- <div class="pc-card-title">{{ msg.kpis.tenantImpact }}</div>
6
- <div class="pc-card-sub">{{ msg.impact.subtitle }}</div>
7
- </div>
8
- </div>
9
- <div class="pc-impact-strip">
10
- <div class="pc-impact-num">{{ tenantImpactTotal }}</div>
11
- <div class="pc-impact-bars">
12
- <div class="pc-impact-bar">
13
- <span
14
- class="pc-impact-bar-fill"
15
- :style="{
16
- width: pct(tenantImpact.auto, tenantImpactTotal),
17
- background: '#10b981',
18
- }"
19
- />
20
- <span class="pc-impact-bar-label">{{ autoLabel }}</span>
21
- </div>
22
- <div class="pc-impact-bar">
23
- <span
24
- class="pc-impact-bar-fill"
25
- :style="{
26
- width: pct(tenantImpact.review, tenantImpactTotal),
27
- background: '#f59e0b',
28
- }"
29
- />
30
- <span class="pc-impact-bar-label">{{ reviewLabel }}</span>
31
- </div>
32
- <div class="pc-impact-bar">
33
- <span
34
- class="pc-impact-bar-fill"
35
- :style="{
36
- width: pct(tenantImpact.conflict, tenantImpactTotal),
37
- background: '#ef4444',
38
- }"
39
- />
40
- <span class="pc-impact-bar-label">{{ conflictLabel }}</span>
41
- </div>
42
- </div>
43
- </div>
44
- <div
45
- v-if="tenantImpact.examples && tenantImpact.examples.length > 0"
46
- class="pc-impact-tenants"
47
- >
48
- <div v-for="t in tenantImpact.examples" :key="t.name" class="pc-impact-tenant">
49
- <div class="pc-impact-avatar">{{ initialsOf(t.name) }}</div>
50
- <div class="pc-impact-body">
51
- <div class="pc-impact-name">{{ t.name }}</div>
52
- <div class="pc-impact-sub">{{ t.plan }}</div>
53
- </div>
54
- <span :class="['pc-chip', impactStateChip(t.state)]">{{
55
- impactStateLabel(t.state)
56
- }}</span>
57
- </div>
58
- </div>
59
- </section>
60
- </template>
61
-
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';
66
- import type { ImpactTenant, TenantImpact } from './types';
67
-
68
- const props = defineProps<{
69
- tenantImpact: TenantImpact | null;
70
- tenantImpactTotal: number;
71
- }>();
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
-
85
- function pct(part: number, total: number): string {
86
- if (total <= 0) return '0%';
87
- return `${Math.max(2, Math.round((part / total) * 100))}%`;
88
- }
89
-
90
- function initialsOf(name: string): string {
91
- return name
92
- .split(/\s+/)
93
- .filter(Boolean)
94
- .slice(0, 2)
95
- .map((s) => s[0]?.toUpperCase() ?? '')
96
- .join('');
97
- }
98
-
99
- function impactStateLabel(state: ImpactTenant['state']): string {
100
- if (state === 'auto') return msg.value.impact.stateAuto;
101
- if (state === 'review') return msg.value.impact.stateReview;
102
- return msg.value.impact.stateConflict;
103
- }
104
-
105
- function impactStateChip(state: ImpactTenant['state']): string {
106
- return state === 'auto'
107
- ? 'pc-chip--live'
108
- : state === 'review'
109
- ? 'pc-chip--draft'
110
- : 'pc-chip--conflict';
111
- }
112
- </script>
@@ -1,68 +0,0 @@
1
- <template>
2
- <div class="pc-kpis">
3
- <div class="pc-kpi">
4
- <div class="pc-kpi-label">{{ msg.kpis.activeVersion }}</div>
5
- <div class="pc-kpi-val">
6
- <template v-if="liveVersion">
7
- v{{ liveVersion.version }}
8
- <span class="pc-chip pc-chip--live pc-chip--dot pc-chip--mid">live</span>
9
- </template>
10
- <template v-else>—</template>
11
- </div>
12
- <div class="pc-kpi-sub">{{ activeVersionSub }}</div>
13
- </div>
14
- <div class="pc-kpi">
15
- <div class="pc-kpi-label">{{ msg.kpis.tenantImpact }}</div>
16
- <div class="pc-kpi-val">{{ tenantImpactTotal }}</div>
17
- <div class="pc-kpi-sub">{{ msg.kpis.tenantsOnPlan }}</div>
18
- </div>
19
- <div class="pc-kpi">
20
- <div class="pc-kpi-label">{{ msg.kpis.versions }}</div>
21
- <div class="pc-kpi-val">{{ versionsCount }}</div>
22
- <div class="pc-kpi-sub">{{ versionsSummary }}</div>
23
- </div>
24
- <div :class="['pc-kpi', draftVersion ? 'pc-kpi--draft' : '']">
25
- <div class="pc-kpi-label" :style="draftVersion ? 'color:#b45309' : ''">
26
- {{ draftVersion ? msg.kpis.openDraft : msg.kpis.noDraft }}
27
- </div>
28
- <div class="pc-kpi-val">
29
- {{ draftVersion ? `v${draftVersion.version}` : '—' }}
30
- </div>
31
- <div class="pc-kpi-sub" :style="draftVersion ? 'color:#b45309' : ''">
32
- {{ draftVersion ? msg.kpis.readyToEdit : msg.kpis.createNewDraft }}
33
- </div>
34
- </div>
35
- </div>
36
- </template>
37
-
38
- <script setup lang="ts">
39
- import { computed } from 'vue';
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';
43
-
44
- const props = defineProps<{
45
- liveVersion: PlanVersionRow | null;
46
- tenantImpactTotal: number;
47
- versionsCount: number;
48
- publishedCount: number;
49
- draftCount: number;
50
- draftVersion: PlanVersionRow | null;
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
- );
68
- </script>