@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,70 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
<!-- Page header -->
|
|
4
|
-
<div class="pl-page-head">
|
|
5
|
-
<div>
|
|
6
|
-
<h2 class="pl-h-title">{{ msg.list.title }}</h2>
|
|
7
|
-
<p class="pl-h-sub">{{ summary }}</p>
|
|
8
|
-
</div>
|
|
9
|
-
<div class="pl-head-actions">
|
|
10
|
-
<button class="pl-btn" type="button" @click="$emit('showMatrix')">
|
|
11
|
-
<svg
|
|
12
|
-
width="14"
|
|
13
|
-
height="14"
|
|
14
|
-
viewBox="0 0 24 24"
|
|
15
|
-
fill="none"
|
|
16
|
-
stroke="currentColor"
|
|
17
|
-
stroke-width="2"
|
|
18
|
-
>
|
|
19
|
-
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
20
|
-
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
21
|
-
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
22
|
-
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
23
|
-
</svg>
|
|
24
|
-
<span>{{ msg.list.matrixView }}</span>
|
|
25
|
-
</button>
|
|
26
|
-
<button class="pl-btn pl-btn--primary" type="button" @click="$emit('createPlan')">
|
|
27
|
-
<svg
|
|
28
|
-
width="14"
|
|
29
|
-
height="14"
|
|
30
|
-
viewBox="0 0 24 24"
|
|
31
|
-
fill="none"
|
|
32
|
-
stroke="currentColor"
|
|
33
|
-
stroke-width="2.5"
|
|
34
|
-
>
|
|
35
|
-
<path d="M12 5v14M5 12h14" />
|
|
36
|
-
</svg>
|
|
37
|
-
<span>{{ msg.list.newPlan }}</span>
|
|
38
|
-
</button>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
2
|
+
<div class="sa-plan-list">
|
|
42
3
|
<!-- List wrapper -->
|
|
43
|
-
<div class="
|
|
44
|
-
<div class="
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<circle cx="11" cy="11" r="7" />
|
|
56
|
-
<path d="M21 21l-4.35-4.35" />
|
|
57
|
-
</svg>
|
|
58
|
-
</span>
|
|
59
|
-
<input v-model="search" :placeholder="msg.list.searchPlaceholder" />
|
|
60
|
-
<span class="pl-kbd">⌘ K</span>
|
|
61
|
-
</div>
|
|
62
|
-
<div class="pl-toolbar-spacer" />
|
|
63
|
-
<div class="pl-sortinfo">{{ msg.list.sortedBy }}</div>
|
|
4
|
+
<div class="sa-plan-list-wrap">
|
|
5
|
+
<div class="sa-plan-list-toolbar">
|
|
6
|
+
<q-input
|
|
7
|
+
v-model="search"
|
|
8
|
+
dense
|
|
9
|
+
outlined
|
|
10
|
+
clearable
|
|
11
|
+
:placeholder="msg.list.searchPlaceholder"
|
|
12
|
+
>
|
|
13
|
+
<template #prepend><q-icon name="search" /></template>
|
|
14
|
+
</q-input>
|
|
15
|
+
<div class="sa-plan-list-sortinfo">{{ msg.list.sortedBy }}</div>
|
|
64
16
|
</div>
|
|
65
17
|
|
|
66
|
-
<div class="
|
|
67
|
-
<div class="
|
|
18
|
+
<div class="sa-plan-list-list">
|
|
19
|
+
<div class="sa-plan-list-list-head">
|
|
68
20
|
<div>{{ msg.list.columnPlan }}</div>
|
|
69
21
|
<div>{{ common.status }}</div>
|
|
70
22
|
<div>{{ msg.list.columnVersion }}</div>
|
|
@@ -73,7 +25,7 @@
|
|
|
73
25
|
<div />
|
|
74
26
|
</div>
|
|
75
27
|
|
|
76
|
-
<div v-if="filteredPlans.length === 0" class="
|
|
28
|
+
<div v-if="filteredPlans.length === 0" class="sa-plan-list-empty">
|
|
77
29
|
<template v-if="resolvedPlans.length === 0">
|
|
78
30
|
{{ msg.list.emptyNoPlans }}
|
|
79
31
|
</template>
|
|
@@ -83,13 +35,16 @@
|
|
|
83
35
|
<template v-for="p in filteredPlans" :key="p.plan.id">
|
|
84
36
|
<!-- Parent row: currently live version (or first future / nothing) -->
|
|
85
37
|
<div
|
|
86
|
-
:class="[
|
|
38
|
+
:class="[
|
|
39
|
+
'sa-plan-list-row',
|
|
40
|
+
{ 'sa-plan-list-row--new': highlightPlanKey === p.planKey },
|
|
41
|
+
]"
|
|
87
42
|
@click="$emit('openPlan', p.plan)"
|
|
88
43
|
>
|
|
89
|
-
<div class="
|
|
90
|
-
<div class="
|
|
44
|
+
<div class="sa-plan-list-cell sa-plan-list-cell--name">
|
|
45
|
+
<div class="sa-plan-list-plan-name">
|
|
91
46
|
<div
|
|
92
|
-
class="
|
|
47
|
+
class="sa-plan-list-plan-mark"
|
|
93
48
|
:style="{
|
|
94
49
|
background: planAccent(p.planKey) + '15',
|
|
95
50
|
color: planAccent(p.planKey),
|
|
@@ -98,49 +53,60 @@
|
|
|
98
53
|
>
|
|
99
54
|
{{ p.planKey.slice(0, 3) }}
|
|
100
55
|
</div>
|
|
101
|
-
<div class="
|
|
102
|
-
<div class="
|
|
56
|
+
<div class="sa-plan-list-plan-titles">
|
|
57
|
+
<div class="sa-plan-list-plan-title">
|
|
103
58
|
{{ p.label }}
|
|
104
59
|
<span
|
|
105
60
|
v-if="highlightPlanKey === p.planKey"
|
|
106
|
-
class="
|
|
61
|
+
class="sa-plan-list-chip sa-plan-list-chip--new"
|
|
107
62
|
>{{ msg.list.badgeNew }}</span
|
|
108
63
|
>
|
|
109
64
|
</div>
|
|
110
|
-
<div class="
|
|
65
|
+
<div class="sa-plan-list-plan-desc">
|
|
66
|
+
{{ p.description || '—' }}
|
|
67
|
+
</div>
|
|
111
68
|
</div>
|
|
112
69
|
</div>
|
|
113
70
|
</div>
|
|
114
71
|
|
|
115
|
-
<div class="
|
|
72
|
+
<div class="sa-plan-list-cell sa-plan-list-cell--status">
|
|
116
73
|
<template v-if="p.currentLive">
|
|
117
|
-
<span
|
|
118
|
-
|
|
119
|
-
|
|
74
|
+
<span
|
|
75
|
+
class="sa-plan-list-chip sa-plan-list-chip--live sa-plan-list-chip--dot"
|
|
76
|
+
>{{ msg.list.chipLive }}</span
|
|
77
|
+
>
|
|
120
78
|
<span
|
|
121
79
|
v-if="!p.currentLive.marketed"
|
|
122
|
-
class="
|
|
80
|
+
class="sa-plan-list-chip sa-plan-list-chip--supersed sa-plan-list-chip--tiny"
|
|
123
81
|
>{{ msg.list.chipPrivate }}</span
|
|
124
82
|
>
|
|
125
83
|
</template>
|
|
126
84
|
<template v-else-if="p.primary && p.primary.publishedAt">
|
|
127
|
-
<span
|
|
85
|
+
<span
|
|
86
|
+
class="sa-plan-list-chip sa-plan-list-chip--scheduled sa-plan-list-chip--dot"
|
|
87
|
+
>
|
|
128
88
|
{{ validFromLabel(p.primary.validFrom) }}
|
|
129
89
|
</span>
|
|
130
90
|
</template>
|
|
131
91
|
<template v-else>
|
|
132
|
-
<span
|
|
133
|
-
|
|
134
|
-
|
|
92
|
+
<span
|
|
93
|
+
class="sa-plan-list-chip sa-plan-list-chip--supersed sa-plan-list-chip--dot"
|
|
94
|
+
>{{ msg.list.chipNoLive }}</span
|
|
95
|
+
>
|
|
135
96
|
</template>
|
|
136
97
|
</div>
|
|
137
98
|
|
|
138
|
-
<div class="
|
|
139
|
-
<div v-if="p.primary" class="
|
|
99
|
+
<div class="sa-plan-list-cell">
|
|
100
|
+
<div v-if="p.primary" class="sa-plan-list-version-num">
|
|
140
101
|
v{{ p.primary.version }}
|
|
141
102
|
</div>
|
|
142
|
-
<div
|
|
143
|
-
|
|
103
|
+
<div
|
|
104
|
+
v-else
|
|
105
|
+
class="sa-plan-list-version-num sa-plan-list-version-num--muted"
|
|
106
|
+
>
|
|
107
|
+
—
|
|
108
|
+
</div>
|
|
109
|
+
<div v-if="p.primary?.validFrom" class="sa-plan-list-version-sub">
|
|
144
110
|
{{
|
|
145
111
|
validityRange(
|
|
146
112
|
p.currentLive ? msg.list.since : msg.list.from,
|
|
@@ -151,9 +117,9 @@
|
|
|
151
117
|
</div>
|
|
152
118
|
</div>
|
|
153
119
|
|
|
154
|
-
<div class="
|
|
120
|
+
<div class="sa-plan-list-cell">
|
|
155
121
|
<template v-if="!p.primary">
|
|
156
|
-
<span class="
|
|
122
|
+
<span class="sa-plan-list-price-text">—</span>
|
|
157
123
|
</template>
|
|
158
124
|
<template
|
|
159
125
|
v-else-if="
|
|
@@ -161,29 +127,31 @@
|
|
|
161
127
|
Number(p.primary.yearlyNet) === 0
|
|
162
128
|
"
|
|
163
129
|
>
|
|
164
|
-
<span class="
|
|
130
|
+
<span class="sa-plan-list-price-text">{{
|
|
131
|
+
msg.list.priceFree
|
|
132
|
+
}}</span>
|
|
165
133
|
</template>
|
|
166
134
|
<template v-else>
|
|
167
135
|
<div>
|
|
168
|
-
<span class="
|
|
136
|
+
<span class="sa-plan-list-price-big">{{
|
|
169
137
|
formatMoney(p.primary.monthlyNet)
|
|
170
138
|
}}</span>
|
|
171
|
-
<span class="
|
|
139
|
+
<span class="sa-plan-list-price-unit">{{
|
|
172
140
|
' ' + msg.list.perMonthShort
|
|
173
141
|
}}</span>
|
|
174
142
|
</div>
|
|
175
|
-
<div class="
|
|
143
|
+
<div class="sa-plan-list-price-sub">
|
|
176
144
|
{{ formatMoney(p.primary.yearlyNet) }}
|
|
177
145
|
{{ msg.list.perYearShort }}
|
|
178
146
|
</div>
|
|
179
147
|
</template>
|
|
180
148
|
</div>
|
|
181
149
|
|
|
182
|
-
<div class="
|
|
183
|
-
<span class="
|
|
184
|
-
<div class="
|
|
150
|
+
<div class="sa-plan-list-cell sa-plan-list-cell--tenants">
|
|
151
|
+
<span class="sa-plan-list-tenant-num">{{ p.tenantCount }}</span>
|
|
152
|
+
<div class="sa-plan-list-tenant-bar">
|
|
185
153
|
<div
|
|
186
|
-
class="
|
|
154
|
+
class="sa-plan-list-tenant-bar-fill"
|
|
187
155
|
:style="{
|
|
188
156
|
width: tenantBarWidth(p.tenantCount),
|
|
189
157
|
background: planAccent(p.planKey),
|
|
@@ -192,10 +160,10 @@
|
|
|
192
160
|
</div>
|
|
193
161
|
</div>
|
|
194
162
|
|
|
195
|
-
<div class="
|
|
163
|
+
<div class="sa-plan-list-cell sa-plan-list-cell--actions" @click.stop>
|
|
196
164
|
<button
|
|
197
165
|
v-if="hasAnyPublished(p)"
|
|
198
|
-
class="
|
|
166
|
+
class="sa-btn sa-btn--sm sa-btn--ghost"
|
|
199
167
|
type="button"
|
|
200
168
|
disabled
|
|
201
169
|
:title="msg.list.actionDeleteBlocked"
|
|
@@ -215,7 +183,7 @@
|
|
|
215
183
|
</button>
|
|
216
184
|
<button
|
|
217
185
|
v-else
|
|
218
|
-
class="
|
|
186
|
+
class="sa-btn sa-btn--sm sa-btn--ghost sa-btn--danger"
|
|
219
187
|
type="button"
|
|
220
188
|
:title="msg.list.actionDeletePlan"
|
|
221
189
|
@click="$emit('archivePlan', p.plan, false)"
|
|
@@ -234,7 +202,7 @@
|
|
|
234
202
|
</svg>
|
|
235
203
|
</button>
|
|
236
204
|
<button
|
|
237
|
-
class="
|
|
205
|
+
class="sa-btn sa-btn--sm sa-btn--ghost"
|
|
238
206
|
type="button"
|
|
239
207
|
:title="msg.list.actionClonePlan"
|
|
240
208
|
@click="$emit('clonePlan', p.plan)"
|
|
@@ -252,7 +220,7 @@
|
|
|
252
220
|
</svg>
|
|
253
221
|
</button>
|
|
254
222
|
<button
|
|
255
|
-
class="
|
|
223
|
+
class="sa-btn sa-btn--sm sa-btn--ghost"
|
|
256
224
|
type="button"
|
|
257
225
|
:title="msg.list.actionNewVersion"
|
|
258
226
|
:disabled="!!p.draft"
|
|
@@ -272,7 +240,7 @@
|
|
|
272
240
|
</svg>
|
|
273
241
|
</button>
|
|
274
242
|
<button
|
|
275
|
-
class="
|
|
243
|
+
class="sa-btn sa-btn--sm"
|
|
276
244
|
type="button"
|
|
277
245
|
:title="msg.list.actionOpenPlan"
|
|
278
246
|
@click="$emit('openPlan', p.plan)"
|
|
@@ -295,86 +263,92 @@
|
|
|
295
263
|
<div
|
|
296
264
|
v-for="sub in p.subRows"
|
|
297
265
|
:key="`${p.plan.id}-${sub.id}`"
|
|
298
|
-
class="
|
|
266
|
+
class="sa-plan-list-row sa-plan-list-row--sub"
|
|
299
267
|
@click.stop="$emit('openPlan', p.plan)"
|
|
300
268
|
>
|
|
301
|
-
<div
|
|
302
|
-
|
|
303
|
-
|
|
269
|
+
<div
|
|
270
|
+
class="sa-plan-list-cell sa-plan-list-cell--name sa-plan-list-cell--sub-name"
|
|
271
|
+
>
|
|
272
|
+
<div class="sa-plan-list-sub-tree" aria-hidden="true">
|
|
273
|
+
<span class="sa-plan-list-sub-tree-elbow" />
|
|
304
274
|
</div>
|
|
305
|
-
<div class="
|
|
306
|
-
<div class="
|
|
275
|
+
<div class="sa-plan-list-sub-titles">
|
|
276
|
+
<div class="sa-plan-list-sub-title">
|
|
307
277
|
v{{ sub.version }}
|
|
308
278
|
<template v-if="sub.publishedAt === null">
|
|
309
279
|
<span
|
|
310
|
-
class="
|
|
280
|
+
class="sa-plan-list-chip sa-plan-list-chip--draft sa-plan-list-chip--dot sa-plan-list-chip--tiny"
|
|
311
281
|
>{{ msg.list.chipDraft }}</span
|
|
312
282
|
>
|
|
313
283
|
</template>
|
|
314
284
|
<template v-else>
|
|
315
285
|
<span
|
|
316
|
-
class="
|
|
286
|
+
class="sa-plan-list-chip sa-plan-list-chip--scheduled sa-plan-list-chip--dot sa-plan-list-chip--tiny"
|
|
317
287
|
>{{ msg.list.chipScheduled }}</span
|
|
318
288
|
>
|
|
319
289
|
</template>
|
|
320
290
|
</div>
|
|
321
|
-
<div class="
|
|
291
|
+
<div class="sa-plan-list-sub-desc">
|
|
322
292
|
{{ sub.changeNote || msg.list.noChangeNote }}
|
|
323
293
|
</div>
|
|
324
294
|
</div>
|
|
325
295
|
</div>
|
|
326
296
|
|
|
327
|
-
<div class="
|
|
297
|
+
<div class="sa-plan-list-cell sa-plan-list-cell--status">
|
|
328
298
|
<span
|
|
329
299
|
v-if="sub.publishedAt === null"
|
|
330
|
-
class="
|
|
300
|
+
class="sa-plan-list-chip sa-plan-list-chip--draft sa-plan-list-chip--dot sa-plan-list-chip--tiny"
|
|
331
301
|
>{{ msg.list.chipDraft }}</span
|
|
332
302
|
>
|
|
333
303
|
<span
|
|
334
304
|
v-else
|
|
335
|
-
class="
|
|
305
|
+
class="sa-plan-list-chip sa-plan-list-chip--scheduled sa-plan-list-chip--dot sa-plan-list-chip--tiny"
|
|
336
306
|
>{{ validFromLabel(sub.validFrom) }}</span
|
|
337
307
|
>
|
|
338
308
|
</div>
|
|
339
309
|
|
|
340
|
-
<div class="
|
|
341
|
-
<div class="
|
|
342
|
-
|
|
310
|
+
<div class="sa-plan-list-cell">
|
|
311
|
+
<div class="sa-plan-list-version-num sa-plan-list-version-num--sub">
|
|
312
|
+
v{{ sub.version }}
|
|
313
|
+
</div>
|
|
314
|
+
<div v-if="sub.validFrom" class="sa-plan-list-version-sub">
|
|
343
315
|
{{ validityRange(msg.list.from, sub.validFrom, sub.validUntil) }}
|
|
344
316
|
</div>
|
|
345
317
|
</div>
|
|
346
318
|
|
|
347
|
-
<div class="
|
|
319
|
+
<div class="sa-plan-list-cell">
|
|
348
320
|
<template
|
|
349
321
|
v-if="Number(sub.monthlyNet) === 0 && Number(sub.yearlyNet) === 0"
|
|
350
322
|
>
|
|
351
|
-
<span class="
|
|
323
|
+
<span class="sa-plan-list-price-text">{{
|
|
324
|
+
msg.list.priceFree
|
|
325
|
+
}}</span>
|
|
352
326
|
</template>
|
|
353
327
|
<template v-else>
|
|
354
328
|
<div>
|
|
355
|
-
<span class="
|
|
329
|
+
<span class="sa-plan-list-price-big">{{
|
|
356
330
|
formatMoney(sub.monthlyNet)
|
|
357
331
|
}}</span>
|
|
358
|
-
<span class="
|
|
332
|
+
<span class="sa-plan-list-price-unit">{{
|
|
359
333
|
' ' + msg.list.perMonthShort
|
|
360
334
|
}}</span>
|
|
361
335
|
</div>
|
|
362
|
-
<div class="
|
|
336
|
+
<div class="sa-plan-list-price-sub">
|
|
363
337
|
{{ formatMoney(sub.yearlyNet) }} {{ msg.list.perYearShort }}
|
|
364
338
|
</div>
|
|
365
339
|
</template>
|
|
366
340
|
</div>
|
|
367
341
|
|
|
368
|
-
<div class="
|
|
369
|
-
<span class="
|
|
342
|
+
<div class="sa-plan-list-cell sa-plan-list-cell--sub-impact">
|
|
343
|
+
<span class="sa-plan-list-version-sub">{{
|
|
370
344
|
tenantCountLabel(p.tenantCount)
|
|
371
345
|
}}</span>
|
|
372
346
|
</div>
|
|
373
347
|
|
|
374
|
-
<div class="
|
|
348
|
+
<div class="sa-plan-list-cell sa-plan-list-cell--actions" @click.stop>
|
|
375
349
|
<button
|
|
376
350
|
v-if="sub.publishedAt === null"
|
|
377
|
-
class="
|
|
351
|
+
class="sa-btn sa-btn--sm sa-btn--ghost sa-btn--danger"
|
|
378
352
|
type="button"
|
|
379
353
|
:title="discardDraftTitle(sub.version)"
|
|
380
354
|
@click="$emit('discardDraft', p.plan, sub)"
|
|
@@ -394,7 +368,7 @@
|
|
|
394
368
|
</button>
|
|
395
369
|
<button
|
|
396
370
|
v-if="sub.publishedAt === null"
|
|
397
|
-
class="
|
|
371
|
+
class="sa-btn sa-btn--sm sa-btn--ghost"
|
|
398
372
|
type="button"
|
|
399
373
|
:title="msg.list.actionEditDraft"
|
|
400
374
|
@click="$emit('editDraft', p.plan, sub)"
|
|
@@ -413,7 +387,7 @@
|
|
|
413
387
|
</svg>
|
|
414
388
|
</button>
|
|
415
389
|
<button
|
|
416
|
-
class="
|
|
390
|
+
class="sa-btn sa-btn--sm"
|
|
417
391
|
type="button"
|
|
418
392
|
:title="msg.list.actionOpenInCockpit"
|
|
419
393
|
@click="$emit('openPlan', p.plan)"
|
|
@@ -438,6 +412,7 @@
|
|
|
438
412
|
</template>
|
|
439
413
|
|
|
440
414
|
<script setup lang="ts">
|
|
415
|
+
import { resolvePlans, type ResolvedPlan } from '../../client/resolve-plans.js';
|
|
441
416
|
import { computed, ref } from 'vue';
|
|
442
417
|
import type { PlanRow, PlanVersionRow } from '@saasicat/types';
|
|
443
418
|
import { formatMessage } from '../../client/i18n/format.js';
|
|
@@ -467,40 +442,19 @@ const props = withDefaults(
|
|
|
467
442
|
|
|
468
443
|
const emit = defineEmits<{
|
|
469
444
|
(e: 'openPlan', plan: PlanRow): void;
|
|
470
|
-
(e: 'createPlan'): void;
|
|
471
445
|
(e: 'clonePlan', plan: PlanRow): void;
|
|
472
446
|
(e: 'newVersion', plan: PlanRow, basis: PlanVersionRow): void;
|
|
473
447
|
(e: 'editDraft', plan: PlanRow, draft: PlanVersionRow): void;
|
|
474
448
|
(e: 'discardDraft', plan: PlanRow, draft: PlanVersionRow): void;
|
|
475
449
|
(e: 'archivePlan', plan: PlanRow, hasLive: boolean): void;
|
|
476
|
-
(e: 'showMatrix'): void;
|
|
477
450
|
}>();
|
|
478
451
|
|
|
479
452
|
const msg = useSaMessages('plans');
|
|
480
453
|
const { locale, intlLocale } = useSuperAdminI18n();
|
|
481
454
|
const common = useSaMessages('common');
|
|
482
455
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
interface ResolvedPlan {
|
|
486
|
-
plan: PlanRow;
|
|
487
|
-
planKey: string;
|
|
488
|
-
label: string;
|
|
489
|
-
description: string | null;
|
|
490
|
-
/** Version currently active for new bookings (validFrom ≤ today < validUntil or validUntil null). */
|
|
491
|
-
currentLive: PlanVersionRow | null;
|
|
492
|
-
/** Version shown on the parent row (currentLive preferred). */
|
|
493
|
-
primary: PlanVersionRow | null;
|
|
494
|
-
/** Drafts + future-published versions (sorted: future ASC by validFrom, drafts at the end). */
|
|
495
|
-
subRows: PlanVersionRow[];
|
|
496
|
-
/** True if the plan has any versions at all (for hide logic). */
|
|
497
|
-
hasAnyVersion: boolean;
|
|
498
|
-
/** True if all versions are expired (validUntil < today, no draft / no future). */
|
|
499
|
-
allExpired: boolean;
|
|
500
|
-
/** First open draft (for the header KPI "offene Drafts"). */
|
|
501
|
-
draft: PlanVersionRow | null;
|
|
502
|
-
tenantCount: number;
|
|
503
|
-
}
|
|
456
|
+
// `clearable` emits null, not '' — see Quasar's use-field clearValue().
|
|
457
|
+
const search = ref<string | null>('');
|
|
504
458
|
|
|
505
459
|
const DEFAULT_ACCENTS: Record<string, string> = {
|
|
506
460
|
STARTER: '#64748b',
|
|
@@ -522,91 +476,19 @@ function planAccent(planKey: string): string {
|
|
|
522
476
|
return FALLBACK_ACCENTS[idx % FALLBACK_ACCENTS.length] ?? FALLBACK_ACCENTS[0]!;
|
|
523
477
|
}
|
|
524
478
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (v.validUntil && v.validUntil.slice(0, 10) < today) return false;
|
|
533
|
-
return true;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function isFutureScheduled(v: PlanVersionRow, today: string): boolean {
|
|
537
|
-
if (v.publishedAt === null) return false;
|
|
538
|
-
if (!v.validFrom) return false;
|
|
539
|
-
return v.validFrom.slice(0, 10) > today;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function isExpired(v: PlanVersionRow, today: string): boolean {
|
|
543
|
-
if (v.publishedAt === null) return false;
|
|
544
|
-
if (!v.validUntil) return false;
|
|
545
|
-
return v.validUntil.slice(0, 10) < today;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const resolvedPlans = computed<ResolvedPlan[]>(() => {
|
|
549
|
-
const today = todayIsoDate();
|
|
550
|
-
return [...props.plans]
|
|
551
|
-
.sort((a, b) => a.sortOrder - b.sortOrder || a.planKey.localeCompare(b.planKey))
|
|
552
|
-
.map<ResolvedPlan>((plan) => {
|
|
553
|
-
const versions = props.versionsByPlanId[plan.id] ?? [];
|
|
554
|
-
const drafts = versions.filter((v) => v.publishedAt === null);
|
|
555
|
-
const published = versions.filter((v) => v.publishedAt !== null);
|
|
556
|
-
const currentLive = published.find((v) => isCurrentlyValid(v, today)) ?? null;
|
|
557
|
-
const futureScheduled = published
|
|
558
|
-
.filter((v) => isFutureScheduled(v, today))
|
|
559
|
-
.sort((a, b) => (a.validFrom ?? '').localeCompare(b.validFrom ?? ''));
|
|
560
|
-
|
|
561
|
-
// Parent = currentLive preferred; otherwise the next upcoming
|
|
562
|
-
// future version; otherwise null (drafts only → root row without a version).
|
|
563
|
-
const primary = currentLive ?? futureScheduled[0] ?? null;
|
|
564
|
-
|
|
565
|
-
// Sub-rows = all visible versions except the parent
|
|
566
|
-
const subSet = new Set<string>();
|
|
567
|
-
if (primary) subSet.add(primary.id);
|
|
568
|
-
const subRows: PlanVersionRow[] = [];
|
|
569
|
-
for (const v of futureScheduled) {
|
|
570
|
-
if (!subSet.has(v.id)) {
|
|
571
|
-
subRows.push(v);
|
|
572
|
-
subSet.add(v.id);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
for (const d of drafts) {
|
|
576
|
-
if (!subSet.has(d.id)) {
|
|
577
|
-
subRows.push(d);
|
|
578
|
-
subSet.add(d.id);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
const allExpired =
|
|
583
|
-
versions.length > 0 &&
|
|
584
|
-
drafts.length === 0 &&
|
|
585
|
-
published.every((v) => isExpired(v, today)) &&
|
|
586
|
-
futureScheduled.length === 0;
|
|
587
|
-
|
|
588
|
-
return {
|
|
589
|
-
plan,
|
|
590
|
-
planKey: plan.planKey,
|
|
591
|
-
label: plan.label,
|
|
592
|
-
description: plan.description ?? null,
|
|
593
|
-
currentLive,
|
|
594
|
-
primary,
|
|
595
|
-
subRows,
|
|
596
|
-
hasAnyVersion: versions.length > 0,
|
|
597
|
-
allExpired,
|
|
598
|
-
draft: drafts[0] ?? null,
|
|
599
|
-
tenantCount: props.tenantCountsByPlanKey[plan.planKey] ?? 0,
|
|
600
|
-
};
|
|
601
|
-
});
|
|
602
|
-
});
|
|
479
|
+
const resolvedPlans = computed(() =>
|
|
480
|
+
resolvePlans({
|
|
481
|
+
plans: props.plans,
|
|
482
|
+
versionsByPlanId: props.versionsByPlanId,
|
|
483
|
+
tenantCountsByPlanKey: props.tenantCountsByPlanKey,
|
|
484
|
+
}),
|
|
485
|
+
);
|
|
603
486
|
|
|
604
487
|
const filteredPlans = computed(() => {
|
|
605
488
|
// Plans with only expired versions are hidden entirely
|
|
606
|
-
//
|
|
607
|
-
// in the admin listing).
|
|
489
|
+
// — only currently-valid and future ones stay visible in the admin listing.
|
|
608
490
|
const base = resolvedPlans.value.filter((p) => !p.allExpired);
|
|
609
|
-
const q = search.value.trim().toLocaleLowerCase(intlLocale.value);
|
|
491
|
+
const q = (search.value ?? '').trim().toLocaleLowerCase(intlLocale.value);
|
|
610
492
|
if (!q) return base;
|
|
611
493
|
return base.filter(
|
|
612
494
|
(p) =>
|
|
@@ -615,21 +497,8 @@ const filteredPlans = computed(() => {
|
|
|
615
497
|
);
|
|
616
498
|
});
|
|
617
499
|
|
|
618
|
-
const liveCount = computed(() => resolvedPlans.value.filter((p) => p.currentLive !== null).length);
|
|
619
|
-
const draftCount = computed(() => resolvedPlans.value.filter((p) => p.draft !== null).length);
|
|
620
|
-
const totalTenants = computed(() => resolvedPlans.value.reduce((sum, p) => sum + p.tenantCount, 0));
|
|
621
|
-
|
|
622
|
-
const summary = computed(() =>
|
|
623
|
-
formatMessage(msg.value.list.summary, {
|
|
624
|
-
plans: resolvedPlans.value.length,
|
|
625
|
-
live: liveCount.value,
|
|
626
|
-
drafts: draftCount.value,
|
|
627
|
-
tenants: totalTenants.value,
|
|
628
|
-
}),
|
|
629
|
-
);
|
|
630
|
-
|
|
631
500
|
const emptyNoMatch = computed(() =>
|
|
632
|
-
formatMessage(msg.value.list.emptyNoMatch, { query: search.value }),
|
|
501
|
+
formatMessage(msg.value.list.emptyNoMatch, { query: search.value ?? '' }),
|
|
633
502
|
);
|
|
634
503
|
|
|
635
504
|
function validFromLabel(validFrom: string | null | undefined): string {
|
|
@@ -661,7 +530,7 @@ function tenantBarWidth(count: number): string {
|
|
|
661
530
|
return `${Math.min(100, count / 1.5)}%`;
|
|
662
531
|
}
|
|
663
532
|
|
|
664
|
-
function onNewVersion(row: ResolvedPlan): void {
|
|
533
|
+
function onNewVersion(row: ResolvedPlan<PlanRow, PlanVersionRow>): void {
|
|
665
534
|
if (row.draft) return; // already an open draft → no new one
|
|
666
535
|
const basis = row.currentLive;
|
|
667
536
|
if (!basis) {
|
|
@@ -672,7 +541,7 @@ function onNewVersion(row: ResolvedPlan): void {
|
|
|
672
541
|
emit('newVersion', row.plan, basis);
|
|
673
542
|
}
|
|
674
543
|
|
|
675
|
-
function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
544
|
+
function hasAnyPublished(row: ResolvedPlan<PlanRow, PlanVersionRow>): boolean {
|
|
676
545
|
// Superseded or expired counts too — the plan root stays in the DB
|
|
677
546
|
// forever for contract-protection P1 reasons.
|
|
678
547
|
const versions = props.versionsByPlanId[row.plan.id] ?? [];
|
|
@@ -681,230 +550,110 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
681
550
|
</script>
|
|
682
551
|
|
|
683
552
|
<style scoped>
|
|
684
|
-
.
|
|
685
|
-
|
|
686
|
-
--
|
|
687
|
-
--
|
|
688
|
-
|
|
689
|
-
--pl-border-strong: #d1d5db;
|
|
690
|
-
--pl-text: #0f172a;
|
|
691
|
-
--pl-text-2: #475569;
|
|
692
|
-
--pl-text-3: #94a3b8;
|
|
693
|
-
--pl-primary: #2563eb;
|
|
694
|
-
--pl-primary-700: #1d4ed8;
|
|
695
|
-
--pl-live-bg: #ecfdf5;
|
|
696
|
-
--pl-draft-bg: #fffbeb;
|
|
697
|
-
--pl-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
698
|
-
--pl-font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
|
699
|
-
|
|
700
|
-
padding: 22px 26px;
|
|
701
|
-
background: var(--pl-bg);
|
|
702
|
-
color: var(--pl-text);
|
|
703
|
-
font-family: var(--pl-font-sans);
|
|
553
|
+
.sa-plan-list {
|
|
554
|
+
/* padding: 22px 26px; */
|
|
555
|
+
background: var(--sa-bg-app);
|
|
556
|
+
color: var(--sa-heading);
|
|
557
|
+
font-family: var(--sa-font-body);
|
|
704
558
|
min-height: 100%;
|
|
705
559
|
box-sizing: border-box;
|
|
706
560
|
}
|
|
707
|
-
.pl * {
|
|
708
|
-
box-sizing: border-box;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
.pl-page-head {
|
|
712
|
-
display: flex;
|
|
713
|
-
align-items: flex-end;
|
|
714
|
-
gap: 20px;
|
|
715
|
-
margin-bottom: 18px;
|
|
716
|
-
}
|
|
717
|
-
.pl-h-title {
|
|
718
|
-
font-size: 22px;
|
|
719
|
-
font-weight: 700;
|
|
720
|
-
letter-spacing: -0.02em;
|
|
721
|
-
margin: 0;
|
|
722
|
-
}
|
|
723
|
-
.pl-h-sub {
|
|
724
|
-
font-size: 12.5px;
|
|
725
|
-
color: var(--pl-text-2);
|
|
726
|
-
margin: 3px 0 0;
|
|
727
|
-
}
|
|
728
|
-
.pl-head-actions {
|
|
729
|
-
margin-left: auto;
|
|
730
|
-
display: flex;
|
|
731
|
-
gap: 8px;
|
|
732
|
-
}
|
|
733
561
|
|
|
734
562
|
/* Buttons */
|
|
735
|
-
.pl-btn {
|
|
736
|
-
display: inline-flex;
|
|
737
|
-
align-items: center;
|
|
738
|
-
gap: 7px;
|
|
739
|
-
padding: 8px 14px;
|
|
740
|
-
border-radius: 7px;
|
|
741
|
-
font: 500 13px var(--pl-font-sans);
|
|
742
|
-
cursor: pointer;
|
|
743
|
-
border: 1px solid var(--pl-border-strong);
|
|
744
|
-
background: #fff;
|
|
745
|
-
color: var(--pl-text);
|
|
746
|
-
transition:
|
|
747
|
-
background 0.12s,
|
|
748
|
-
border-color 0.12s;
|
|
749
|
-
}
|
|
750
|
-
.pl-btn:hover:not(:disabled) {
|
|
751
|
-
background: var(--pl-surface-2);
|
|
752
|
-
}
|
|
753
|
-
.pl-btn:disabled {
|
|
754
|
-
cursor: not-allowed;
|
|
755
|
-
opacity: 0.4;
|
|
756
|
-
}
|
|
757
|
-
.pl-btn--primary {
|
|
758
|
-
background: var(--pl-primary);
|
|
759
|
-
border-color: var(--pl-primary);
|
|
760
|
-
color: #fff;
|
|
761
|
-
}
|
|
762
|
-
.pl-btn--primary:hover {
|
|
763
|
-
background: var(--pl-primary-700);
|
|
764
|
-
}
|
|
765
|
-
.pl-btn--ghost {
|
|
766
|
-
border-color: transparent;
|
|
767
|
-
background: transparent;
|
|
768
|
-
}
|
|
769
|
-
.pl-btn--ghost:hover:not(:disabled) {
|
|
770
|
-
background: rgba(15, 23, 42, 0.05);
|
|
771
|
-
}
|
|
772
|
-
.pl-btn--danger {
|
|
773
|
-
color: #ef4444;
|
|
774
|
-
}
|
|
775
|
-
.pl-btn--danger:hover:not(:disabled) {
|
|
776
|
-
background: #fef2f2;
|
|
777
|
-
color: #b91c1c;
|
|
778
|
-
}
|
|
779
|
-
.pl-btn--sm {
|
|
780
|
-
padding: 5px 8px;
|
|
781
|
-
font-size: 12px;
|
|
782
|
-
gap: 5px;
|
|
783
|
-
}
|
|
784
563
|
|
|
785
564
|
/* List wrap */
|
|
786
|
-
.
|
|
787
|
-
background: var(--
|
|
788
|
-
border: 1px solid var(--
|
|
565
|
+
.sa-plan-list-wrap {
|
|
566
|
+
background: var(--sa-bg-surface);
|
|
567
|
+
border: 1px solid var(--sa-border);
|
|
789
568
|
border-radius: 10px;
|
|
790
569
|
overflow: hidden;
|
|
791
570
|
}
|
|
792
|
-
.
|
|
571
|
+
.sa-plan-list-toolbar {
|
|
793
572
|
display: flex;
|
|
794
573
|
align-items: center;
|
|
795
574
|
gap: 10px;
|
|
796
575
|
padding: 12px 16px;
|
|
797
|
-
border-bottom: 1px solid var(--
|
|
798
|
-
background:
|
|
799
|
-
}
|
|
800
|
-
.pl-search {
|
|
801
|
-
flex: 1;
|
|
802
|
-
max-width: 360px;
|
|
803
|
-
display: flex;
|
|
804
|
-
align-items: center;
|
|
805
|
-
gap: 8px;
|
|
806
|
-
background: #fff;
|
|
807
|
-
border: 1px solid var(--pl-border);
|
|
808
|
-
border-radius: 7px;
|
|
809
|
-
padding: 7px 10px;
|
|
810
|
-
}
|
|
811
|
-
.pl-search-ico {
|
|
812
|
-
color: var(--pl-text-3);
|
|
813
|
-
display: inline-flex;
|
|
814
|
-
}
|
|
815
|
-
.pl-search input {
|
|
816
|
-
flex: 1;
|
|
817
|
-
border: 0;
|
|
818
|
-
outline: 0;
|
|
819
|
-
font: 13px var(--pl-font-sans);
|
|
820
|
-
background: transparent;
|
|
821
|
-
color: var(--pl-text);
|
|
822
|
-
}
|
|
823
|
-
.pl-kbd {
|
|
824
|
-
font: 600 10.5px var(--pl-font-mono);
|
|
825
|
-
background: #f1f5f9;
|
|
826
|
-
color: var(--pl-text-2);
|
|
827
|
-
padding: 2px 6px;
|
|
828
|
-
border-radius: 4px;
|
|
829
|
-
border: 1px solid #e2e8f0;
|
|
576
|
+
border-bottom: 1px solid var(--sa-border);
|
|
577
|
+
background: var(--sa-bg-surface-2);
|
|
830
578
|
}
|
|
831
|
-
.
|
|
579
|
+
/* The search takes the row; the sort hint keeps its content width. */
|
|
580
|
+
.sa-plan-list-toolbar > .q-field {
|
|
832
581
|
flex: 1;
|
|
833
582
|
}
|
|
834
|
-
.
|
|
583
|
+
.sa-plan-list-sortinfo {
|
|
835
584
|
margin-left: auto;
|
|
836
585
|
font-size: 11.5px;
|
|
837
|
-
color: var(--
|
|
586
|
+
color: var(--sa-muted-light);
|
|
838
587
|
}
|
|
839
588
|
|
|
840
589
|
/* List grid */
|
|
841
|
-
.
|
|
590
|
+
.sa-plan-list-list {
|
|
842
591
|
display: grid;
|
|
843
592
|
grid-template-columns: 1.6fr 0.9fr 0.7fr 0.9fr 1.4fr 160px;
|
|
844
593
|
align-items: center;
|
|
845
594
|
}
|
|
846
|
-
.
|
|
595
|
+
.sa-plan-list-list-head {
|
|
847
596
|
display: contents;
|
|
848
597
|
}
|
|
849
|
-
.
|
|
598
|
+
.sa-plan-list-list-head > div {
|
|
850
599
|
background: #fbfbfd;
|
|
851
600
|
padding: 10px 16px;
|
|
852
601
|
font-size: 10.5px;
|
|
853
602
|
text-transform: uppercase;
|
|
854
603
|
letter-spacing: 0.1em;
|
|
855
|
-
color: var(--
|
|
604
|
+
color: var(--sa-muted-dark);
|
|
856
605
|
font-weight: 700;
|
|
857
|
-
border-bottom: 1px solid var(--
|
|
606
|
+
border-bottom: 1px solid var(--sa-border);
|
|
858
607
|
}
|
|
859
|
-
.
|
|
608
|
+
.sa-plan-list-empty {
|
|
860
609
|
grid-column: 1 / -1;
|
|
861
610
|
padding: 32px 24px;
|
|
862
611
|
text-align: center;
|
|
863
|
-
color: var(--
|
|
612
|
+
color: var(--sa-muted-light);
|
|
864
613
|
font-style: italic;
|
|
865
614
|
font-size: 13px;
|
|
866
615
|
}
|
|
867
|
-
.
|
|
616
|
+
.sa-plan-list-row {
|
|
868
617
|
display: contents;
|
|
869
618
|
cursor: pointer;
|
|
870
619
|
}
|
|
871
|
-
.
|
|
620
|
+
.sa-plan-list-row > .sa-plan-list-cell {
|
|
872
621
|
padding: 14px 16px;
|
|
873
|
-
border-bottom: 1px solid
|
|
622
|
+
border-bottom: 1px solid var(--sa-border-soft);
|
|
874
623
|
transition: background 0.12s;
|
|
875
624
|
}
|
|
876
|
-
.
|
|
625
|
+
.sa-plan-list-row:hover > .sa-plan-list-cell {
|
|
877
626
|
background: #fcfcfd;
|
|
878
627
|
}
|
|
879
|
-
.
|
|
880
|
-
background: var(--
|
|
881
|
-
animation:
|
|
628
|
+
.sa-plan-list-row--new > .sa-plan-list-cell {
|
|
629
|
+
background: var(--sa-positive-bg) !important;
|
|
630
|
+
animation: sa-plan-list-flashNew 2.4s ease-out;
|
|
882
631
|
}
|
|
883
|
-
@keyframes
|
|
632
|
+
@keyframes sa-plan-list-flashNew {
|
|
884
633
|
0% {
|
|
885
634
|
background: #d1fae5 !important;
|
|
886
635
|
}
|
|
887
636
|
100% {
|
|
888
|
-
background: var(--
|
|
637
|
+
background: var(--sa-positive-bg) !important;
|
|
889
638
|
}
|
|
890
639
|
}
|
|
891
640
|
|
|
892
641
|
/* Sub-rows (drafts + future-scheduled versions, indented under the parent) */
|
|
893
|
-
.
|
|
642
|
+
.sa-plan-list-row--sub > .sa-plan-list-cell {
|
|
894
643
|
background: #fafbfd;
|
|
895
644
|
padding-top: 10px;
|
|
896
645
|
padding-bottom: 10px;
|
|
897
646
|
}
|
|
898
|
-
.
|
|
899
|
-
background:
|
|
647
|
+
.sa-plan-list-row--sub:hover > .sa-plan-list-cell {
|
|
648
|
+
background: var(--sa-border-soft);
|
|
900
649
|
}
|
|
901
|
-
.
|
|
650
|
+
.sa-plan-list-cell--sub-name {
|
|
902
651
|
display: flex;
|
|
903
652
|
align-items: center;
|
|
904
653
|
gap: 12px;
|
|
905
654
|
padding-left: 32px !important;
|
|
906
655
|
}
|
|
907
|
-
.
|
|
656
|
+
.sa-plan-list-sub-tree {
|
|
908
657
|
display: inline-flex;
|
|
909
658
|
align-items: center;
|
|
910
659
|
width: 28px;
|
|
@@ -912,7 +661,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
912
661
|
flex: 0 0 auto;
|
|
913
662
|
position: relative;
|
|
914
663
|
}
|
|
915
|
-
.
|
|
664
|
+
.sa-plan-list-sub-tree-elbow {
|
|
916
665
|
position: absolute;
|
|
917
666
|
top: 0;
|
|
918
667
|
bottom: 50%;
|
|
@@ -922,73 +671,73 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
922
671
|
border-bottom: 1.5px solid #cbd5e1;
|
|
923
672
|
border-bottom-left-radius: 6px;
|
|
924
673
|
}
|
|
925
|
-
.
|
|
674
|
+
.sa-plan-list-sub-titles {
|
|
926
675
|
min-width: 0;
|
|
927
676
|
}
|
|
928
|
-
.
|
|
677
|
+
.sa-plan-list-sub-title {
|
|
929
678
|
font-size: 13px;
|
|
930
679
|
font-weight: 600;
|
|
931
|
-
color: var(--
|
|
680
|
+
color: var(--sa-muted-dark);
|
|
932
681
|
display: flex;
|
|
933
682
|
align-items: center;
|
|
934
683
|
gap: 8px;
|
|
935
684
|
}
|
|
936
|
-
.
|
|
685
|
+
.sa-plan-list-sub-desc {
|
|
937
686
|
font-size: 11.5px;
|
|
938
|
-
color:
|
|
687
|
+
color: var(--sa-muted);
|
|
939
688
|
margin-top: 2px;
|
|
940
689
|
overflow: hidden;
|
|
941
690
|
text-overflow: ellipsis;
|
|
942
691
|
white-space: nowrap;
|
|
943
692
|
}
|
|
944
|
-
.
|
|
693
|
+
.sa-plan-list-version-num--sub {
|
|
945
694
|
font-size: 13px;
|
|
946
695
|
font-weight: 600;
|
|
947
|
-
color: var(--
|
|
696
|
+
color: var(--sa-muted-dark);
|
|
948
697
|
}
|
|
949
|
-
.
|
|
950
|
-
color: var(--
|
|
698
|
+
.sa-plan-list-cell--sub-impact {
|
|
699
|
+
color: var(--sa-muted-light);
|
|
951
700
|
}
|
|
952
701
|
|
|
953
|
-
.
|
|
702
|
+
.sa-plan-list-plan-name {
|
|
954
703
|
display: flex;
|
|
955
704
|
align-items: center;
|
|
956
705
|
gap: 12px;
|
|
957
706
|
}
|
|
958
|
-
.
|
|
707
|
+
.sa-plan-list-plan-mark {
|
|
959
708
|
width: 36px;
|
|
960
709
|
height: 36px;
|
|
961
710
|
border-radius: 8px;
|
|
962
711
|
display: grid;
|
|
963
712
|
place-items: center;
|
|
964
|
-
font: 700 11px var(--
|
|
713
|
+
font: 700 11px var(--sa-font-mono);
|
|
965
714
|
letter-spacing: 0.04em;
|
|
966
715
|
flex: 0 0 auto;
|
|
967
716
|
border: 1px solid;
|
|
968
717
|
}
|
|
969
|
-
.
|
|
718
|
+
.sa-plan-list-plan-titles {
|
|
970
719
|
min-width: 0;
|
|
971
720
|
}
|
|
972
|
-
.
|
|
721
|
+
.sa-plan-list-plan-title {
|
|
973
722
|
font-size: 14.5px;
|
|
974
723
|
font-weight: 700;
|
|
975
|
-
color: var(--
|
|
724
|
+
color: var(--sa-heading);
|
|
976
725
|
letter-spacing: -0.01em;
|
|
977
726
|
display: flex;
|
|
978
727
|
align-items: center;
|
|
979
728
|
gap: 8px;
|
|
980
729
|
flex-wrap: wrap;
|
|
981
730
|
}
|
|
982
|
-
.
|
|
731
|
+
.sa-plan-list-plan-desc {
|
|
983
732
|
font-size: 11.5px;
|
|
984
|
-
color:
|
|
733
|
+
color: var(--sa-muted);
|
|
985
734
|
margin-top: 2px;
|
|
986
735
|
overflow: hidden;
|
|
987
736
|
text-overflow: ellipsis;
|
|
988
737
|
white-space: nowrap;
|
|
989
738
|
}
|
|
990
739
|
|
|
991
|
-
.
|
|
740
|
+
.sa-plan-list-cell--status {
|
|
992
741
|
display: flex;
|
|
993
742
|
flex-direction: column;
|
|
994
743
|
gap: 3px;
|
|
@@ -996,7 +745,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
996
745
|
}
|
|
997
746
|
|
|
998
747
|
/* Chips */
|
|
999
|
-
.
|
|
748
|
+
.sa-plan-list-chip {
|
|
1000
749
|
display: inline-flex;
|
|
1001
750
|
align-items: center;
|
|
1002
751
|
gap: 5px;
|
|
@@ -1004,41 +753,41 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
1004
753
|
border-radius: 999px;
|
|
1005
754
|
font-size: 11px;
|
|
1006
755
|
font-weight: 600;
|
|
1007
|
-
background: var(--
|
|
1008
|
-
color: var(--
|
|
1009
|
-
border: 1px solid var(--
|
|
756
|
+
background: var(--sa-bg-surface-2);
|
|
757
|
+
color: var(--sa-muted-dark);
|
|
758
|
+
border: 1px solid var(--sa-border);
|
|
1010
759
|
}
|
|
1011
|
-
.
|
|
760
|
+
.sa-plan-list-chip--tiny {
|
|
1012
761
|
padding: 1px 6px;
|
|
1013
762
|
font-size: 10px;
|
|
1014
763
|
}
|
|
1015
|
-
.
|
|
764
|
+
.sa-plan-list-chip--new {
|
|
1016
765
|
background: #dbeafe;
|
|
1017
766
|
color: #1e40af;
|
|
1018
767
|
border-color: #bfdbfe;
|
|
1019
768
|
font-size: 10px;
|
|
1020
769
|
}
|
|
1021
|
-
.
|
|
1022
|
-
background: var(--
|
|
770
|
+
.sa-plan-list-chip--live {
|
|
771
|
+
background: var(--sa-positive-bg);
|
|
1023
772
|
color: #047857;
|
|
1024
773
|
border-color: #a7f3d0;
|
|
1025
774
|
}
|
|
1026
|
-
.
|
|
1027
|
-
background: var(--
|
|
775
|
+
.sa-plan-list-chip--draft {
|
|
776
|
+
background: var(--sa-warning-bg);
|
|
1028
777
|
color: #b45309;
|
|
1029
778
|
border-color: #fde68a;
|
|
1030
779
|
}
|
|
1031
|
-
.
|
|
1032
|
-
background:
|
|
1033
|
-
color: var(--
|
|
780
|
+
.sa-plan-list-chip--supersed {
|
|
781
|
+
background: var(--sa-border-soft);
|
|
782
|
+
color: var(--sa-muted-dark);
|
|
1034
783
|
border-color: #cbd5e1;
|
|
1035
784
|
}
|
|
1036
|
-
.
|
|
785
|
+
.sa-plan-list-chip--scheduled {
|
|
1037
786
|
background: #eef2ff;
|
|
1038
787
|
color: #4338ca;
|
|
1039
788
|
border-color: #c7d2fe;
|
|
1040
789
|
}
|
|
1041
|
-
.
|
|
790
|
+
.sa-plan-list-chip--dot::before {
|
|
1042
791
|
content: '';
|
|
1043
792
|
width: 6px;
|
|
1044
793
|
height: 6px;
|
|
@@ -1046,62 +795,62 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
1046
795
|
background: currentColor;
|
|
1047
796
|
}
|
|
1048
797
|
|
|
1049
|
-
.
|
|
798
|
+
.sa-plan-list-version-num {
|
|
1050
799
|
font-size: 13px;
|
|
1051
800
|
font-weight: 700;
|
|
1052
|
-
color: var(--
|
|
801
|
+
color: var(--sa-heading);
|
|
1053
802
|
}
|
|
1054
|
-
.
|
|
803
|
+
.sa-plan-list-version-num--muted {
|
|
1055
804
|
color: #cbd5e1;
|
|
1056
805
|
}
|
|
1057
|
-
.
|
|
806
|
+
.sa-plan-list-version-sub {
|
|
1058
807
|
font-size: 10.5px;
|
|
1059
|
-
color: var(--
|
|
808
|
+
color: var(--sa-muted-light);
|
|
1060
809
|
margin-top: 2px;
|
|
1061
810
|
}
|
|
1062
811
|
|
|
1063
|
-
.
|
|
812
|
+
.sa-plan-list-price-big {
|
|
1064
813
|
font-size: 14px;
|
|
1065
814
|
font-weight: 700;
|
|
1066
|
-
color: var(--
|
|
815
|
+
color: var(--sa-heading);
|
|
1067
816
|
}
|
|
1068
|
-
.
|
|
817
|
+
.sa-plan-list-price-unit {
|
|
1069
818
|
font-size: 11px;
|
|
1070
|
-
color: var(--
|
|
819
|
+
color: var(--sa-muted-light);
|
|
1071
820
|
}
|
|
1072
|
-
.
|
|
821
|
+
.sa-plan-list-price-sub {
|
|
1073
822
|
font-size: 10.5px;
|
|
1074
|
-
color: var(--
|
|
823
|
+
color: var(--sa-muted-light);
|
|
1075
824
|
margin-top: 2px;
|
|
1076
825
|
}
|
|
1077
|
-
.
|
|
826
|
+
.sa-plan-list-price-text {
|
|
1078
827
|
font-size: 13px;
|
|
1079
828
|
font-weight: 600;
|
|
1080
|
-
color: var(--
|
|
829
|
+
color: var(--sa-heading);
|
|
1081
830
|
}
|
|
1082
831
|
|
|
1083
|
-
.
|
|
832
|
+
.sa-plan-list-cell--tenants {
|
|
1084
833
|
display: flex;
|
|
1085
834
|
align-items: center;
|
|
1086
835
|
gap: 10px;
|
|
1087
836
|
}
|
|
1088
|
-
.
|
|
837
|
+
.sa-plan-list-tenant-num {
|
|
1089
838
|
font-size: 16px;
|
|
1090
839
|
font-weight: 700;
|
|
1091
|
-
color: var(--
|
|
840
|
+
color: var(--sa-heading);
|
|
1092
841
|
}
|
|
1093
|
-
.
|
|
842
|
+
.sa-plan-list-tenant-bar {
|
|
1094
843
|
flex: 1;
|
|
1095
844
|
height: 6px;
|
|
1096
|
-
background:
|
|
845
|
+
background: var(--sa-border-soft);
|
|
1097
846
|
border-radius: 999px;
|
|
1098
847
|
overflow: hidden;
|
|
1099
848
|
}
|
|
1100
|
-
.
|
|
849
|
+
.sa-plan-list-tenant-bar-fill {
|
|
1101
850
|
height: 100%;
|
|
1102
851
|
}
|
|
1103
852
|
|
|
1104
|
-
.
|
|
853
|
+
.sa-plan-list-cell--actions {
|
|
1105
854
|
display: flex;
|
|
1106
855
|
gap: 4px;
|
|
1107
856
|
justify-content: flex-end;
|