@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.
Files changed (132) hide show
  1. package/dist/{catalog-BY_t9prv.d.cts → catalog-DJcfm5N2.d.cts} +20 -7
  2. package/dist/{catalog-BY_t9prv.d.ts → catalog-DJcfm5N2.d.ts} +20 -7
  3. package/dist/{chunk-JN6HZGLV.js → chunk-F7NRM6KI.js} +54 -28
  4. package/dist/{chunk-7IFKVGZV.js → chunk-LY52T4N4.js} +21 -2
  5. package/dist/{chunk-OTP3OSSW.js → chunk-XMMFN2WJ.js} +74 -3
  6. package/dist/client/index.cjs +132 -29
  7. package/dist/client/index.d.cts +53 -3
  8. package/dist/client/index.d.ts +53 -3
  9. package/dist/client/index.js +14 -2
  10. package/dist/index.cjs +319 -162
  11. package/dist/index.d.cts +30 -8
  12. package/dist/index.d.ts +30 -8
  13. package/dist/index.js +176 -133
  14. package/dist/quasar/index.cjs +76 -29
  15. package/dist/quasar/index.d.cts +2 -2
  16. package/dist/quasar/index.d.ts +2 -2
  17. package/dist/quasar/index.js +6 -2
  18. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  19. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  20. package/dist/{use-super-admin-i18n-c_Eqsu9u.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
  21. package/dist/{use-super-admin-i18n-DlJRGIVq.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
  22. package/package.json +18 -8
  23. package/src/client/i18n/messages/bundles.ts +0 -2
  24. package/src/client/i18n/messages/common.ts +22 -0
  25. package/src/client/i18n/messages/discovery.ts +2 -2
  26. package/src/client/i18n/messages/marketing.ts +2 -4
  27. package/src/client/i18n/messages/plan-detail.ts +0 -4
  28. package/src/client/i18n/messages/plans.ts +20 -8
  29. package/src/client/i18n/messages/tenants.ts +0 -2
  30. package/src/client/index.ts +1 -0
  31. package/src/client/manifest-loader.ts +1 -1
  32. package/src/client/resolve-plans.ts +144 -0
  33. package/src/components/BundleVersionPublishDialog.vue +6 -6
  34. package/src/components/KvBlock.vue +4 -4
  35. package/src/components/MarketingPromotionsTab.vue +34 -48
  36. package/src/components/MfaPromptDialog.vue +8 -2
  37. package/src/components/TenantActionConfirmDialog.vue +4 -4
  38. package/src/components/VersionDiffPreview.vue +7 -7
  39. package/src/components/admin-page/AdminFilters.vue +12 -0
  40. package/src/components/admin-page/AdminHero.vue +22 -5
  41. package/src/components/admin-page/AdminKpi.vue +84 -0
  42. package/src/components/admin-page/AdminPaginator.vue +195 -0
  43. package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
  44. package/src/components/admin-page/AdminSection.vue +31 -16
  45. package/src/components/admin-page/AdminStatistics.vue +40 -0
  46. package/src/components/admin-page/AdminTable.vue +175 -0
  47. package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
  48. package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
  49. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
  50. package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
  51. package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
  52. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
  53. package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
  54. package/src/components/dialogs/PilotCreateDialog.vue +12 -12
  55. package/src/components/dialogs/PilotEditDialog.vue +7 -7
  56. package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
  57. package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
  58. package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
  59. package/src/components/plan/PlanGrid.vue +1 -1
  60. package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
  61. package/src/components/plan-detail/PlanDetail.vue +92 -302
  62. package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
  63. package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
  64. package/src/components/plan-list/PlanList.vue +72 -290
  65. package/src/components/plan-matrix/PlanMatrix.vue +66 -183
  66. package/src/components/plan-review/PlanReview.vue +191 -275
  67. package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
  68. package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
  69. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
  70. package/src/index.ts +1 -0
  71. package/src/pages-standard/AdminLayout.vue +34 -10
  72. package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
  73. package/src/pages-standard/AuditPage.vue +53 -72
  74. package/src/pages-standard/BundlesPage.vue +128 -144
  75. package/src/pages-standard/DashboardPage.vue +43 -107
  76. package/src/pages-standard/DiscoveryPage.vue +158 -200
  77. package/src/pages-standard/EmailHistoryPage.vue +98 -88
  78. package/src/pages-standard/MarketingCatalogPage.vue +237 -267
  79. package/src/pages-standard/PilotsPage.vue +44 -60
  80. package/src/pages-standard/PlansPage.vue +230 -105
  81. package/src/pages-standard/PlatformEmailPage.vue +37 -40
  82. package/src/pages-standard/PromoCodeDetailPage.vue +16 -30
  83. package/src/pages-standard/PromoCodesPage.vue +86 -112
  84. package/src/pages-standard/SubscriptionsPage.vue +10 -17
  85. package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
  86. package/src/pages-standard/SuperAdminSetupWizard.vue +20 -17
  87. package/src/pages-standard/TenantDetailPage.vue +45 -41
  88. package/src/pages-standard/TenantsPage.vue +184 -264
  89. package/src/pages-standard/UsersPage.vue +69 -104
  90. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
  91. package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
  92. package/src/pages-standard/bundles-page/BundlesToolbar.vue +6 -10
  93. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
  94. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
  95. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
  96. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
  97. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
  98. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +1 -1
  99. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +2 -13
  100. package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
  101. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
  102. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
  103. package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
  104. package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
  105. package/src/pages-standard/plans-page/PlansPageToast.vue +1 -1
  106. package/src/pages-standard/sa-theme.css +481 -71
  107. package/src/pages-standard/tenant-detail/TenantUsers.vue +2 -8
  108. package/src/pages-standard/tenants/StatusPill.vue +2 -2
  109. package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
  110. package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
  111. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  112. package/src/pages-tenant/TenantPlanSection.vue +12 -20
  113. package/src/quasar/create-super-admin-app.ts +4 -0
  114. package/src/vue/entitlement-provider.ts +0 -1
  115. package/src/vue/feature-router-guard.ts +0 -1
  116. package/src/vue/super-admin-context.ts +106 -1
  117. package/src/vue/use-api-list.ts +2 -1
  118. package/src/vue/use-pagination.ts +63 -0
  119. package/src/vue/use-plans.ts +1 -2
  120. package/src/vue/use-sign-out.ts +90 -0
  121. package/src/vue/use-tenant-billing.ts +1 -1
  122. package/src/vue/use-tenant-manifest.ts +0 -1
  123. package/src/components/KpiCard.vue +0 -53
  124. package/src/components/plan-cockpit/PlanCockpit.vue +0 -1027
  125. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
  126. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -99
  127. package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
  128. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
  129. package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
  130. package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -249
  131. package/src/components/plan-cockpit/types.ts +0 -72
  132. 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
- <div class="sa-plan-list-search">
9
- <span class="sa-plan-list-search-ico" aria-hidden="true">
10
- <svg
11
- width="14"
12
- height="14"
13
- viewBox="0 0 24 24"
14
- fill="none"
15
- stroke="currentColor"
16
- stroke-width="2"
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-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost"
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-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost sa-plan-list-btn--danger"
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-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost"
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-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost"
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-plan-list-btn sa-plan-list-btn--sm"
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-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost sa-plan-list-btn--danger"
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-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost"
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-plan-list-btn sa-plan-list-btn--sm"
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
- const search = ref('');
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
- function todayIsoDate(): string {
509
- return new Date().toISOString().slice(0, 10);
510
- }
511
-
512
- function isCurrentlyValid(v: PlanVersionRow, today: string): boolean {
513
- if (v.publishedAt === null) return false;
514
- if (v.validFrom && v.validFrom.slice(0, 10) > today) return false;
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
- // (SPEC_V2 §4.2.1: only currently-valid + future ones stay visible
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
- --sa-plan-list-bg: #f6f7f9;
669
- --sa-plan-list-surface: #ffffff;
670
- --sa-plan-list-surface-2: #f8fafc;
671
- --sa-plan-list-border: #e5e7eb;
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-plan-list-surface);
755
- border: 1px solid var(--sa-plan-list-border);
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-plan-list-border);
765
- background: #fbfbfd;
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
- .sa-plan-list-toolbar-spacer {
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-plan-list-text-3);
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-plan-list-text-2);
604
+ color: var(--sa-muted-dark);
823
605
  font-weight: 700;
824
- border-bottom: 1px solid var(--sa-plan-list-border);
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-plan-list-text-3);
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 #f1f5f9;
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-plan-list-live-bg) !important;
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-plan-list-live-bg) !important;
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: #f1f5f9;
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-plan-list-text-2);
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: #64748b;
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-plan-list-text-2);
696
+ color: var(--sa-muted-dark);
915
697
  }
916
698
  .sa-plan-list-cell--sub-impact {
917
- color: var(--sa-plan-list-text-3);
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-plan-list-font-mono);
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-plan-list-text);
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: #64748b;
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-plan-list-surface-2);
975
- color: var(--sa-plan-list-text-2);
976
- border: 1px solid var(--sa-plan-list-border);
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-plan-list-live-bg);
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-plan-list-draft-bg);
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: #f1f5f9;
1000
- color: var(--sa-plan-list-text-2);
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-plan-list-text);
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-plan-list-text-3);
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-plan-list-text);
815
+ color: var(--sa-heading);
1034
816
  }
1035
817
  .sa-plan-list-price-unit {
1036
818
  font-size: 11px;
1037
- color: var(--sa-plan-list-text-3);
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-plan-list-text-3);
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-plan-list-text);
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-plan-list-text);
840
+ color: var(--sa-heading);
1059
841
  }
1060
842
  .sa-plan-list-tenant-bar {
1061
843
  flex: 1;
1062
844
  height: 6px;
1063
- background: #f1f5f9;
845
+ background: var(--sa-border-soft);
1064
846
  border-radius: 999px;
1065
847
  overflow: hidden;
1066
848
  }