@saasicat/ui-vue 0.19.0 → 0.21.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 (66) hide show
  1. package/dist/{catalog-JXdKdIqG.d.cts → catalog-BY_t9prv.d.cts} +1 -0
  2. package/dist/{catalog-JXdKdIqG.d.ts → catalog-BY_t9prv.d.ts} +1 -0
  3. package/dist/{chunk-RVG6LQHL.js → chunk-7IFKVGZV.js} +1 -1
  4. package/dist/{chunk-DPTIOQXX.js → chunk-JN6HZGLV.js} +2 -0
  5. package/dist/{chunk-ORX3ZG47.js → chunk-OTP3OSSW.js} +1 -1
  6. package/dist/client/index.cjs +2 -0
  7. package/dist/client/index.d.cts +2 -2
  8. package/dist/client/index.d.ts +2 -2
  9. package/dist/client/index.js +2 -2
  10. package/dist/index.cjs +2 -0
  11. package/dist/index.d.cts +4 -4
  12. package/dist/index.d.ts +4 -4
  13. package/dist/index.js +3 -3
  14. package/dist/quasar/index.cjs +2 -0
  15. package/dist/quasar/index.d.cts +2 -2
  16. package/dist/quasar/index.d.ts +2 -2
  17. package/dist/quasar/index.js +2 -2
  18. package/dist/{use-super-admin-i18n-1nCOkvgi.d.cts → use-super-admin-i18n-DlJRGIVq.d.cts} +1 -1
  19. package/dist/{use-super-admin-i18n-BlqcD0N7.d.ts → use-super-admin-i18n-c_Eqsu9u.d.ts} +1 -1
  20. package/package.json +2 -2
  21. package/src/client/i18n/messages/common.ts +2 -0
  22. package/src/components/admin-page/AdminBody.vue +40 -0
  23. package/src/components/admin-page/AdminHero.vue +32 -0
  24. package/src/components/admin-page/AdminPage.vue +16 -0
  25. package/src/components/admin-page/AdminSection.vue +38 -0
  26. package/src/components/plan-cockpit/PlanCockpit.vue +15 -0
  27. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +8 -8
  28. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +10 -11
  29. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +8 -8
  30. package/src/components/plan-cockpit/PlanCockpitVersions.vue +9 -11
  31. package/src/components/plan-detail/PlanAuditLog.vue +7 -8
  32. package/src/components/plan-detail/PlanDetail.vue +14 -1
  33. package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
  34. package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
  35. package/src/components/plan-list/PlanList.vue +224 -257
  36. package/src/pages-standard/AdminLayout.vue +4 -2
  37. package/src/pages-standard/AuditPage.vue +18 -43
  38. package/src/pages-standard/BundlesPage.vue +17 -34
  39. package/src/pages-standard/DashboardPage.vue +20 -51
  40. package/src/pages-standard/DiscoveryPage.vue +38 -51
  41. package/src/pages-standard/EmailHistoryPage.vue +20 -71
  42. package/src/pages-standard/MarketingCatalogPage.vue +304 -315
  43. package/src/pages-standard/PilotsPage.vue +24 -56
  44. package/src/pages-standard/PlansPage.vue +25 -22
  45. package/src/pages-standard/PlatformEmailPage.vue +19 -51
  46. package/src/pages-standard/PromoCodeDetailPage.vue +39 -50
  47. package/src/pages-standard/PromoCodesPage.vue +8 -15
  48. package/src/pages-standard/SubscriptionsPage.vue +9 -35
  49. package/src/pages-standard/SuperAdminSetupWizard.vue +2 -1
  50. package/src/pages-standard/TenantDetailPage.vue +79 -174
  51. package/src/pages-standard/TenantsPage.vue +8 -33
  52. package/src/pages-standard/UsersPage.vue +21 -54
  53. package/src/pages-standard/bundles-page/BundlesToolbar.vue +67 -0
  54. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +111 -73
  55. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +54 -62
  56. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
  57. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
  58. package/src/pages-standard/plans-page/PlansPageToast.vue +5 -5
  59. package/src/pages-standard/sa-theme.css +85 -29
  60. package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
  61. package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
  62. package/src/pages-standard/tenant-detail/TenantUsers.vue +21 -0
  63. package/src/pages-standard/tenant-detail/types.ts +38 -0
  64. package/src/vue/use-mfa-prompt.ts +72 -0
  65. package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
  66. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +0 -34
@@ -1,49 +1,12 @@
1
1
  <template>
2
- <div class="pl">
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>
2
+ <div class="sa-plan-list">
3
+ <p class="sa-plan-list-summary">{{ summary }}</p>
41
4
 
42
5
  <!-- List wrapper -->
43
- <div class="pl-wrap">
44
- <div class="pl-toolbar">
45
- <div class="pl-search">
46
- <span class="pl-search-ico" aria-hidden="true">
6
+ <div class="sa-plan-list-wrap">
7
+ <div class="sa-plan-list-toolbar">
8
+ <div class="sa-plan-list-search">
9
+ <span class="sa-plan-list-search-ico" aria-hidden="true">
47
10
  <svg
48
11
  width="14"
49
12
  height="14"
@@ -57,14 +20,14 @@
57
20
  </svg>
58
21
  </span>
59
22
  <input v-model="search" :placeholder="msg.list.searchPlaceholder" />
60
- <span class="pl-kbd">⌘ K</span>
23
+ <span class="sa-plan-list-kbd">⌘ K</span>
61
24
  </div>
62
- <div class="pl-toolbar-spacer" />
63
- <div class="pl-sortinfo">{{ msg.list.sortedBy }}</div>
25
+ <div class="sa-plan-list-toolbar-spacer" />
26
+ <div class="sa-plan-list-sortinfo">{{ msg.list.sortedBy }}</div>
64
27
  </div>
65
28
 
66
- <div class="pl-list">
67
- <div class="pl-list-head">
29
+ <div class="sa-plan-list-list">
30
+ <div class="sa-plan-list-list-head">
68
31
  <div>{{ msg.list.columnPlan }}</div>
69
32
  <div>{{ common.status }}</div>
70
33
  <div>{{ msg.list.columnVersion }}</div>
@@ -73,7 +36,7 @@
73
36
  <div />
74
37
  </div>
75
38
 
76
- <div v-if="filteredPlans.length === 0" class="pl-empty">
39
+ <div v-if="filteredPlans.length === 0" class="sa-plan-list-empty">
77
40
  <template v-if="resolvedPlans.length === 0">
78
41
  {{ msg.list.emptyNoPlans }}
79
42
  </template>
@@ -83,13 +46,16 @@
83
46
  <template v-for="p in filteredPlans" :key="p.plan.id">
84
47
  <!-- Parent row: currently live version (or first future / nothing) -->
85
48
  <div
86
- :class="['pl-row', { 'pl-row--new': highlightPlanKey === p.planKey }]"
49
+ :class="[
50
+ 'sa-plan-list-row',
51
+ { 'sa-plan-list-row--new': highlightPlanKey === p.planKey },
52
+ ]"
87
53
  @click="$emit('openPlan', p.plan)"
88
54
  >
89
- <div class="pl-cell pl-cell--name">
90
- <div class="pl-plan-name">
55
+ <div class="sa-plan-list-cell sa-plan-list-cell--name">
56
+ <div class="sa-plan-list-plan-name">
91
57
  <div
92
- class="pl-plan-mark"
58
+ class="sa-plan-list-plan-mark"
93
59
  :style="{
94
60
  background: planAccent(p.planKey) + '15',
95
61
  color: planAccent(p.planKey),
@@ -98,49 +64,60 @@
98
64
  >
99
65
  {{ p.planKey.slice(0, 3) }}
100
66
  </div>
101
- <div class="pl-plan-titles">
102
- <div class="pl-plan-title">
67
+ <div class="sa-plan-list-plan-titles">
68
+ <div class="sa-plan-list-plan-title">
103
69
  {{ p.label }}
104
70
  <span
105
71
  v-if="highlightPlanKey === p.planKey"
106
- class="pl-chip pl-chip--new"
72
+ class="sa-plan-list-chip sa-plan-list-chip--new"
107
73
  >{{ msg.list.badgeNew }}</span
108
74
  >
109
75
  </div>
110
- <div class="pl-plan-desc">{{ p.description || '—' }}</div>
76
+ <div class="sa-plan-list-plan-desc">
77
+ {{ p.description || '—' }}
78
+ </div>
111
79
  </div>
112
80
  </div>
113
81
  </div>
114
82
 
115
- <div class="pl-cell pl-cell--status">
83
+ <div class="sa-plan-list-cell sa-plan-list-cell--status">
116
84
  <template v-if="p.currentLive">
117
- <span class="pl-chip pl-chip--live pl-chip--dot">{{
118
- msg.list.chipLive
119
- }}</span>
85
+ <span
86
+ class="sa-plan-list-chip sa-plan-list-chip--live sa-plan-list-chip--dot"
87
+ >{{ msg.list.chipLive }}</span
88
+ >
120
89
  <span
121
90
  v-if="!p.currentLive.marketed"
122
- class="pl-chip pl-chip--supersed pl-chip--tiny"
91
+ class="sa-plan-list-chip sa-plan-list-chip--supersed sa-plan-list-chip--tiny"
123
92
  >{{ msg.list.chipPrivate }}</span
124
93
  >
125
94
  </template>
126
95
  <template v-else-if="p.primary && p.primary.publishedAt">
127
- <span class="pl-chip pl-chip--scheduled pl-chip--dot">
96
+ <span
97
+ class="sa-plan-list-chip sa-plan-list-chip--scheduled sa-plan-list-chip--dot"
98
+ >
128
99
  {{ validFromLabel(p.primary.validFrom) }}
129
100
  </span>
130
101
  </template>
131
102
  <template v-else>
132
- <span class="pl-chip pl-chip--supersed pl-chip--dot">{{
133
- msg.list.chipNoLive
134
- }}</span>
103
+ <span
104
+ class="sa-plan-list-chip sa-plan-list-chip--supersed sa-plan-list-chip--dot"
105
+ >{{ msg.list.chipNoLive }}</span
106
+ >
135
107
  </template>
136
108
  </div>
137
109
 
138
- <div class="pl-cell">
139
- <div v-if="p.primary" class="pl-version-num">
110
+ <div class="sa-plan-list-cell">
111
+ <div v-if="p.primary" class="sa-plan-list-version-num">
140
112
  v{{ p.primary.version }}
141
113
  </div>
142
- <div v-else class="pl-version-num pl-version-num--muted">—</div>
143
- <div v-if="p.primary?.validFrom" class="pl-version-sub">
114
+ <div
115
+ v-else
116
+ class="sa-plan-list-version-num sa-plan-list-version-num--muted"
117
+ >
118
+ —
119
+ </div>
120
+ <div v-if="p.primary?.validFrom" class="sa-plan-list-version-sub">
144
121
  {{
145
122
  validityRange(
146
123
  p.currentLive ? msg.list.since : msg.list.from,
@@ -151,9 +128,9 @@
151
128
  </div>
152
129
  </div>
153
130
 
154
- <div class="pl-cell">
131
+ <div class="sa-plan-list-cell">
155
132
  <template v-if="!p.primary">
156
- <span class="pl-price-text">—</span>
133
+ <span class="sa-plan-list-price-text">—</span>
157
134
  </template>
158
135
  <template
159
136
  v-else-if="
@@ -161,29 +138,31 @@
161
138
  Number(p.primary.yearlyNet) === 0
162
139
  "
163
140
  >
164
- <span class="pl-price-text">{{ msg.list.priceFree }}</span>
141
+ <span class="sa-plan-list-price-text">{{
142
+ msg.list.priceFree
143
+ }}</span>
165
144
  </template>
166
145
  <template v-else>
167
146
  <div>
168
- <span class="pl-price-big">{{
147
+ <span class="sa-plan-list-price-big">{{
169
148
  formatMoney(p.primary.monthlyNet)
170
149
  }}</span>
171
- <span class="pl-price-unit">{{
150
+ <span class="sa-plan-list-price-unit">{{
172
151
  ' ' + msg.list.perMonthShort
173
152
  }}</span>
174
153
  </div>
175
- <div class="pl-price-sub">
154
+ <div class="sa-plan-list-price-sub">
176
155
  {{ formatMoney(p.primary.yearlyNet) }}
177
156
  {{ msg.list.perYearShort }}
178
157
  </div>
179
158
  </template>
180
159
  </div>
181
160
 
182
- <div class="pl-cell pl-cell--tenants">
183
- <span class="pl-tenant-num">{{ p.tenantCount }}</span>
184
- <div class="pl-tenant-bar">
161
+ <div class="sa-plan-list-cell sa-plan-list-cell--tenants">
162
+ <span class="sa-plan-list-tenant-num">{{ p.tenantCount }}</span>
163
+ <div class="sa-plan-list-tenant-bar">
185
164
  <div
186
- class="pl-tenant-bar-fill"
165
+ class="sa-plan-list-tenant-bar-fill"
187
166
  :style="{
188
167
  width: tenantBarWidth(p.tenantCount),
189
168
  background: planAccent(p.planKey),
@@ -192,10 +171,10 @@
192
171
  </div>
193
172
  </div>
194
173
 
195
- <div class="pl-cell pl-cell--actions" @click.stop>
174
+ <div class="sa-plan-list-cell sa-plan-list-cell--actions" @click.stop>
196
175
  <button
197
176
  v-if="hasAnyPublished(p)"
198
- class="pl-btn pl-btn--sm pl-btn--ghost"
177
+ class="sa-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost"
199
178
  type="button"
200
179
  disabled
201
180
  :title="msg.list.actionDeleteBlocked"
@@ -215,7 +194,7 @@
215
194
  </button>
216
195
  <button
217
196
  v-else
218
- class="pl-btn pl-btn--sm pl-btn--ghost pl-btn--danger"
197
+ class="sa-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost sa-plan-list-btn--danger"
219
198
  type="button"
220
199
  :title="msg.list.actionDeletePlan"
221
200
  @click="$emit('archivePlan', p.plan, false)"
@@ -234,7 +213,7 @@
234
213
  </svg>
235
214
  </button>
236
215
  <button
237
- class="pl-btn pl-btn--sm pl-btn--ghost"
216
+ class="sa-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost"
238
217
  type="button"
239
218
  :title="msg.list.actionClonePlan"
240
219
  @click="$emit('clonePlan', p.plan)"
@@ -252,7 +231,7 @@
252
231
  </svg>
253
232
  </button>
254
233
  <button
255
- class="pl-btn pl-btn--sm pl-btn--ghost"
234
+ class="sa-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost"
256
235
  type="button"
257
236
  :title="msg.list.actionNewVersion"
258
237
  :disabled="!!p.draft"
@@ -272,7 +251,7 @@
272
251
  </svg>
273
252
  </button>
274
253
  <button
275
- class="pl-btn pl-btn--sm"
254
+ class="sa-plan-list-btn sa-plan-list-btn--sm"
276
255
  type="button"
277
256
  :title="msg.list.actionOpenPlan"
278
257
  @click="$emit('openPlan', p.plan)"
@@ -295,86 +274,92 @@
295
274
  <div
296
275
  v-for="sub in p.subRows"
297
276
  :key="`${p.plan.id}-${sub.id}`"
298
- class="pl-row pl-row--sub"
277
+ class="sa-plan-list-row sa-plan-list-row--sub"
299
278
  @click.stop="$emit('openPlan', p.plan)"
300
279
  >
301
- <div class="pl-cell pl-cell--name pl-cell--sub-name">
302
- <div class="pl-sub-tree" aria-hidden="true">
303
- <span class="pl-sub-tree-elbow" />
280
+ <div
281
+ class="sa-plan-list-cell sa-plan-list-cell--name sa-plan-list-cell--sub-name"
282
+ >
283
+ <div class="sa-plan-list-sub-tree" aria-hidden="true">
284
+ <span class="sa-plan-list-sub-tree-elbow" />
304
285
  </div>
305
- <div class="pl-sub-titles">
306
- <div class="pl-sub-title">
286
+ <div class="sa-plan-list-sub-titles">
287
+ <div class="sa-plan-list-sub-title">
307
288
  v{{ sub.version }}
308
289
  <template v-if="sub.publishedAt === null">
309
290
  <span
310
- class="pl-chip pl-chip--draft pl-chip--dot pl-chip--tiny"
291
+ class="sa-plan-list-chip sa-plan-list-chip--draft sa-plan-list-chip--dot sa-plan-list-chip--tiny"
311
292
  >{{ msg.list.chipDraft }}</span
312
293
  >
313
294
  </template>
314
295
  <template v-else>
315
296
  <span
316
- class="pl-chip pl-chip--scheduled pl-chip--dot pl-chip--tiny"
297
+ class="sa-plan-list-chip sa-plan-list-chip--scheduled sa-plan-list-chip--dot sa-plan-list-chip--tiny"
317
298
  >{{ msg.list.chipScheduled }}</span
318
299
  >
319
300
  </template>
320
301
  </div>
321
- <div class="pl-sub-desc">
302
+ <div class="sa-plan-list-sub-desc">
322
303
  {{ sub.changeNote || msg.list.noChangeNote }}
323
304
  </div>
324
305
  </div>
325
306
  </div>
326
307
 
327
- <div class="pl-cell pl-cell--status">
308
+ <div class="sa-plan-list-cell sa-plan-list-cell--status">
328
309
  <span
329
310
  v-if="sub.publishedAt === null"
330
- class="pl-chip pl-chip--draft pl-chip--dot pl-chip--tiny"
311
+ class="sa-plan-list-chip sa-plan-list-chip--draft sa-plan-list-chip--dot sa-plan-list-chip--tiny"
331
312
  >{{ msg.list.chipDraft }}</span
332
313
  >
333
314
  <span
334
315
  v-else
335
- class="pl-chip pl-chip--scheduled pl-chip--dot pl-chip--tiny"
316
+ class="sa-plan-list-chip sa-plan-list-chip--scheduled sa-plan-list-chip--dot sa-plan-list-chip--tiny"
336
317
  >{{ validFromLabel(sub.validFrom) }}</span
337
318
  >
338
319
  </div>
339
320
 
340
- <div class="pl-cell">
341
- <div class="pl-version-num pl-version-num--sub">v{{ sub.version }}</div>
342
- <div v-if="sub.validFrom" class="pl-version-sub">
321
+ <div class="sa-plan-list-cell">
322
+ <div class="sa-plan-list-version-num sa-plan-list-version-num--sub">
323
+ v{{ sub.version }}
324
+ </div>
325
+ <div v-if="sub.validFrom" class="sa-plan-list-version-sub">
343
326
  {{ validityRange(msg.list.from, sub.validFrom, sub.validUntil) }}
344
327
  </div>
345
328
  </div>
346
329
 
347
- <div class="pl-cell">
330
+ <div class="sa-plan-list-cell">
348
331
  <template
349
332
  v-if="Number(sub.monthlyNet) === 0 && Number(sub.yearlyNet) === 0"
350
333
  >
351
- <span class="pl-price-text">{{ msg.list.priceFree }}</span>
334
+ <span class="sa-plan-list-price-text">{{
335
+ msg.list.priceFree
336
+ }}</span>
352
337
  </template>
353
338
  <template v-else>
354
339
  <div>
355
- <span class="pl-price-big">{{
340
+ <span class="sa-plan-list-price-big">{{
356
341
  formatMoney(sub.monthlyNet)
357
342
  }}</span>
358
- <span class="pl-price-unit">{{
343
+ <span class="sa-plan-list-price-unit">{{
359
344
  ' ' + msg.list.perMonthShort
360
345
  }}</span>
361
346
  </div>
362
- <div class="pl-price-sub">
347
+ <div class="sa-plan-list-price-sub">
363
348
  {{ formatMoney(sub.yearlyNet) }} {{ msg.list.perYearShort }}
364
349
  </div>
365
350
  </template>
366
351
  </div>
367
352
 
368
- <div class="pl-cell pl-cell--sub-impact">
369
- <span class="pl-version-sub">{{
353
+ <div class="sa-plan-list-cell sa-plan-list-cell--sub-impact">
354
+ <span class="sa-plan-list-version-sub">{{
370
355
  tenantCountLabel(p.tenantCount)
371
356
  }}</span>
372
357
  </div>
373
358
 
374
- <div class="pl-cell pl-cell--actions" @click.stop>
359
+ <div class="sa-plan-list-cell sa-plan-list-cell--actions" @click.stop>
375
360
  <button
376
361
  v-if="sub.publishedAt === null"
377
- class="pl-btn pl-btn--sm pl-btn--ghost pl-btn--danger"
362
+ class="sa-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost sa-plan-list-btn--danger"
378
363
  type="button"
379
364
  :title="discardDraftTitle(sub.version)"
380
365
  @click="$emit('discardDraft', p.plan, sub)"
@@ -394,7 +379,7 @@
394
379
  </button>
395
380
  <button
396
381
  v-if="sub.publishedAt === null"
397
- class="pl-btn pl-btn--sm pl-btn--ghost"
382
+ class="sa-plan-list-btn sa-plan-list-btn--sm sa-plan-list-btn--ghost"
398
383
  type="button"
399
384
  :title="msg.list.actionEditDraft"
400
385
  @click="$emit('editDraft', p.plan, sub)"
@@ -413,7 +398,7 @@
413
398
  </svg>
414
399
  </button>
415
400
  <button
416
- class="pl-btn pl-btn--sm"
401
+ class="sa-plan-list-btn sa-plan-list-btn--sm"
417
402
  type="button"
418
403
  :title="msg.list.actionOpenInCockpit"
419
404
  @click="$emit('openPlan', p.plan)"
@@ -467,13 +452,11 @@ const props = withDefaults(
467
452
 
468
453
  const emit = defineEmits<{
469
454
  (e: 'openPlan', plan: PlanRow): void;
470
- (e: 'createPlan'): void;
471
455
  (e: 'clonePlan', plan: PlanRow): void;
472
456
  (e: 'newVersion', plan: PlanRow, basis: PlanVersionRow): void;
473
457
  (e: 'editDraft', plan: PlanRow, draft: PlanVersionRow): void;
474
458
  (e: 'discardDraft', plan: PlanRow, draft: PlanVersionRow): void;
475
459
  (e: 'archivePlan', plan: PlanRow, hasLive: boolean): void;
476
- (e: 'showMatrix'): void;
477
460
  }>();
478
461
 
479
462
  const msg = useSaMessages('plans');
@@ -681,230 +664,214 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
681
664
  </script>
682
665
 
683
666
  <style scoped>
684
- .pl {
685
- --pl-bg: #f6f7f9;
686
- --pl-surface: #ffffff;
687
- --pl-surface-2: #f8fafc;
688
- --pl-border: #e5e7eb;
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;
667
+ .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;
699
683
 
700
684
  padding: 22px 26px;
701
- background: var(--pl-bg);
702
- color: var(--pl-text);
703
- font-family: var(--pl-font-sans);
685
+ background: var(--sa-plan-list-bg);
686
+ color: var(--sa-plan-list-text);
687
+ font-family: var(--sa-plan-list-font-sans);
704
688
  min-height: 100%;
705
689
  box-sizing: border-box;
706
690
  }
707
- .pl * {
691
+ .sa-plan-list * {
708
692
  box-sizing: border-box;
709
693
  }
710
694
 
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 {
695
+ .sa-plan-list-summary {
724
696
  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;
697
+ color: var(--sa-plan-list-text-2);
698
+ margin: 0 0 18px;
732
699
  }
733
700
 
734
701
  /* Buttons */
735
- .pl-btn {
702
+ .sa-plan-list-btn {
736
703
  display: inline-flex;
737
704
  align-items: center;
738
705
  gap: 7px;
739
706
  padding: 8px 14px;
740
707
  border-radius: 7px;
741
- font: 500 13px var(--pl-font-sans);
708
+ font: 500 13px var(--sa-plan-list-font-sans);
742
709
  cursor: pointer;
743
- border: 1px solid var(--pl-border-strong);
710
+ border: 1px solid var(--sa-plan-list-border-strong);
744
711
  background: #fff;
745
- color: var(--pl-text);
712
+ color: var(--sa-plan-list-text);
746
713
  transition:
747
714
  background 0.12s,
748
715
  border-color 0.12s;
749
716
  }
750
- .pl-btn:hover:not(:disabled) {
751
- background: var(--pl-surface-2);
717
+ .sa-plan-list-btn:hover:not(:disabled) {
718
+ background: var(--sa-plan-list-surface-2);
752
719
  }
753
- .pl-btn:disabled {
720
+ .sa-plan-list-btn:disabled {
754
721
  cursor: not-allowed;
755
722
  opacity: 0.4;
756
723
  }
757
- .pl-btn--primary {
758
- background: var(--pl-primary);
759
- border-color: var(--pl-primary);
724
+ .sa-plan-list-btn--primary {
725
+ background: var(--sa-plan-list-primary);
726
+ border-color: var(--sa-plan-list-primary);
760
727
  color: #fff;
761
728
  }
762
- .pl-btn--primary:hover {
763
- background: var(--pl-primary-700);
729
+ .sa-plan-list-btn--primary:hover {
730
+ background: var(--sa-plan-list-primary-700);
764
731
  }
765
- .pl-btn--ghost {
732
+ .sa-plan-list-btn--ghost {
766
733
  border-color: transparent;
767
734
  background: transparent;
768
735
  }
769
- .pl-btn--ghost:hover:not(:disabled) {
736
+ .sa-plan-list-btn--ghost:hover:not(:disabled) {
770
737
  background: rgba(15, 23, 42, 0.05);
771
738
  }
772
- .pl-btn--danger {
739
+ .sa-plan-list-btn--danger {
773
740
  color: #ef4444;
774
741
  }
775
- .pl-btn--danger:hover:not(:disabled) {
742
+ .sa-plan-list-btn--danger:hover:not(:disabled) {
776
743
  background: #fef2f2;
777
744
  color: #b91c1c;
778
745
  }
779
- .pl-btn--sm {
746
+ .sa-plan-list-btn--sm {
780
747
  padding: 5px 8px;
781
748
  font-size: 12px;
782
749
  gap: 5px;
783
750
  }
784
751
 
785
752
  /* List wrap */
786
- .pl-wrap {
787
- background: var(--pl-surface);
788
- border: 1px solid var(--pl-border);
753
+ .sa-plan-list-wrap {
754
+ background: var(--sa-plan-list-surface);
755
+ border: 1px solid var(--sa-plan-list-border);
789
756
  border-radius: 10px;
790
757
  overflow: hidden;
791
758
  }
792
- .pl-toolbar {
759
+ .sa-plan-list-toolbar {
793
760
  display: flex;
794
761
  align-items: center;
795
762
  gap: 10px;
796
763
  padding: 12px 16px;
797
- border-bottom: 1px solid var(--pl-border);
764
+ border-bottom: 1px solid var(--sa-plan-list-border);
798
765
  background: #fbfbfd;
799
766
  }
800
- .pl-search {
767
+ .sa-plan-list-search {
801
768
  flex: 1;
802
769
  max-width: 360px;
803
770
  display: flex;
804
771
  align-items: center;
805
772
  gap: 8px;
806
773
  background: #fff;
807
- border: 1px solid var(--pl-border);
774
+ border: 1px solid var(--sa-plan-list-border);
808
775
  border-radius: 7px;
809
776
  padding: 7px 10px;
810
777
  }
811
- .pl-search-ico {
812
- color: var(--pl-text-3);
778
+ .sa-plan-list-search-ico {
779
+ color: var(--sa-plan-list-text-3);
813
780
  display: inline-flex;
814
781
  }
815
- .pl-search input {
782
+ .sa-plan-list-search input {
816
783
  flex: 1;
817
784
  border: 0;
818
785
  outline: 0;
819
- font: 13px var(--pl-font-sans);
786
+ font: 13px var(--sa-plan-list-font-sans);
820
787
  background: transparent;
821
- color: var(--pl-text);
788
+ color: var(--sa-plan-list-text);
822
789
  }
823
- .pl-kbd {
824
- font: 600 10.5px var(--pl-font-mono);
790
+ .sa-plan-list-kbd {
791
+ font: 600 10.5px var(--sa-plan-list-font-mono);
825
792
  background: #f1f5f9;
826
- color: var(--pl-text-2);
793
+ color: var(--sa-plan-list-text-2);
827
794
  padding: 2px 6px;
828
795
  border-radius: 4px;
829
796
  border: 1px solid #e2e8f0;
830
797
  }
831
- .pl-toolbar-spacer {
798
+ .sa-plan-list-toolbar-spacer {
832
799
  flex: 1;
833
800
  }
834
- .pl-sortinfo {
801
+ .sa-plan-list-sortinfo {
835
802
  margin-left: auto;
836
803
  font-size: 11.5px;
837
- color: var(--pl-text-3);
804
+ color: var(--sa-plan-list-text-3);
838
805
  }
839
806
 
840
807
  /* List grid */
841
- .pl-list {
808
+ .sa-plan-list-list {
842
809
  display: grid;
843
810
  grid-template-columns: 1.6fr 0.9fr 0.7fr 0.9fr 1.4fr 160px;
844
811
  align-items: center;
845
812
  }
846
- .pl-list-head {
813
+ .sa-plan-list-list-head {
847
814
  display: contents;
848
815
  }
849
- .pl-list-head > div {
816
+ .sa-plan-list-list-head > div {
850
817
  background: #fbfbfd;
851
818
  padding: 10px 16px;
852
819
  font-size: 10.5px;
853
820
  text-transform: uppercase;
854
821
  letter-spacing: 0.1em;
855
- color: var(--pl-text-2);
822
+ color: var(--sa-plan-list-text-2);
856
823
  font-weight: 700;
857
- border-bottom: 1px solid var(--pl-border);
824
+ border-bottom: 1px solid var(--sa-plan-list-border);
858
825
  }
859
- .pl-empty {
826
+ .sa-plan-list-empty {
860
827
  grid-column: 1 / -1;
861
828
  padding: 32px 24px;
862
829
  text-align: center;
863
- color: var(--pl-text-3);
830
+ color: var(--sa-plan-list-text-3);
864
831
  font-style: italic;
865
832
  font-size: 13px;
866
833
  }
867
- .pl-row {
834
+ .sa-plan-list-row {
868
835
  display: contents;
869
836
  cursor: pointer;
870
837
  }
871
- .pl-row > .pl-cell {
838
+ .sa-plan-list-row > .sa-plan-list-cell {
872
839
  padding: 14px 16px;
873
840
  border-bottom: 1px solid #f1f5f9;
874
841
  transition: background 0.12s;
875
842
  }
876
- .pl-row:hover > .pl-cell {
843
+ .sa-plan-list-row:hover > .sa-plan-list-cell {
877
844
  background: #fcfcfd;
878
845
  }
879
- .pl-row--new > .pl-cell {
880
- background: var(--pl-live-bg) !important;
881
- animation: pl-flashNew 2.4s ease-out;
846
+ .sa-plan-list-row--new > .sa-plan-list-cell {
847
+ background: var(--sa-plan-list-live-bg) !important;
848
+ animation: sa-plan-list-flashNew 2.4s ease-out;
882
849
  }
883
- @keyframes pl-flashNew {
850
+ @keyframes sa-plan-list-flashNew {
884
851
  0% {
885
852
  background: #d1fae5 !important;
886
853
  }
887
854
  100% {
888
- background: var(--pl-live-bg) !important;
855
+ background: var(--sa-plan-list-live-bg) !important;
889
856
  }
890
857
  }
891
858
 
892
859
  /* Sub-rows (drafts + future-scheduled versions, indented under the parent) */
893
- .pl-row--sub > .pl-cell {
860
+ .sa-plan-list-row--sub > .sa-plan-list-cell {
894
861
  background: #fafbfd;
895
862
  padding-top: 10px;
896
863
  padding-bottom: 10px;
897
864
  }
898
- .pl-row--sub:hover > .pl-cell {
865
+ .sa-plan-list-row--sub:hover > .sa-plan-list-cell {
899
866
  background: #f1f5f9;
900
867
  }
901
- .pl-cell--sub-name {
868
+ .sa-plan-list-cell--sub-name {
902
869
  display: flex;
903
870
  align-items: center;
904
871
  gap: 12px;
905
872
  padding-left: 32px !important;
906
873
  }
907
- .pl-sub-tree {
874
+ .sa-plan-list-sub-tree {
908
875
  display: inline-flex;
909
876
  align-items: center;
910
877
  width: 28px;
@@ -912,7 +879,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
912
879
  flex: 0 0 auto;
913
880
  position: relative;
914
881
  }
915
- .pl-sub-tree-elbow {
882
+ .sa-plan-list-sub-tree-elbow {
916
883
  position: absolute;
917
884
  top: 0;
918
885
  bottom: 50%;
@@ -922,18 +889,18 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
922
889
  border-bottom: 1.5px solid #cbd5e1;
923
890
  border-bottom-left-radius: 6px;
924
891
  }
925
- .pl-sub-titles {
892
+ .sa-plan-list-sub-titles {
926
893
  min-width: 0;
927
894
  }
928
- .pl-sub-title {
895
+ .sa-plan-list-sub-title {
929
896
  font-size: 13px;
930
897
  font-weight: 600;
931
- color: var(--pl-text-2);
898
+ color: var(--sa-plan-list-text-2);
932
899
  display: flex;
933
900
  align-items: center;
934
901
  gap: 8px;
935
902
  }
936
- .pl-sub-desc {
903
+ .sa-plan-list-sub-desc {
937
904
  font-size: 11.5px;
938
905
  color: #64748b;
939
906
  margin-top: 2px;
@@ -941,45 +908,45 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
941
908
  text-overflow: ellipsis;
942
909
  white-space: nowrap;
943
910
  }
944
- .pl-version-num--sub {
911
+ .sa-plan-list-version-num--sub {
945
912
  font-size: 13px;
946
913
  font-weight: 600;
947
- color: var(--pl-text-2);
914
+ color: var(--sa-plan-list-text-2);
948
915
  }
949
- .pl-cell--sub-impact {
950
- color: var(--pl-text-3);
916
+ .sa-plan-list-cell--sub-impact {
917
+ color: var(--sa-plan-list-text-3);
951
918
  }
952
919
 
953
- .pl-plan-name {
920
+ .sa-plan-list-plan-name {
954
921
  display: flex;
955
922
  align-items: center;
956
923
  gap: 12px;
957
924
  }
958
- .pl-plan-mark {
925
+ .sa-plan-list-plan-mark {
959
926
  width: 36px;
960
927
  height: 36px;
961
928
  border-radius: 8px;
962
929
  display: grid;
963
930
  place-items: center;
964
- font: 700 11px var(--pl-font-mono);
931
+ font: 700 11px var(--sa-plan-list-font-mono);
965
932
  letter-spacing: 0.04em;
966
933
  flex: 0 0 auto;
967
934
  border: 1px solid;
968
935
  }
969
- .pl-plan-titles {
936
+ .sa-plan-list-plan-titles {
970
937
  min-width: 0;
971
938
  }
972
- .pl-plan-title {
939
+ .sa-plan-list-plan-title {
973
940
  font-size: 14.5px;
974
941
  font-weight: 700;
975
- color: var(--pl-text);
942
+ color: var(--sa-plan-list-text);
976
943
  letter-spacing: -0.01em;
977
944
  display: flex;
978
945
  align-items: center;
979
946
  gap: 8px;
980
947
  flex-wrap: wrap;
981
948
  }
982
- .pl-plan-desc {
949
+ .sa-plan-list-plan-desc {
983
950
  font-size: 11.5px;
984
951
  color: #64748b;
985
952
  margin-top: 2px;
@@ -988,7 +955,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
988
955
  white-space: nowrap;
989
956
  }
990
957
 
991
- .pl-cell--status {
958
+ .sa-plan-list-cell--status {
992
959
  display: flex;
993
960
  flex-direction: column;
994
961
  gap: 3px;
@@ -996,7 +963,7 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
996
963
  }
997
964
 
998
965
  /* Chips */
999
- .pl-chip {
966
+ .sa-plan-list-chip {
1000
967
  display: inline-flex;
1001
968
  align-items: center;
1002
969
  gap: 5px;
@@ -1004,41 +971,41 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
1004
971
  border-radius: 999px;
1005
972
  font-size: 11px;
1006
973
  font-weight: 600;
1007
- background: var(--pl-surface-2);
1008
- color: var(--pl-text-2);
1009
- border: 1px solid var(--pl-border);
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);
1010
977
  }
1011
- .pl-chip--tiny {
978
+ .sa-plan-list-chip--tiny {
1012
979
  padding: 1px 6px;
1013
980
  font-size: 10px;
1014
981
  }
1015
- .pl-chip--new {
982
+ .sa-plan-list-chip--new {
1016
983
  background: #dbeafe;
1017
984
  color: #1e40af;
1018
985
  border-color: #bfdbfe;
1019
986
  font-size: 10px;
1020
987
  }
1021
- .pl-chip--live {
1022
- background: var(--pl-live-bg);
988
+ .sa-plan-list-chip--live {
989
+ background: var(--sa-plan-list-live-bg);
1023
990
  color: #047857;
1024
991
  border-color: #a7f3d0;
1025
992
  }
1026
- .pl-chip--draft {
1027
- background: var(--pl-draft-bg);
993
+ .sa-plan-list-chip--draft {
994
+ background: var(--sa-plan-list-draft-bg);
1028
995
  color: #b45309;
1029
996
  border-color: #fde68a;
1030
997
  }
1031
- .pl-chip--supersed {
998
+ .sa-plan-list-chip--supersed {
1032
999
  background: #f1f5f9;
1033
- color: var(--pl-text-2);
1000
+ color: var(--sa-plan-list-text-2);
1034
1001
  border-color: #cbd5e1;
1035
1002
  }
1036
- .pl-chip--scheduled {
1003
+ .sa-plan-list-chip--scheduled {
1037
1004
  background: #eef2ff;
1038
1005
  color: #4338ca;
1039
1006
  border-color: #c7d2fe;
1040
1007
  }
1041
- .pl-chip--dot::before {
1008
+ .sa-plan-list-chip--dot::before {
1042
1009
  content: '';
1043
1010
  width: 6px;
1044
1011
  height: 6px;
@@ -1046,62 +1013,62 @@ function hasAnyPublished(row: ResolvedPlan): boolean {
1046
1013
  background: currentColor;
1047
1014
  }
1048
1015
 
1049
- .pl-version-num {
1016
+ .sa-plan-list-version-num {
1050
1017
  font-size: 13px;
1051
1018
  font-weight: 700;
1052
- color: var(--pl-text);
1019
+ color: var(--sa-plan-list-text);
1053
1020
  }
1054
- .pl-version-num--muted {
1021
+ .sa-plan-list-version-num--muted {
1055
1022
  color: #cbd5e1;
1056
1023
  }
1057
- .pl-version-sub {
1024
+ .sa-plan-list-version-sub {
1058
1025
  font-size: 10.5px;
1059
- color: var(--pl-text-3);
1026
+ color: var(--sa-plan-list-text-3);
1060
1027
  margin-top: 2px;
1061
1028
  }
1062
1029
 
1063
- .pl-price-big {
1030
+ .sa-plan-list-price-big {
1064
1031
  font-size: 14px;
1065
1032
  font-weight: 700;
1066
- color: var(--pl-text);
1033
+ color: var(--sa-plan-list-text);
1067
1034
  }
1068
- .pl-price-unit {
1035
+ .sa-plan-list-price-unit {
1069
1036
  font-size: 11px;
1070
- color: var(--pl-text-3);
1037
+ color: var(--sa-plan-list-text-3);
1071
1038
  }
1072
- .pl-price-sub {
1039
+ .sa-plan-list-price-sub {
1073
1040
  font-size: 10.5px;
1074
- color: var(--pl-text-3);
1041
+ color: var(--sa-plan-list-text-3);
1075
1042
  margin-top: 2px;
1076
1043
  }
1077
- .pl-price-text {
1044
+ .sa-plan-list-price-text {
1078
1045
  font-size: 13px;
1079
1046
  font-weight: 600;
1080
- color: var(--pl-text);
1047
+ color: var(--sa-plan-list-text);
1081
1048
  }
1082
1049
 
1083
- .pl-cell--tenants {
1050
+ .sa-plan-list-cell--tenants {
1084
1051
  display: flex;
1085
1052
  align-items: center;
1086
1053
  gap: 10px;
1087
1054
  }
1088
- .pl-tenant-num {
1055
+ .sa-plan-list-tenant-num {
1089
1056
  font-size: 16px;
1090
1057
  font-weight: 700;
1091
- color: var(--pl-text);
1058
+ color: var(--sa-plan-list-text);
1092
1059
  }
1093
- .pl-tenant-bar {
1060
+ .sa-plan-list-tenant-bar {
1094
1061
  flex: 1;
1095
1062
  height: 6px;
1096
1063
  background: #f1f5f9;
1097
1064
  border-radius: 999px;
1098
1065
  overflow: hidden;
1099
1066
  }
1100
- .pl-tenant-bar-fill {
1067
+ .sa-plan-list-tenant-bar-fill {
1101
1068
  height: 100%;
1102
1069
  }
1103
1070
 
1104
- .pl-cell--actions {
1071
+ .sa-plan-list-cell--actions {
1105
1072
  display: flex;
1106
1073
  gap: 4px;
1107
1074
  justify-content: flex-end;