@saasicat/ui-vue 0.21.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-BY_t9prv.d.cts → catalog-DJcfm5N2.d.cts} +20 -7
- package/dist/{catalog-BY_t9prv.d.ts → catalog-DJcfm5N2.d.ts} +20 -7
- package/dist/{chunk-JN6HZGLV.js → chunk-F7NRM6KI.js} +54 -28
- package/dist/{chunk-7IFKVGZV.js → chunk-LY52T4N4.js} +21 -2
- package/dist/{chunk-OTP3OSSW.js → chunk-XMMFN2WJ.js} +74 -3
- package/dist/client/index.cjs +132 -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 +319 -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 +76 -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-c_Eqsu9u.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
- package/dist/{use-super-admin-i18n-DlJRGIVq.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 +22 -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/AdminFilters.vue +12 -0
- package/src/components/admin-page/AdminHero.vue +22 -5
- package/src/components/admin-page/AdminKpi.vue +84 -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 +31 -16
- 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/PlanDetail.vue +92 -302
- package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
- package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
- package/src/components/plan-list/PlanList.vue +72 -290
- 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 +34 -10
- package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
- package/src/pages-standard/AuditPage.vue +53 -72
- package/src/pages-standard/BundlesPage.vue +128 -144
- package/src/pages-standard/DashboardPage.vue +43 -107
- package/src/pages-standard/DiscoveryPage.vue +158 -200
- package/src/pages-standard/EmailHistoryPage.vue +98 -88
- package/src/pages-standard/MarketingCatalogPage.vue +237 -267
- package/src/pages-standard/PilotsPage.vue +44 -60
- package/src/pages-standard/PlansPage.vue +230 -105
- package/src/pages-standard/PlatformEmailPage.vue +37 -40
- package/src/pages-standard/PromoCodeDetailPage.vue +16 -30
- package/src/pages-standard/PromoCodesPage.vue +86 -112
- package/src/pages-standard/SubscriptionsPage.vue +10 -17
- package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
- package/src/pages-standard/SuperAdminSetupWizard.vue +20 -17
- package/src/pages-standard/TenantDetailPage.vue +45 -41
- package/src/pages-standard/TenantsPage.vue +184 -264
- package/src/pages-standard/UsersPage.vue +69 -104
- 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 +6 -10
- 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 +1 -1
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +2 -13
- 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 +1 -1
- package/src/pages-standard/sa-theme.css +481 -71
- package/src/pages-standard/tenant-detail/TenantUsers.vue +2 -8
- 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-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 -1027
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -99
- 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 -249
- package/src/components/plan-cockpit/types.ts +0 -72
- package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="sa-plan-list">
|
|
3
|
-
<p class="sa-plan-list-summary">{{ summary }}</p>
|
|
4
|
-
|
|
5
3
|
<!-- List wrapper -->
|
|
6
4
|
<div class="sa-plan-list-wrap">
|
|
7
5
|
<div class="sa-plan-list-toolbar">
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
>
|
|
18
|
-
<circle cx="11" cy="11" r="7" />
|
|
19
|
-
<path d="M21 21l-4.35-4.35" />
|
|
20
|
-
</svg>
|
|
21
|
-
</span>
|
|
22
|
-
<input v-model="search" :placeholder="msg.list.searchPlaceholder" />
|
|
23
|
-
<span class="sa-plan-list-kbd">⌘ K</span>
|
|
24
|
-
</div>
|
|
25
|
-
<div class="sa-plan-list-toolbar-spacer" />
|
|
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>
|
|
26
15
|
<div class="sa-plan-list-sortinfo">{{ msg.list.sortedBy }}</div>
|
|
27
16
|
</div>
|
|
28
17
|
|
|
@@ -174,7 +163,7 @@
|
|
|
174
163
|
<div class="sa-plan-list-cell sa-plan-list-cell--actions" @click.stop>
|
|
175
164
|
<button
|
|
176
165
|
v-if="hasAnyPublished(p)"
|
|
177
|
-
class="sa-
|
|
166
|
+
class="sa-btn sa-btn--sm sa-btn--ghost"
|
|
178
167
|
type="button"
|
|
179
168
|
disabled
|
|
180
169
|
:title="msg.list.actionDeleteBlocked"
|
|
@@ -194,7 +183,7 @@
|
|
|
194
183
|
</button>
|
|
195
184
|
<button
|
|
196
185
|
v-else
|
|
197
|
-
class="sa-
|
|
186
|
+
class="sa-btn sa-btn--sm sa-btn--ghost sa-btn--danger"
|
|
198
187
|
type="button"
|
|
199
188
|
:title="msg.list.actionDeletePlan"
|
|
200
189
|
@click="$emit('archivePlan', p.plan, false)"
|
|
@@ -213,7 +202,7 @@
|
|
|
213
202
|
</svg>
|
|
214
203
|
</button>
|
|
215
204
|
<button
|
|
216
|
-
class="sa-
|
|
205
|
+
class="sa-btn sa-btn--sm sa-btn--ghost"
|
|
217
206
|
type="button"
|
|
218
207
|
:title="msg.list.actionClonePlan"
|
|
219
208
|
@click="$emit('clonePlan', p.plan)"
|
|
@@ -231,7 +220,7 @@
|
|
|
231
220
|
</svg>
|
|
232
221
|
</button>
|
|
233
222
|
<button
|
|
234
|
-
class="sa-
|
|
223
|
+
class="sa-btn sa-btn--sm sa-btn--ghost"
|
|
235
224
|
type="button"
|
|
236
225
|
:title="msg.list.actionNewVersion"
|
|
237
226
|
:disabled="!!p.draft"
|
|
@@ -251,7 +240,7 @@
|
|
|
251
240
|
</svg>
|
|
252
241
|
</button>
|
|
253
242
|
<button
|
|
254
|
-
class="sa-
|
|
243
|
+
class="sa-btn sa-btn--sm"
|
|
255
244
|
type="button"
|
|
256
245
|
:title="msg.list.actionOpenPlan"
|
|
257
246
|
@click="$emit('openPlan', p.plan)"
|
|
@@ -359,7 +348,7 @@
|
|
|
359
348
|
<div class="sa-plan-list-cell sa-plan-list-cell--actions" @click.stop>
|
|
360
349
|
<button
|
|
361
350
|
v-if="sub.publishedAt === null"
|
|
362
|
-
class="sa-
|
|
351
|
+
class="sa-btn sa-btn--sm sa-btn--ghost sa-btn--danger"
|
|
363
352
|
type="button"
|
|
364
353
|
:title="discardDraftTitle(sub.version)"
|
|
365
354
|
@click="$emit('discardDraft', p.plan, sub)"
|
|
@@ -379,7 +368,7 @@
|
|
|
379
368
|
</button>
|
|
380
369
|
<button
|
|
381
370
|
v-if="sub.publishedAt === null"
|
|
382
|
-
class="sa-
|
|
371
|
+
class="sa-btn sa-btn--sm sa-btn--ghost"
|
|
383
372
|
type="button"
|
|
384
373
|
:title="msg.list.actionEditDraft"
|
|
385
374
|
@click="$emit('editDraft', p.plan, sub)"
|
|
@@ -398,7 +387,7 @@
|
|
|
398
387
|
</svg>
|
|
399
388
|
</button>
|
|
400
389
|
<button
|
|
401
|
-
class="sa-
|
|
390
|
+
class="sa-btn sa-btn--sm"
|
|
402
391
|
type="button"
|
|
403
392
|
:title="msg.list.actionOpenInCockpit"
|
|
404
393
|
@click="$emit('openPlan', p.plan)"
|
|
@@ -423,6 +412,7 @@
|
|
|
423
412
|
</template>
|
|
424
413
|
|
|
425
414
|
<script setup lang="ts">
|
|
415
|
+
import { resolvePlans, type ResolvedPlan } from '../../client/resolve-plans.js';
|
|
426
416
|
import { computed, ref } from 'vue';
|
|
427
417
|
import type { PlanRow, PlanVersionRow } from '@saasicat/types';
|
|
428
418
|
import { formatMessage } from '../../client/i18n/format.js';
|
|
@@ -463,27 +453,8 @@ const msg = useSaMessages('plans');
|
|
|
463
453
|
const { locale, intlLocale } = useSuperAdminI18n();
|
|
464
454
|
const common = useSaMessages('common');
|
|
465
455
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
interface ResolvedPlan {
|
|
469
|
-
plan: PlanRow;
|
|
470
|
-
planKey: string;
|
|
471
|
-
label: string;
|
|
472
|
-
description: string | null;
|
|
473
|
-
/** Version currently active for new bookings (validFrom ≤ today < validUntil or validUntil null). */
|
|
474
|
-
currentLive: PlanVersionRow | null;
|
|
475
|
-
/** Version shown on the parent row (currentLive preferred). */
|
|
476
|
-
primary: PlanVersionRow | null;
|
|
477
|
-
/** Drafts + future-published versions (sorted: future ASC by validFrom, drafts at the end). */
|
|
478
|
-
subRows: PlanVersionRow[];
|
|
479
|
-
/** True if the plan has any versions at all (for hide logic). */
|
|
480
|
-
hasAnyVersion: boolean;
|
|
481
|
-
/** True if all versions are expired (validUntil < today, no draft / no future). */
|
|
482
|
-
allExpired: boolean;
|
|
483
|
-
/** First open draft (for the header KPI "offene Drafts"). */
|
|
484
|
-
draft: PlanVersionRow | null;
|
|
485
|
-
tenantCount: number;
|
|
486
|
-
}
|
|
456
|
+
// `clearable` emits null, not '' — see Quasar's use-field clearValue().
|
|
457
|
+
const search = ref<string | null>('');
|
|
487
458
|
|
|
488
459
|
const DEFAULT_ACCENTS: Record<string, string> = {
|
|
489
460
|
STARTER: '#64748b',
|
|
@@ -505,91 +476,19 @@ function planAccent(planKey: string): string {
|
|
|
505
476
|
return FALLBACK_ACCENTS[idx % FALLBACK_ACCENTS.length] ?? FALLBACK_ACCENTS[0]!;
|
|
506
477
|
}
|
|
507
478
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (v.validUntil && v.validUntil.slice(0, 10) < today) return false;
|
|
516
|
-
return true;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function isFutureScheduled(v: PlanVersionRow, today: string): boolean {
|
|
520
|
-
if (v.publishedAt === null) return false;
|
|
521
|
-
if (!v.validFrom) return false;
|
|
522
|
-
return v.validFrom.slice(0, 10) > today;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function isExpired(v: PlanVersionRow, today: string): boolean {
|
|
526
|
-
if (v.publishedAt === null) return false;
|
|
527
|
-
if (!v.validUntil) return false;
|
|
528
|
-
return v.validUntil.slice(0, 10) < today;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const resolvedPlans = computed<ResolvedPlan[]>(() => {
|
|
532
|
-
const today = todayIsoDate();
|
|
533
|
-
return [...props.plans]
|
|
534
|
-
.sort((a, b) => a.sortOrder - b.sortOrder || a.planKey.localeCompare(b.planKey))
|
|
535
|
-
.map<ResolvedPlan>((plan) => {
|
|
536
|
-
const versions = props.versionsByPlanId[plan.id] ?? [];
|
|
537
|
-
const drafts = versions.filter((v) => v.publishedAt === null);
|
|
538
|
-
const published = versions.filter((v) => v.publishedAt !== null);
|
|
539
|
-
const currentLive = published.find((v) => isCurrentlyValid(v, today)) ?? null;
|
|
540
|
-
const futureScheduled = published
|
|
541
|
-
.filter((v) => isFutureScheduled(v, today))
|
|
542
|
-
.sort((a, b) => (a.validFrom ?? '').localeCompare(b.validFrom ?? ''));
|
|
543
|
-
|
|
544
|
-
// Parent = currentLive preferred; otherwise the next upcoming
|
|
545
|
-
// future version; otherwise null (drafts only → root row without a version).
|
|
546
|
-
const primary = currentLive ?? futureScheduled[0] ?? null;
|
|
547
|
-
|
|
548
|
-
// Sub-rows = all visible versions except the parent
|
|
549
|
-
const subSet = new Set<string>();
|
|
550
|
-
if (primary) subSet.add(primary.id);
|
|
551
|
-
const subRows: PlanVersionRow[] = [];
|
|
552
|
-
for (const v of futureScheduled) {
|
|
553
|
-
if (!subSet.has(v.id)) {
|
|
554
|
-
subRows.push(v);
|
|
555
|
-
subSet.add(v.id);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
for (const d of drafts) {
|
|
559
|
-
if (!subSet.has(d.id)) {
|
|
560
|
-
subRows.push(d);
|
|
561
|
-
subSet.add(d.id);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const allExpired =
|
|
566
|
-
versions.length > 0 &&
|
|
567
|
-
drafts.length === 0 &&
|
|
568
|
-
published.every((v) => isExpired(v, today)) &&
|
|
569
|
-
futureScheduled.length === 0;
|
|
570
|
-
|
|
571
|
-
return {
|
|
572
|
-
plan,
|
|
573
|
-
planKey: plan.planKey,
|
|
574
|
-
label: plan.label,
|
|
575
|
-
description: plan.description ?? null,
|
|
576
|
-
currentLive,
|
|
577
|
-
primary,
|
|
578
|
-
subRows,
|
|
579
|
-
hasAnyVersion: versions.length > 0,
|
|
580
|
-
allExpired,
|
|
581
|
-
draft: drafts[0] ?? null,
|
|
582
|
-
tenantCount: props.tenantCountsByPlanKey[plan.planKey] ?? 0,
|
|
583
|
-
};
|
|
584
|
-
});
|
|
585
|
-
});
|
|
479
|
+
const resolvedPlans = computed(() =>
|
|
480
|
+
resolvePlans({
|
|
481
|
+
plans: props.plans,
|
|
482
|
+
versionsByPlanId: props.versionsByPlanId,
|
|
483
|
+
tenantCountsByPlanKey: props.tenantCountsByPlanKey,
|
|
484
|
+
}),
|
|
485
|
+
);
|
|
586
486
|
|
|
587
487
|
const filteredPlans = computed(() => {
|
|
588
488
|
// Plans with only expired versions are hidden entirely
|
|
589
|
-
//
|
|
590
|
-
// in the admin listing).
|
|
489
|
+
// — only currently-valid and future ones stay visible in the admin listing.
|
|
591
490
|
const base = resolvedPlans.value.filter((p) => !p.allExpired);
|
|
592
|
-
const q = search.value.trim().toLocaleLowerCase(intlLocale.value);
|
|
491
|
+
const q = (search.value ?? '').trim().toLocaleLowerCase(intlLocale.value);
|
|
593
492
|
if (!q) return base;
|
|
594
493
|
return base.filter(
|
|
595
494
|
(p) =>
|
|
@@ -598,21 +497,8 @@ const filteredPlans = computed(() => {
|
|
|
598
497
|
);
|
|
599
498
|
});
|
|
600
499
|
|
|
601
|
-
const liveCount = computed(() => resolvedPlans.value.filter((p) => p.currentLive !== null).length);
|
|
602
|
-
const draftCount = computed(() => resolvedPlans.value.filter((p) => p.draft !== null).length);
|
|
603
|
-
const totalTenants = computed(() => resolvedPlans.value.reduce((sum, p) => sum + p.tenantCount, 0));
|
|
604
|
-
|
|
605
|
-
const summary = computed(() =>
|
|
606
|
-
formatMessage(msg.value.list.summary, {
|
|
607
|
-
plans: resolvedPlans.value.length,
|
|
608
|
-
live: liveCount.value,
|
|
609
|
-
drafts: draftCount.value,
|
|
610
|
-
tenants: totalTenants.value,
|
|
611
|
-
}),
|
|
612
|
-
);
|
|
613
|
-
|
|
614
500
|
const emptyNoMatch = computed(() =>
|
|
615
|
-
formatMessage(msg.value.list.emptyNoMatch, { query: search.value }),
|
|
501
|
+
formatMessage(msg.value.list.emptyNoMatch, { query: search.value ?? '' }),
|
|
616
502
|
);
|
|
617
503
|
|
|
618
504
|
function validFromLabel(validFrom: string | null | undefined): string {
|
|
@@ -644,7 +530,7 @@ function tenantBarWidth(count: number): string {
|
|
|
644
530
|
return `${Math.min(100, count / 1.5)}%`;
|
|
645
531
|
}
|
|
646
532
|
|
|
647
|
-
function onNewVersion(row: ResolvedPlan): void {
|
|
533
|
+
function onNewVersion(row: ResolvedPlan<PlanRow, PlanVersionRow>): void {
|
|
648
534
|
if (row.draft) return; // already an open draft → no new one
|
|
649
535
|
const basis = row.currentLive;
|
|
650
536
|
if (!basis) {
|
|
@@ -655,7 +541,7 @@ function onNewVersion(row: ResolvedPlan): void {
|
|
|
655
541
|
emit('newVersion', row.plan, basis);
|
|
656
542
|
}
|
|
657
543
|
|
|
658
|
-
function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
544
|
+
function hasAnyPublished(row: ResolvedPlan<PlanRow, PlanVersionRow>): boolean {
|
|
659
545
|
// Superseded or expired counts too — the plan root stays in the DB
|
|
660
546
|
// forever for contract-protection P1 reasons.
|
|
661
547
|
const versions = props.versionsByPlanId[row.plan.id] ?? [];
|
|
@@ -665,94 +551,20 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
665
551
|
|
|
666
552
|
<style scoped>
|
|
667
553
|
.sa-plan-list {
|
|
668
|
-
|
|
669
|
-
--sa-
|
|
670
|
-
--sa-
|
|
671
|
-
--sa-
|
|
672
|
-
--sa-plan-list-border-strong: #d1d5db;
|
|
673
|
-
--sa-plan-list-text: #0f172a;
|
|
674
|
-
--sa-plan-list-text-2: #475569;
|
|
675
|
-
--sa-plan-list-text-3: #94a3b8;
|
|
676
|
-
--sa-plan-list-primary: #2563eb;
|
|
677
|
-
--sa-plan-list-primary-700: #1d4ed8;
|
|
678
|
-
--sa-plan-list-live-bg: #ecfdf5;
|
|
679
|
-
--sa-plan-list-draft-bg: #fffbeb;
|
|
680
|
-
--sa-plan-list-font-sans:
|
|
681
|
-
'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
682
|
-
--sa-plan-list-font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
|
683
|
-
|
|
684
|
-
padding: 22px 26px;
|
|
685
|
-
background: var(--sa-plan-list-bg);
|
|
686
|
-
color: var(--sa-plan-list-text);
|
|
687
|
-
font-family: var(--sa-plan-list-font-sans);
|
|
554
|
+
/* padding: 22px 26px; */
|
|
555
|
+
background: var(--sa-bg-app);
|
|
556
|
+
color: var(--sa-heading);
|
|
557
|
+
font-family: var(--sa-font-body);
|
|
688
558
|
min-height: 100%;
|
|
689
559
|
box-sizing: border-box;
|
|
690
560
|
}
|
|
691
|
-
.sa-plan-list * {
|
|
692
|
-
box-sizing: border-box;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
.sa-plan-list-summary {
|
|
696
|
-
font-size: 12.5px;
|
|
697
|
-
color: var(--sa-plan-list-text-2);
|
|
698
|
-
margin: 0 0 18px;
|
|
699
|
-
}
|
|
700
561
|
|
|
701
562
|
/* Buttons */
|
|
702
|
-
.sa-plan-list-btn {
|
|
703
|
-
display: inline-flex;
|
|
704
|
-
align-items: center;
|
|
705
|
-
gap: 7px;
|
|
706
|
-
padding: 8px 14px;
|
|
707
|
-
border-radius: 7px;
|
|
708
|
-
font: 500 13px var(--sa-plan-list-font-sans);
|
|
709
|
-
cursor: pointer;
|
|
710
|
-
border: 1px solid var(--sa-plan-list-border-strong);
|
|
711
|
-
background: #fff;
|
|
712
|
-
color: var(--sa-plan-list-text);
|
|
713
|
-
transition:
|
|
714
|
-
background 0.12s,
|
|
715
|
-
border-color 0.12s;
|
|
716
|
-
}
|
|
717
|
-
.sa-plan-list-btn:hover:not(:disabled) {
|
|
718
|
-
background: var(--sa-plan-list-surface-2);
|
|
719
|
-
}
|
|
720
|
-
.sa-plan-list-btn:disabled {
|
|
721
|
-
cursor: not-allowed;
|
|
722
|
-
opacity: 0.4;
|
|
723
|
-
}
|
|
724
|
-
.sa-plan-list-btn--primary {
|
|
725
|
-
background: var(--sa-plan-list-primary);
|
|
726
|
-
border-color: var(--sa-plan-list-primary);
|
|
727
|
-
color: #fff;
|
|
728
|
-
}
|
|
729
|
-
.sa-plan-list-btn--primary:hover {
|
|
730
|
-
background: var(--sa-plan-list-primary-700);
|
|
731
|
-
}
|
|
732
|
-
.sa-plan-list-btn--ghost {
|
|
733
|
-
border-color: transparent;
|
|
734
|
-
background: transparent;
|
|
735
|
-
}
|
|
736
|
-
.sa-plan-list-btn--ghost:hover:not(:disabled) {
|
|
737
|
-
background: rgba(15, 23, 42, 0.05);
|
|
738
|
-
}
|
|
739
|
-
.sa-plan-list-btn--danger {
|
|
740
|
-
color: #ef4444;
|
|
741
|
-
}
|
|
742
|
-
.sa-plan-list-btn--danger:hover:not(:disabled) {
|
|
743
|
-
background: #fef2f2;
|
|
744
|
-
color: #b91c1c;
|
|
745
|
-
}
|
|
746
|
-
.sa-plan-list-btn--sm {
|
|
747
|
-
padding: 5px 8px;
|
|
748
|
-
font-size: 12px;
|
|
749
|
-
gap: 5px;
|
|
750
|
-
}
|
|
751
563
|
|
|
752
564
|
/* List wrap */
|
|
753
565
|
.sa-plan-list-wrap {
|
|
754
|
-
background: var(--sa-
|
|
755
|
-
border: 1px solid var(--sa-
|
|
566
|
+
background: var(--sa-bg-surface);
|
|
567
|
+
border: 1px solid var(--sa-border);
|
|
756
568
|
border-radius: 10px;
|
|
757
569
|
overflow: hidden;
|
|
758
570
|
}
|
|
@@ -761,47 +573,17 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
761
573
|
align-items: center;
|
|
762
574
|
gap: 10px;
|
|
763
575
|
padding: 12px 16px;
|
|
764
|
-
border-bottom: 1px solid var(--sa-
|
|
765
|
-
background:
|
|
766
|
-
}
|
|
767
|
-
.sa-plan-list-search {
|
|
768
|
-
flex: 1;
|
|
769
|
-
max-width: 360px;
|
|
770
|
-
display: flex;
|
|
771
|
-
align-items: center;
|
|
772
|
-
gap: 8px;
|
|
773
|
-
background: #fff;
|
|
774
|
-
border: 1px solid var(--sa-plan-list-border);
|
|
775
|
-
border-radius: 7px;
|
|
776
|
-
padding: 7px 10px;
|
|
777
|
-
}
|
|
778
|
-
.sa-plan-list-search-ico {
|
|
779
|
-
color: var(--sa-plan-list-text-3);
|
|
780
|
-
display: inline-flex;
|
|
781
|
-
}
|
|
782
|
-
.sa-plan-list-search input {
|
|
783
|
-
flex: 1;
|
|
784
|
-
border: 0;
|
|
785
|
-
outline: 0;
|
|
786
|
-
font: 13px var(--sa-plan-list-font-sans);
|
|
787
|
-
background: transparent;
|
|
788
|
-
color: var(--sa-plan-list-text);
|
|
789
|
-
}
|
|
790
|
-
.sa-plan-list-kbd {
|
|
791
|
-
font: 600 10.5px var(--sa-plan-list-font-mono);
|
|
792
|
-
background: #f1f5f9;
|
|
793
|
-
color: var(--sa-plan-list-text-2);
|
|
794
|
-
padding: 2px 6px;
|
|
795
|
-
border-radius: 4px;
|
|
796
|
-
border: 1px solid #e2e8f0;
|
|
576
|
+
border-bottom: 1px solid var(--sa-border);
|
|
577
|
+
background: var(--sa-bg-surface-2);
|
|
797
578
|
}
|
|
798
|
-
.
|
|
579
|
+
/* The search takes the row; the sort hint keeps its content width. */
|
|
580
|
+
.sa-plan-list-toolbar > .q-field {
|
|
799
581
|
flex: 1;
|
|
800
582
|
}
|
|
801
583
|
.sa-plan-list-sortinfo {
|
|
802
584
|
margin-left: auto;
|
|
803
585
|
font-size: 11.5px;
|
|
804
|
-
color: var(--sa-
|
|
586
|
+
color: var(--sa-muted-light);
|
|
805
587
|
}
|
|
806
588
|
|
|
807
589
|
/* List grid */
|
|
@@ -819,15 +601,15 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
819
601
|
font-size: 10.5px;
|
|
820
602
|
text-transform: uppercase;
|
|
821
603
|
letter-spacing: 0.1em;
|
|
822
|
-
color: var(--sa-
|
|
604
|
+
color: var(--sa-muted-dark);
|
|
823
605
|
font-weight: 700;
|
|
824
|
-
border-bottom: 1px solid var(--sa-
|
|
606
|
+
border-bottom: 1px solid var(--sa-border);
|
|
825
607
|
}
|
|
826
608
|
.sa-plan-list-empty {
|
|
827
609
|
grid-column: 1 / -1;
|
|
828
610
|
padding: 32px 24px;
|
|
829
611
|
text-align: center;
|
|
830
|
-
color: var(--sa-
|
|
612
|
+
color: var(--sa-muted-light);
|
|
831
613
|
font-style: italic;
|
|
832
614
|
font-size: 13px;
|
|
833
615
|
}
|
|
@@ -837,14 +619,14 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
837
619
|
}
|
|
838
620
|
.sa-plan-list-row > .sa-plan-list-cell {
|
|
839
621
|
padding: 14px 16px;
|
|
840
|
-
border-bottom: 1px solid
|
|
622
|
+
border-bottom: 1px solid var(--sa-border-soft);
|
|
841
623
|
transition: background 0.12s;
|
|
842
624
|
}
|
|
843
625
|
.sa-plan-list-row:hover > .sa-plan-list-cell {
|
|
844
626
|
background: #fcfcfd;
|
|
845
627
|
}
|
|
846
628
|
.sa-plan-list-row--new > .sa-plan-list-cell {
|
|
847
|
-
background: var(--sa-
|
|
629
|
+
background: var(--sa-positive-bg) !important;
|
|
848
630
|
animation: sa-plan-list-flashNew 2.4s ease-out;
|
|
849
631
|
}
|
|
850
632
|
@keyframes sa-plan-list-flashNew {
|
|
@@ -852,7 +634,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
852
634
|
background: #d1fae5 !important;
|
|
853
635
|
}
|
|
854
636
|
100% {
|
|
855
|
-
background: var(--sa-
|
|
637
|
+
background: var(--sa-positive-bg) !important;
|
|
856
638
|
}
|
|
857
639
|
}
|
|
858
640
|
|
|
@@ -863,7 +645,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
863
645
|
padding-bottom: 10px;
|
|
864
646
|
}
|
|
865
647
|
.sa-plan-list-row--sub:hover > .sa-plan-list-cell {
|
|
866
|
-
background:
|
|
648
|
+
background: var(--sa-border-soft);
|
|
867
649
|
}
|
|
868
650
|
.sa-plan-list-cell--sub-name {
|
|
869
651
|
display: flex;
|
|
@@ -895,14 +677,14 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
895
677
|
.sa-plan-list-sub-title {
|
|
896
678
|
font-size: 13px;
|
|
897
679
|
font-weight: 600;
|
|
898
|
-
color: var(--sa-
|
|
680
|
+
color: var(--sa-muted-dark);
|
|
899
681
|
display: flex;
|
|
900
682
|
align-items: center;
|
|
901
683
|
gap: 8px;
|
|
902
684
|
}
|
|
903
685
|
.sa-plan-list-sub-desc {
|
|
904
686
|
font-size: 11.5px;
|
|
905
|
-
color:
|
|
687
|
+
color: var(--sa-muted);
|
|
906
688
|
margin-top: 2px;
|
|
907
689
|
overflow: hidden;
|
|
908
690
|
text-overflow: ellipsis;
|
|
@@ -911,10 +693,10 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
911
693
|
.sa-plan-list-version-num--sub {
|
|
912
694
|
font-size: 13px;
|
|
913
695
|
font-weight: 600;
|
|
914
|
-
color: var(--sa-
|
|
696
|
+
color: var(--sa-muted-dark);
|
|
915
697
|
}
|
|
916
698
|
.sa-plan-list-cell--sub-impact {
|
|
917
|
-
color: var(--sa-
|
|
699
|
+
color: var(--sa-muted-light);
|
|
918
700
|
}
|
|
919
701
|
|
|
920
702
|
.sa-plan-list-plan-name {
|
|
@@ -928,7 +710,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
928
710
|
border-radius: 8px;
|
|
929
711
|
display: grid;
|
|
930
712
|
place-items: center;
|
|
931
|
-
font: 700 11px var(--sa-
|
|
713
|
+
font: 700 11px var(--sa-font-mono);
|
|
932
714
|
letter-spacing: 0.04em;
|
|
933
715
|
flex: 0 0 auto;
|
|
934
716
|
border: 1px solid;
|
|
@@ -939,7 +721,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
939
721
|
.sa-plan-list-plan-title {
|
|
940
722
|
font-size: 14.5px;
|
|
941
723
|
font-weight: 700;
|
|
942
|
-
color: var(--sa-
|
|
724
|
+
color: var(--sa-heading);
|
|
943
725
|
letter-spacing: -0.01em;
|
|
944
726
|
display: flex;
|
|
945
727
|
align-items: center;
|
|
@@ -948,7 +730,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
948
730
|
}
|
|
949
731
|
.sa-plan-list-plan-desc {
|
|
950
732
|
font-size: 11.5px;
|
|
951
|
-
color:
|
|
733
|
+
color: var(--sa-muted);
|
|
952
734
|
margin-top: 2px;
|
|
953
735
|
overflow: hidden;
|
|
954
736
|
text-overflow: ellipsis;
|
|
@@ -971,9 +753,9 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
971
753
|
border-radius: 999px;
|
|
972
754
|
font-size: 11px;
|
|
973
755
|
font-weight: 600;
|
|
974
|
-
background: var(--sa-
|
|
975
|
-
color: var(--sa-
|
|
976
|
-
border: 1px solid var(--sa-
|
|
756
|
+
background: var(--sa-bg-surface-2);
|
|
757
|
+
color: var(--sa-muted-dark);
|
|
758
|
+
border: 1px solid var(--sa-border);
|
|
977
759
|
}
|
|
978
760
|
.sa-plan-list-chip--tiny {
|
|
979
761
|
padding: 1px 6px;
|
|
@@ -986,18 +768,18 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
986
768
|
font-size: 10px;
|
|
987
769
|
}
|
|
988
770
|
.sa-plan-list-chip--live {
|
|
989
|
-
background: var(--sa-
|
|
771
|
+
background: var(--sa-positive-bg);
|
|
990
772
|
color: #047857;
|
|
991
773
|
border-color: #a7f3d0;
|
|
992
774
|
}
|
|
993
775
|
.sa-plan-list-chip--draft {
|
|
994
|
-
background: var(--sa-
|
|
776
|
+
background: var(--sa-warning-bg);
|
|
995
777
|
color: #b45309;
|
|
996
778
|
border-color: #fde68a;
|
|
997
779
|
}
|
|
998
780
|
.sa-plan-list-chip--supersed {
|
|
999
|
-
background:
|
|
1000
|
-
color: var(--sa-
|
|
781
|
+
background: var(--sa-border-soft);
|
|
782
|
+
color: var(--sa-muted-dark);
|
|
1001
783
|
border-color: #cbd5e1;
|
|
1002
784
|
}
|
|
1003
785
|
.sa-plan-list-chip--scheduled {
|
|
@@ -1016,35 +798,35 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
1016
798
|
.sa-plan-list-version-num {
|
|
1017
799
|
font-size: 13px;
|
|
1018
800
|
font-weight: 700;
|
|
1019
|
-
color: var(--sa-
|
|
801
|
+
color: var(--sa-heading);
|
|
1020
802
|
}
|
|
1021
803
|
.sa-plan-list-version-num--muted {
|
|
1022
804
|
color: #cbd5e1;
|
|
1023
805
|
}
|
|
1024
806
|
.sa-plan-list-version-sub {
|
|
1025
807
|
font-size: 10.5px;
|
|
1026
|
-
color: var(--sa-
|
|
808
|
+
color: var(--sa-muted-light);
|
|
1027
809
|
margin-top: 2px;
|
|
1028
810
|
}
|
|
1029
811
|
|
|
1030
812
|
.sa-plan-list-price-big {
|
|
1031
813
|
font-size: 14px;
|
|
1032
814
|
font-weight: 700;
|
|
1033
|
-
color: var(--sa-
|
|
815
|
+
color: var(--sa-heading);
|
|
1034
816
|
}
|
|
1035
817
|
.sa-plan-list-price-unit {
|
|
1036
818
|
font-size: 11px;
|
|
1037
|
-
color: var(--sa-
|
|
819
|
+
color: var(--sa-muted-light);
|
|
1038
820
|
}
|
|
1039
821
|
.sa-plan-list-price-sub {
|
|
1040
822
|
font-size: 10.5px;
|
|
1041
|
-
color: var(--sa-
|
|
823
|
+
color: var(--sa-muted-light);
|
|
1042
824
|
margin-top: 2px;
|
|
1043
825
|
}
|
|
1044
826
|
.sa-plan-list-price-text {
|
|
1045
827
|
font-size: 13px;
|
|
1046
828
|
font-weight: 600;
|
|
1047
|
-
color: var(--sa-
|
|
829
|
+
color: var(--sa-heading);
|
|
1048
830
|
}
|
|
1049
831
|
|
|
1050
832
|
.sa-plan-list-cell--tenants {
|
|
@@ -1055,12 +837,12 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
|
|
|
1055
837
|
.sa-plan-list-tenant-num {
|
|
1056
838
|
font-size: 16px;
|
|
1057
839
|
font-weight: 700;
|
|
1058
|
-
color: var(--sa-
|
|
840
|
+
color: var(--sa-heading);
|
|
1059
841
|
}
|
|
1060
842
|
.sa-plan-list-tenant-bar {
|
|
1061
843
|
flex: 1;
|
|
1062
844
|
height: 6px;
|
|
1063
|
-
background:
|
|
845
|
+
background: var(--sa-border-soft);
|
|
1064
846
|
border-radius: 999px;
|
|
1065
847
|
overflow: hidden;
|
|
1066
848
|
}
|