@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.
- package/dist/{catalog-JXdKdIqG.d.cts → catalog-DJcfm5N2.d.cts} +21 -7
- package/dist/{catalog-JXdKdIqG.d.ts → catalog-DJcfm5N2.d.ts} +21 -7
- package/dist/{chunk-DPTIOQXX.js → chunk-F7NRM6KI.js} +56 -28
- package/dist/{chunk-RVG6LQHL.js → chunk-LY52T4N4.js} +21 -2
- package/dist/{chunk-ORX3ZG47.js → chunk-XMMFN2WJ.js} +74 -3
- package/dist/client/index.cjs +134 -29
- package/dist/client/index.d.cts +53 -3
- package/dist/client/index.d.ts +53 -3
- package/dist/client/index.js +14 -2
- package/dist/index.cjs +321 -162
- package/dist/index.d.cts +30 -8
- package/dist/index.d.ts +30 -8
- package/dist/index.js +176 -133
- package/dist/quasar/index.cjs +78 -29
- package/dist/quasar/index.d.cts +2 -2
- package/dist/quasar/index.d.ts +2 -2
- package/dist/quasar/index.js +6 -2
- package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
- package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
- package/dist/{use-super-admin-i18n-BlqcD0N7.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
- package/dist/{use-super-admin-i18n-1nCOkvgi.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
- package/package.json +18 -8
- package/src/client/i18n/messages/bundles.ts +0 -2
- package/src/client/i18n/messages/common.ts +24 -0
- package/src/client/i18n/messages/discovery.ts +2 -2
- package/src/client/i18n/messages/marketing.ts +2 -4
- package/src/client/i18n/messages/plan-detail.ts +0 -4
- package/src/client/i18n/messages/plans.ts +20 -8
- package/src/client/i18n/messages/tenants.ts +0 -2
- package/src/client/index.ts +1 -0
- package/src/client/manifest-loader.ts +1 -1
- package/src/client/resolve-plans.ts +144 -0
- package/src/components/BundleVersionPublishDialog.vue +6 -6
- package/src/components/KvBlock.vue +4 -4
- package/src/components/MarketingPromotionsTab.vue +34 -48
- package/src/components/MfaPromptDialog.vue +8 -2
- package/src/components/TenantActionConfirmDialog.vue +4 -4
- package/src/components/VersionDiffPreview.vue +7 -7
- package/src/components/admin-page/AdminBody.vue +40 -0
- package/src/components/admin-page/AdminFilters.vue +12 -0
- package/src/components/admin-page/AdminHero.vue +49 -0
- package/src/components/admin-page/AdminKpi.vue +84 -0
- package/src/components/admin-page/AdminPage.vue +16 -0
- package/src/components/admin-page/AdminPaginator.vue +195 -0
- package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
- package/src/components/admin-page/AdminSection.vue +53 -0
- package/src/components/admin-page/AdminStatistics.vue +40 -0
- package/src/components/admin-page/AdminTable.vue +175 -0
- package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
- package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
- package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
- package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
- package/src/components/dialogs/PilotCreateDialog.vue +12 -12
- package/src/components/dialogs/PilotEditDialog.vue +7 -7
- package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
- package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
- package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
- package/src/components/plan/PlanGrid.vue +1 -1
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
- package/src/components/plan-detail/PlanAuditLog.vue +7 -8
- package/src/components/plan-detail/PlanDetail.vue +95 -292
- package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
- package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
- package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
- package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
- package/src/components/plan-list/PlanList.vue +206 -457
- package/src/components/plan-matrix/PlanMatrix.vue +66 -183
- package/src/components/plan-review/PlanReview.vue +191 -275
- package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
- package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
- package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
- package/src/index.ts +1 -0
- package/src/pages-standard/AdminLayout.vue +38 -12
- package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
- package/src/pages-standard/AuditPage.vue +65 -109
- package/src/pages-standard/BundlesPage.vue +146 -179
- package/src/pages-standard/DashboardPage.vue +59 -154
- package/src/pages-standard/DiscoveryPage.vue +172 -227
- package/src/pages-standard/EmailHistoryPage.vue +118 -159
- package/src/pages-standard/MarketingCatalogPage.vue +399 -440
- package/src/pages-standard/PilotsPage.vue +69 -117
- package/src/pages-standard/PlansPage.vue +240 -112
- package/src/pages-standard/PlatformEmailPage.vue +56 -91
- package/src/pages-standard/PromoCodeDetailPage.vue +49 -74
- package/src/pages-standard/PromoCodesPage.vue +95 -128
- package/src/pages-standard/SubscriptionsPage.vue +19 -52
- package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
- package/src/pages-standard/SuperAdminSetupWizard.vue +22 -18
- package/src/pages-standard/TenantDetailPage.vue +100 -191
- package/src/pages-standard/TenantsPage.vue +190 -295
- package/src/pages-standard/UsersPage.vue +89 -157
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
- package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
- package/src/pages-standard/bundles-page/BundlesToolbar.vue +63 -0
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
- package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +111 -73
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +43 -62
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
- package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
- package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
- package/src/pages-standard/sa-theme.css +552 -86
- package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
- package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
- package/src/pages-standard/tenant-detail/TenantUsers.vue +15 -0
- package/src/pages-standard/tenant-detail/types.ts +38 -0
- package/src/pages-standard/tenants/StatusPill.vue +2 -2
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
- package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
- package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
- package/src/pages-tenant/TenantPlanSection.vue +12 -20
- package/src/quasar/create-super-admin-app.ts +4 -0
- package/src/vue/entitlement-provider.ts +0 -1
- package/src/vue/feature-router-guard.ts +0 -1
- package/src/vue/super-admin-context.ts +106 -1
- package/src/vue/use-api-list.ts +2 -1
- package/src/vue/use-mfa-prompt.ts +72 -0
- package/src/vue/use-pagination.ts +63 -0
- package/src/vue/use-plans.ts +1 -2
- package/src/vue/use-sign-out.ts +90 -0
- package/src/vue/use-tenant-billing.ts +1 -1
- package/src/vue/use-tenant-manifest.ts +0 -1
- package/src/components/KpiCard.vue +0 -53
- package/src/components/plan-cockpit/PlanCockpit.vue +0 -1012
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -100
- package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
- package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
- package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
- package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -251
- package/src/components/plan-cockpit/types.ts +0 -72
- package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
- package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
- package/src/pages-standard/discovery-page/DiscoveryHeader.vue +0 -34
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<AdminPage class="sa-plans">
|
|
3
|
+
<AdminHero v-if="!viewOwnsHero" :title="heroTitle" :subtitle="heroSubtitle">
|
|
4
|
+
<template v-if="mode === 'cockpit' && selectedPlan" #title>
|
|
5
|
+
<PlanTitleEdit
|
|
6
|
+
:plan="selectedPlan"
|
|
7
|
+
:editable="detailDraftVersion !== null"
|
|
8
|
+
@update-plan="onUpdatePlanFromDetail"
|
|
9
|
+
/>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<template v-if="mode === 'list'" #actions>
|
|
13
|
+
<button class="sa-btn" type="button" @click="mode = 'matrix'">
|
|
14
|
+
<q-icon name="grid_view" size="16px" />
|
|
15
|
+
<span>{{ msg.list.matrixView }}</span>
|
|
16
|
+
</button>
|
|
17
|
+
<button class="sa-btn sa-btn--primary" type="button" @click="openCreate">
|
|
18
|
+
<q-icon name="add" size="16px" />
|
|
19
|
+
<span>{{ msg.list.newPlan }}</span>
|
|
20
|
+
</button>
|
|
21
|
+
</template>
|
|
22
|
+
<template v-else-if="mode === 'matrix'" #actions>
|
|
23
|
+
<button class="sa-btn" type="button" @click="mode = 'list'">
|
|
24
|
+
<q-icon name="storefront" size="16px" />
|
|
25
|
+
<span>{{ msg.matrix.catalogPreview }}</span>
|
|
26
|
+
</button>
|
|
27
|
+
<button class="sa-btn sa-btn--primary" type="button" @click="openCreate">
|
|
28
|
+
<q-icon name="add" size="16px" />
|
|
29
|
+
<span>{{ msg.matrix.createPlan }}</span>
|
|
30
|
+
</button>
|
|
31
|
+
</template>
|
|
32
|
+
<template v-else-if="mode === 'cockpit' && selectedPlan" #actions>
|
|
33
|
+
<button class="sa-btn" type="button" @click="onBackToList">
|
|
34
|
+
<q-icon name="arrow_back" size="16px" />
|
|
35
|
+
<span>{{ common.back }}</span>
|
|
36
|
+
</button>
|
|
37
|
+
<button
|
|
38
|
+
v-if="detailIsDeletable"
|
|
39
|
+
class="sa-btn sa-btn--danger"
|
|
40
|
+
type="button"
|
|
41
|
+
:title="planDetailMsg.header.deletePlanTitle"
|
|
42
|
+
@click="onArchiveSelectedPlan"
|
|
43
|
+
>
|
|
44
|
+
<q-icon name="delete_outline" size="16px" />
|
|
45
|
+
<span>{{ planDetailMsg.header.deletePlan }}</span>
|
|
46
|
+
</button>
|
|
47
|
+
<button class="sa-btn" type="button" @click="onCloneSelectedPlan">
|
|
48
|
+
<q-icon name="content_copy" size="16px" />
|
|
49
|
+
<span>{{ planDetailMsg.header.clonePlan }}</span>
|
|
50
|
+
</button>
|
|
51
|
+
<button
|
|
52
|
+
v-if="!detailDraftVersion"
|
|
53
|
+
class="sa-btn sa-btn--primary"
|
|
54
|
+
type="button"
|
|
55
|
+
@click="openCreateDraft"
|
|
56
|
+
>
|
|
57
|
+
<q-icon name="add" size="16px" />
|
|
58
|
+
<span>{{ planDetailMsg.header.newDraftVersion }}</span>
|
|
59
|
+
</button>
|
|
60
|
+
<button
|
|
61
|
+
v-else
|
|
62
|
+
class="sa-btn sa-btn--primary"
|
|
63
|
+
type="button"
|
|
64
|
+
@click="openPublish(detailDraftVersion)"
|
|
65
|
+
>
|
|
66
|
+
<q-icon name="bolt" size="16px" />
|
|
67
|
+
<span>{{ publishDraftLabel }}</span>
|
|
68
|
+
</button>
|
|
69
|
+
</template>
|
|
70
|
+
</AdminHero>
|
|
71
|
+
|
|
3
72
|
<q-banner v-if="error" class="bg-negative text-white q-ma-md" rounded>
|
|
4
73
|
<template #avatar>
|
|
5
74
|
<q-icon name="error" />
|
|
@@ -10,29 +79,44 @@
|
|
|
10
79
|
</template>
|
|
11
80
|
</q-banner>
|
|
12
81
|
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<template v-else>
|
|
82
|
+
<AdminBody
|
|
83
|
+
v-if="!viewOwnsHero"
|
|
84
|
+
:loading="loading && plans.length === 0"
|
|
85
|
+
:loading-text="msg.page.loading"
|
|
86
|
+
>
|
|
19
87
|
<!-- Default: list view (plan simulation) + Bundle overview -->
|
|
20
88
|
<template v-if="mode === 'list'">
|
|
21
|
-
<
|
|
22
|
-
:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
89
|
+
<AdminSection>
|
|
90
|
+
<AdminStatistics :columns="4">
|
|
91
|
+
<AdminKpi :label="msg.list.statPlans" :value="planCounts.plans" />
|
|
92
|
+
<AdminKpi
|
|
93
|
+
:label="msg.list.statLive"
|
|
94
|
+
:value="planCounts.live"
|
|
95
|
+
tone="positive"
|
|
96
|
+
/>
|
|
97
|
+
<AdminKpi
|
|
98
|
+
:label="msg.list.statDrafts"
|
|
99
|
+
:value="planCounts.drafts"
|
|
100
|
+
:tone="planCounts.drafts > 0 ? 'warn' : 'neutral'"
|
|
101
|
+
/>
|
|
102
|
+
<AdminKpi :label="msg.list.statTenants" :value="planCounts.tenants" />
|
|
103
|
+
</AdminStatistics>
|
|
104
|
+
</AdminSection>
|
|
105
|
+
<AdminSection>
|
|
106
|
+
<PlanList
|
|
107
|
+
:plans="plans"
|
|
108
|
+
:versions-by-plan-id="versionsByPlanId"
|
|
109
|
+
:tenant-counts-by-plan-key="tenantCountsByPlanKey"
|
|
110
|
+
:plan-accents="props.planAccents"
|
|
111
|
+
:highlight-plan-key="highlightPlanKey"
|
|
112
|
+
@open-plan="onOpenPlan"
|
|
113
|
+
@clone-plan="onClonePlan"
|
|
114
|
+
@new-version="onNewVersionFromList"
|
|
115
|
+
@edit-draft="onEditDraftFromList"
|
|
116
|
+
@discard-draft="onDiscardDraftFromList"
|
|
117
|
+
@archive-plan="onArchivePlanFromList"
|
|
118
|
+
/>
|
|
119
|
+
</AdminSection>
|
|
36
120
|
<PlanBundleOverview
|
|
37
121
|
:bundles="availableBundles"
|
|
38
122
|
:plans="plans"
|
|
@@ -41,21 +125,21 @@
|
|
|
41
125
|
</template>
|
|
42
126
|
|
|
43
127
|
<!-- V1 matrix: component comparison -->
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
128
|
+
<AdminSection v-else-if="mode === 'matrix'">
|
|
129
|
+
<PlanMatrix
|
|
130
|
+
:plans="plans"
|
|
131
|
+
:versions-by-plan-id="versionsByPlanId"
|
|
132
|
+
:available-quotas="availableQuotas"
|
|
133
|
+
:available-bundles="availableBundles"
|
|
134
|
+
:feature-registry="featureRegistry"
|
|
135
|
+
:tenant-counts-by-plan-key="tenantCountsByPlanKey"
|
|
136
|
+
:plan-accents="props.planAccents"
|
|
137
|
+
:loading="bulkVersionsLoading"
|
|
138
|
+
@open-plan="onOpenPlan"
|
|
139
|
+
@create-plan="openCreate"
|
|
140
|
+
@clone-plan="onClonePlan"
|
|
141
|
+
/>
|
|
142
|
+
</AdminSection>
|
|
59
143
|
|
|
60
144
|
<!-- Plan detail: drill-in for a single Plan (plan simulation) -->
|
|
61
145
|
<PlanDetail
|
|
@@ -69,51 +153,47 @@
|
|
|
69
153
|
:audit-rows="auditRows"
|
|
70
154
|
:loading-audit="loadingAudit"
|
|
71
155
|
:submit-terminate="onSubmitTerminate"
|
|
72
|
-
@back="onBackToList"
|
|
73
156
|
@create-draft="openCreateDraft"
|
|
74
157
|
@edit-draft="openEditDraft"
|
|
75
158
|
@publish="openPublish"
|
|
76
|
-
@clone-plan="onClonePlan(selectedPlan)"
|
|
77
|
-
@delete-plan="onArchivePlanFromList(selectedPlan, false)"
|
|
78
|
-
@update-plan="onUpdatePlanFromDetail"
|
|
79
|
-
/>
|
|
80
|
-
|
|
81
|
-
<!-- V2 split-view editor as a full-screen view (plan simulation) -->
|
|
82
|
-
<PlanVersionEditor
|
|
83
|
-
v-else-if="mode === 'editor' && draftEditing && selectedPlan"
|
|
84
|
-
:plan-key="selectedPlan.planKey"
|
|
85
|
-
:editing-id="draftEditing.editingId"
|
|
86
|
-
:initial-form="draftEditing.initialForm"
|
|
87
|
-
:saving="draftSaving"
|
|
88
|
-
:available-features="availableFeatures"
|
|
89
|
-
:available-quotas="availableQuotas"
|
|
90
|
-
:available-bundles="availableBundles"
|
|
91
|
-
:feature-registry="featureRegistry"
|
|
92
|
-
:plan-display-name="selectedPlan.label"
|
|
93
|
-
:save-error="draftSaveError"
|
|
94
|
-
:predecessor-version="editorPredecessor"
|
|
95
|
-
@save="onEditorNext"
|
|
96
|
-
@cancel="onCancelEditor"
|
|
97
159
|
/>
|
|
160
|
+
</AdminBody>
|
|
161
|
+
|
|
162
|
+
<!-- V2 split-view editor as a full-screen view (plan simulation) -->
|
|
163
|
+
<PlanVersionEditor
|
|
164
|
+
v-if="mode === 'editor' && draftEditing && selectedPlan"
|
|
165
|
+
:plan-key="selectedPlan.planKey"
|
|
166
|
+
:editing-id="draftEditing.editingId"
|
|
167
|
+
:initial-form="draftEditing.initialForm"
|
|
168
|
+
:saving="draftSaving"
|
|
169
|
+
:available-features="availableFeatures"
|
|
170
|
+
:available-quotas="availableQuotas"
|
|
171
|
+
:available-bundles="availableBundles"
|
|
172
|
+
:feature-registry="featureRegistry"
|
|
173
|
+
:plan-display-name="selectedPlan.label"
|
|
174
|
+
:save-error="draftSaveError"
|
|
175
|
+
:predecessor-version="editorPredecessor"
|
|
176
|
+
@save="onEditorNext"
|
|
177
|
+
@cancel="onCancelEditor"
|
|
178
|
+
/>
|
|
98
179
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
</template>
|
|
180
|
+
<!-- V3 Review & Publish — wizard step 3 (plan simulation) -->
|
|
181
|
+
<PlanReview
|
|
182
|
+
v-else-if="mode === 'review' && reviewDraft && selectedPlan"
|
|
183
|
+
:plan="selectedPlan"
|
|
184
|
+
:version="reviewDraft"
|
|
185
|
+
:predecessor="reviewPredecessor"
|
|
186
|
+
:available-quotas="availableQuotas"
|
|
187
|
+
:available-bundles="availableBundles"
|
|
188
|
+
:feature-registry="featureRegistry"
|
|
189
|
+
:tenant-impact-count="tenantCountsByPlanKey[selectedPlan.planKey] ?? 0"
|
|
190
|
+
:saving="draftSaving"
|
|
191
|
+
:publishing="publishing"
|
|
192
|
+
:publish-error="reviewError"
|
|
193
|
+
@back="onReviewBack"
|
|
194
|
+
@save-and-exit="onReviewSaveExit"
|
|
195
|
+
@publish="onReviewPublish"
|
|
196
|
+
/>
|
|
117
197
|
|
|
118
198
|
<PlansPageToast :message="toastMessage" />
|
|
119
199
|
|
|
@@ -157,11 +237,18 @@
|
|
|
157
237
|
:format-change-value="formatChangeValue"
|
|
158
238
|
@execute="executePublish"
|
|
159
239
|
/>
|
|
160
|
-
</
|
|
240
|
+
</AdminPage>
|
|
161
241
|
</template>
|
|
162
242
|
|
|
163
243
|
<script setup lang="ts">
|
|
164
244
|
import { computed, onMounted, ref, shallowRef, watch } from 'vue';
|
|
245
|
+
import AdminBody from '../components/admin-page/AdminBody.vue';
|
|
246
|
+
import { countPlans, resolvePlans } from '../client/resolve-plans.js';
|
|
247
|
+
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
248
|
+
import AdminSection from '../components/admin-page/AdminSection.vue';
|
|
249
|
+
import AdminPage from '../components/admin-page/AdminPage.vue';
|
|
250
|
+
import AdminStatistics from '../components/admin-page/AdminStatistics.vue';
|
|
251
|
+
import AdminKpi from '../components/admin-page/AdminKpi.vue';
|
|
165
252
|
import type {
|
|
166
253
|
FeatureCatalogEntryRow,
|
|
167
254
|
PlanRow,
|
|
@@ -182,9 +269,11 @@ import type { HttpClient } from '../client/types.js';
|
|
|
182
269
|
import type { SaMessages } from '../client/i18n/index.js';
|
|
183
270
|
import { formatMessage } from '../client/i18n/format.js';
|
|
184
271
|
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
272
|
+
import { useSuperAdminHttp } from '../vue/use-super-admin-context.js';
|
|
185
273
|
import PlanVersionEditor from '../components/plan-version-editor/PlanVersionEditor.vue';
|
|
186
274
|
import PlanMatrix from '../components/plan-matrix/PlanMatrix.vue';
|
|
187
275
|
import PlanDetail from '../components/plan-detail/PlanDetail.vue';
|
|
276
|
+
import PlanTitleEdit from '../components/plan-detail/PlanTitleEdit.vue';
|
|
188
277
|
import PlanList from '../components/plan-list/PlanList.vue';
|
|
189
278
|
import PlanCreateDialog, {
|
|
190
279
|
type PlanCreateSubmit,
|
|
@@ -251,6 +340,64 @@ const props = defineProps<{
|
|
|
251
340
|
}>();
|
|
252
341
|
|
|
253
342
|
const msg = useSaMessages('plans');
|
|
343
|
+
// The plan detail's own strings: its header moved into this page's hero.
|
|
344
|
+
const planDetailMsg = useSaMessages('planDetail');
|
|
345
|
+
|
|
346
|
+
// Fallback for the requests this page issues directly. It resolves to the
|
|
347
|
+
// client the app registered via `createSuperAdminApp({ http })`, or to
|
|
348
|
+
// `defaultHttpClient()` — never to a bare `fetch()`, which would drop the
|
|
349
|
+
// app's Authorization header on exactly these calls.
|
|
350
|
+
const shellHttp = useSuperAdminHttp();
|
|
351
|
+
|
|
352
|
+
// The same derivation PlanList uses for its rows, so the counts above the list
|
|
353
|
+
// and the list itself cannot drift apart.
|
|
354
|
+
const planCounts = computed(() =>
|
|
355
|
+
countPlans(
|
|
356
|
+
resolvePlans({
|
|
357
|
+
plans: plans.value,
|
|
358
|
+
versionsByPlanId: versionsByPlanId.value,
|
|
359
|
+
tenantCountsByPlanKey: tenantCountsByPlanKey.value,
|
|
360
|
+
}),
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
// The editor and the review render the hero themselves: their actions hang on
|
|
365
|
+
// state that lives in those views — the publish checklist, the force flags, the
|
|
366
|
+
// draft's validity — so the page steps aside rather than lifting all of it up.
|
|
367
|
+
const viewOwnsHero = computed(() => mode.value === 'editor' || mode.value === 'review');
|
|
368
|
+
|
|
369
|
+
// One page, one heading. Every view here — the list, the matrix, a single plan
|
|
370
|
+
// — is a mode of the same page, so each swaps the hero's title and actions
|
|
371
|
+
// instead of rendering a second header underneath it. The plan detail used to
|
|
372
|
+
// do the latter, which put a competing <h1> on the page.
|
|
373
|
+
const heroTitle = computed(() => {
|
|
374
|
+
if (mode.value === 'cockpit' && selectedPlan.value) return selectedPlan.value.label;
|
|
375
|
+
return mode.value === 'matrix' ? msg.value.matrix.title : msg.value.list.title;
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const heroSubtitle = computed(() => {
|
|
379
|
+
if (mode.value !== 'cockpit' || !selectedPlan.value) return msg.value.page.subtitle;
|
|
380
|
+
return selectedPlan.value.description || planDetailMsg.value.header.noDescription;
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const detailDraftVersion = computed(
|
|
384
|
+
() => versions.value.find((v) => v.publishedAt === null) ?? null,
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
// Deleting a plan is only offered while nothing depends on it: no tenants, no
|
|
388
|
+
// published version, no open draft.
|
|
389
|
+
const detailIsDeletable = computed(
|
|
390
|
+
() =>
|
|
391
|
+
detailDraftVersion.value === null &&
|
|
392
|
+
versions.value.every((v) => v.publishedAt === null) &&
|
|
393
|
+
Object.values(impactByVersion.value).every((n) => n === 0),
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
const publishDraftLabel = computed(() =>
|
|
397
|
+
formatMessage(planDetailMsg.value.header.publishDraft, {
|
|
398
|
+
version: detailDraftVersion.value?.version ?? '',
|
|
399
|
+
}),
|
|
400
|
+
);
|
|
254
401
|
const common = useSaMessages('common');
|
|
255
402
|
|
|
256
403
|
const composable: UsePlansResult = usePlans({
|
|
@@ -315,22 +462,6 @@ const tenantCountsByPlanKey = computed<Record<string, number>>(
|
|
|
315
462
|
() => props.tenantCountsByPlanKey ?? loadedTenantCounts.value,
|
|
316
463
|
);
|
|
317
464
|
|
|
318
|
-
function planAccent(planKey: string): string {
|
|
319
|
-
return props.planAccents?.[planKey] ?? defaultAccent(planKey);
|
|
320
|
-
}
|
|
321
|
-
const DEFAULT_ACCENTS: Record<string, string> = {
|
|
322
|
-
STARTER: '#64748b',
|
|
323
|
-
STANDARD: '#2563eb',
|
|
324
|
-
PRO: '#7c3aed',
|
|
325
|
-
PROFESSIONAL: '#7c3aed',
|
|
326
|
-
BUSINESS: '#0ea5e9',
|
|
327
|
-
ENTERPRISE: '#0f766e',
|
|
328
|
-
BASIC: '#475569',
|
|
329
|
-
};
|
|
330
|
-
function defaultAccent(planKey: string): string {
|
|
331
|
-
return DEFAULT_ACCENTS[planKey] ?? '#2563eb';
|
|
332
|
-
}
|
|
333
|
-
|
|
334
465
|
// ─── Plan master creation (V1 plan simulation step 1) ───
|
|
335
466
|
const createDialogOpen = ref(false);
|
|
336
467
|
const creatingPlan = ref(false);
|
|
@@ -418,7 +549,7 @@ async function reloadAllVersions(): Promise<void> {
|
|
|
418
549
|
if (plans.value.length === 0) return;
|
|
419
550
|
bulkVersionsLoading.value = true;
|
|
420
551
|
try {
|
|
421
|
-
const http = props.http ??
|
|
552
|
+
const http = props.http ?? shellHttp;
|
|
422
553
|
const token = props.getAuthToken?.();
|
|
423
554
|
const headers: Record<string, string> = token ? { Authorization: `Bearer ${token}` } : {};
|
|
424
555
|
const results = await Promise.all(
|
|
@@ -539,6 +670,18 @@ async function onEditDraftFromList(plan: PlanRow, draft: PlanVersionRow): Promis
|
|
|
539
670
|
openEditDraft(draft);
|
|
540
671
|
}
|
|
541
672
|
|
|
673
|
+
// The hero's detail actions only render while a plan is selected, but a
|
|
674
|
+
// template condition on the surrounding slot does not narrow `selectedPlan`
|
|
675
|
+
// inside a handler expression — a consumer's stricter build rejects it. These
|
|
676
|
+
// carry the guard in code, where the type checker can see it.
|
|
677
|
+
function onCloneSelectedPlan(): void {
|
|
678
|
+
if (selectedPlan.value) onClonePlan(selectedPlan.value);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function onArchiveSelectedPlan(): void {
|
|
682
|
+
if (selectedPlan.value) onArchivePlanFromList(selectedPlan.value, false);
|
|
683
|
+
}
|
|
684
|
+
|
|
542
685
|
function onBackToList(): void {
|
|
543
686
|
mode.value = 'list';
|
|
544
687
|
selectedPlan.value = null;
|
|
@@ -555,7 +698,6 @@ async function onUpdatePlanFromDetail(patch: { label: string }): Promise<void> {
|
|
|
555
698
|
const updated = await update(selectedPlan.value.id, patch);
|
|
556
699
|
selectedPlan.value = updated;
|
|
557
700
|
} catch (err: unknown) {
|
|
558
|
-
// eslint-disable-next-line no-console
|
|
559
701
|
console.error('[PlansPage] Plan rename failed', err);
|
|
560
702
|
}
|
|
561
703
|
}
|
|
@@ -578,7 +720,7 @@ const featureRegistry = computed<Record<string, { label?: string; group?: string
|
|
|
578
720
|
}));
|
|
579
721
|
|
|
580
722
|
async function loadEditorSources(): Promise<void> {
|
|
581
|
-
const http = props.http ??
|
|
723
|
+
const http = props.http ?? shellHttp;
|
|
582
724
|
const token = props.getAuthToken?.();
|
|
583
725
|
const headers: Record<string, string> = token ? { Authorization: `Bearer ${token}` } : {};
|
|
584
726
|
try {
|
|
@@ -926,7 +1068,6 @@ async function onReviewSaveExit(): Promise<void> {
|
|
|
926
1068
|
draftEditing.value = null;
|
|
927
1069
|
mode.value = selectedPlan.value ? 'cockpit' : 'list';
|
|
928
1070
|
} catch (err: unknown) {
|
|
929
|
-
// eslint-disable-next-line no-console
|
|
930
1071
|
console.error('[PlansPage] Saving the draft failed', err);
|
|
931
1072
|
reviewError.value = describeDraftSaveError(err);
|
|
932
1073
|
} finally {
|
|
@@ -963,7 +1104,6 @@ async function onReviewPublish(payload: {
|
|
|
963
1104
|
draftEditing.value = null;
|
|
964
1105
|
mode.value = 'cockpit';
|
|
965
1106
|
} catch (err: unknown) {
|
|
966
|
-
// eslint-disable-next-line no-console
|
|
967
1107
|
console.error('[PlansPage] Publishing failed', err);
|
|
968
1108
|
reviewError.value = describePublishError(err);
|
|
969
1109
|
} finally {
|
|
@@ -997,7 +1137,6 @@ async function executeArchive(): Promise<void> {
|
|
|
997
1137
|
archiveOpen.value = false;
|
|
998
1138
|
flashToast(formatMessage(msg.value.page.toastPlanDeleted, { planKey: plan.planKey }));
|
|
999
1139
|
} catch (err: unknown) {
|
|
1000
|
-
// eslint-disable-next-line no-console
|
|
1001
1140
|
console.error('[PlansPage] Archive/purge failed', err);
|
|
1002
1141
|
const status = (err as { status?: number })?.status;
|
|
1003
1142
|
const body = (err as { body?: { code?: string; message?: string } })?.body;
|
|
@@ -1064,7 +1203,7 @@ async function executeDiscard(): Promise<void> {
|
|
|
1064
1203
|
} catch (err: unknown) {
|
|
1065
1204
|
// Full error object to the console — in the diagnostic case you can see
|
|
1066
1205
|
// e.g. CORS preflight errors or the real network stack there.
|
|
1067
|
-
|
|
1206
|
+
|
|
1068
1207
|
console.error('[PlansPage] Discard failed', err);
|
|
1069
1208
|
|
|
1070
1209
|
const status = (err as { status?: number })?.status;
|
|
@@ -1238,17 +1377,6 @@ onMounted(async () => {
|
|
|
1238
1377
|
|
|
1239
1378
|
<style scoped>
|
|
1240
1379
|
.sa-plans {
|
|
1241
|
-
min-height: calc(100vh - 56px);
|
|
1242
|
-
background: #f6f7f9;
|
|
1243
1380
|
position: relative;
|
|
1244
1381
|
}
|
|
1245
|
-
.sa-plans__loading {
|
|
1246
|
-
display: flex;
|
|
1247
|
-
align-items: center;
|
|
1248
|
-
justify-content: center;
|
|
1249
|
-
gap: 12px;
|
|
1250
|
-
padding: 48px;
|
|
1251
|
-
color: #64748b;
|
|
1252
|
-
font-size: 14px;
|
|
1253
|
-
}
|
|
1254
1382
|
</style>
|