@saasicat/ui-vue 0.7.0 → 0.8.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 (40) hide show
  1. package/dist/{chunk-A3W2N26K.js → chunk-7N3CJTOA.js} +7 -227
  2. package/dist/{chunk-X5SQ5HMB.js → chunk-TBLXKSTR.js} +0 -137
  3. package/dist/{chunk-QZOSDCNP.js → chunk-TY2WYKME.js} +1 -1
  4. package/dist/client/index.cjs +112 -467
  5. package/dist/client/index.d.cts +4 -62
  6. package/dist/client/index.d.ts +4 -62
  7. package/dist/client/index.js +2 -6
  8. package/dist/index.cjs +204 -583
  9. package/dist/index.d.cts +10 -55
  10. package/dist/index.d.ts +10 -55
  11. package/dist/index.js +74 -101
  12. package/dist/{messages-7b0P8W75.d.ts → messages-BIYw32u1.d.cts} +0 -67
  13. package/dist/{messages-7b0P8W75.d.cts → messages-BIYw32u1.d.ts} +0 -67
  14. package/dist/quasar/index.cjs +0 -136
  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-B3v3-SWZ.d.ts → use-super-admin-i18n-CWNu_W3u.d.ts} +1 -1
  19. package/dist/{use-super-admin-i18n-DMktRVEt.d.cts → use-super-admin-i18n-DL_qI1BY.d.cts} +1 -1
  20. package/package.json +2 -2
  21. package/src/client/i18n/messages/nav.ts +0 -2
  22. package/src/client/i18n/messages/plan-versions.ts +0 -143
  23. package/src/client/index.ts +0 -2
  24. package/src/client/nav-builder.ts +9 -4
  25. package/src/index.ts +0 -1
  26. package/src/pages-standard/AdminLayout.vue +1 -1
  27. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +1 -1
  28. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +1 -1
  29. package/src/pages-standard/plan-versions/catalog-history.types.ts +37 -0
  30. package/src/pages-standard/plan-versions/format.ts +2 -38
  31. package/src/client/plan-versions-catalog.ts +0 -380
  32. package/src/pages-standard/PlanVersionsPage.vue +0 -220
  33. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +0 -309
  34. package/src/pages-standard/plan-versions/PlanVersionStatusBadge.vue +0 -48
  35. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +0 -326
  36. package/src/pages-standard/plan-versions/PlanVersionsKpi.vue +0 -65
  37. package/src/pages-standard/plan-versions/PlanVersionsList.vue +0 -315
  38. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +0 -261
  39. package/src/pages-standard/plan-versions/types.ts +0 -33
  40. package/src/vue/use-plan-versions.ts +0 -83
@@ -1,65 +0,0 @@
1
- <template>
2
- <div class="sa-pv-kpi">
3
- <div class="sa-pv-kpi__icon"><q-icon :name="icon" size="18px" /></div>
4
- <div>
5
- <div class="sa-pv-kpi__label">{{ label }}</div>
6
- <div class="sa-pv-kpi__value">
7
- {{ value }}
8
- <span v-if="sub" class="sa-pv-kpi__sub">{{ sub }}</span>
9
- </div>
10
- </div>
11
- </div>
12
- </template>
13
-
14
- <script setup lang="ts">
15
- defineProps<{
16
- label: string;
17
- value: number | string;
18
- icon: string;
19
- sub?: string;
20
- }>();
21
- </script>
22
-
23
- <style scoped>
24
- .sa-pv-kpi {
25
- background: #fff;
26
- border: 1px solid var(--sa-border, #e2e8f0);
27
- border-radius: 10px;
28
- padding: 12px 14px;
29
- display: flex;
30
- align-items: center;
31
- gap: 12px;
32
- }
33
- .sa-pv-kpi__icon {
34
- width: 36px;
35
- height: 36px;
36
- border-radius: 9px;
37
- background: var(--sa-primary-soft, rgba(63, 107, 255, 0.08));
38
- display: flex;
39
- align-items: center;
40
- justify-content: center;
41
- }
42
- .sa-pv-kpi__icon :deep(.q-icon) {
43
- color: var(--sa-primary, #3f6bff);
44
- }
45
- .sa-pv-kpi__label {
46
- font-size: 10.5px;
47
- font-weight: 700;
48
- letter-spacing: 0.08em;
49
- color: var(--sa-muted, #64748b);
50
- text-transform: uppercase;
51
- }
52
- .sa-pv-kpi__value {
53
- font-family: var(--sa-font-head, system-ui, sans-serif);
54
- font-weight: 700;
55
- font-size: 20px;
56
- color: var(--sa-heading, #0f172a);
57
- line-height: 1.1;
58
- margin-top: 2px;
59
- }
60
- .sa-pv-kpi__sub {
61
- font-size: 12px;
62
- font-weight: 500;
63
- color: var(--sa-muted, #64748b);
64
- }
65
- </style>
@@ -1,315 +0,0 @@
1
- <template>
2
- <div class="sa-pv-list">
3
- <div class="sa-pv-list__kpis">
4
- <PlanVersionsKpi
5
- :label="msg.list.kpiPlans"
6
- :value="snapshot.plans.length"
7
- icon="inventory_2"
8
- />
9
- <PlanVersionsKpi
10
- :label="msg.list.kpiSelfService"
11
- :value="marketedCount"
12
- icon="storefront"
13
- :sub="totalPlansLabel"
14
- />
15
- <PlanVersionsKpi
16
- :label="msg.list.kpiFeaturesTotal"
17
- :value="totalFeatures"
18
- icon="extension"
19
- />
20
- </div>
21
-
22
- <div v-if="missingPlans.length > 0" class="sa-pv-list__missing">
23
- <q-icon name="info" size="16px" />
24
- <div>
25
- <strong>{{ msg.list.missingPlansLabel }}</strong> {{ missingPlans.join(', ') }}
26
- {{ msg.list.missingPlansHint }}
27
- <template v-if="canEdit">
28
- <button
29
- v-for="planId in missingPlans"
30
- :key="`new-${planId}`"
31
- type="button"
32
- class="sa-pv-list__missing-btn"
33
- @click="emit('createPlanDraft', planId)"
34
- >
35
- {{ createDraftLabel(planId) }}
36
- </button>
37
- </template>
38
- </div>
39
- </div>
40
-
41
- <div class="sa-pv-list__table">
42
- <slot name="table-head">
43
- <div
44
- class="sa-pv-list__head sa-pv-list__row sa-pv-list__row--default"
45
- :style="rowGridStyle"
46
- >
47
- <div>{{ msg.list.colPlan }}</div>
48
- <div class="sa-pv-list__th--num">{{ msg.list.colPriceNet }}</div>
49
- <div
50
- v-for="col in quotaColumns"
51
- :key="`hd-${col.key}`"
52
- class="sa-pv-list__th--num"
53
- >
54
- {{ col.label }}
55
- </div>
56
- <div class="sa-pv-list__th--num">{{ msg.list.colFeatures }}</div>
57
- </div>
58
- </slot>
59
-
60
- <template v-for="(plan, i) in snapshot.plans" :key="plan.planId">
61
- <slot
62
- name="row"
63
- :plan="plan"
64
- :is-open="openId === plan.planId"
65
- :is-last="i === snapshot.plans.length - 1"
66
- :can-edit="canEdit"
67
- :toggle="() => onToggle(plan.planId)"
68
- >
69
- <div
70
- class="sa-pv-list__default-row sa-pv-list__row sa-pv-list__row--default"
71
- :style="rowGridStyle"
72
- >
73
- <div class="sa-pv-list__default-name">
74
- <strong>{{ plan.planId }}</strong>
75
- <span class="sa-pv-list__default-version">v{{ plan.version }}</span>
76
- <span v-if="plan.isDraft" class="sa-pv-list__default-chip">{{
77
- msg.status.draft
78
- }}</span>
79
- </div>
80
- <div class="sa-pv-list__th--num">
81
- {{ formatEuro(plan.monthlyNet) }}
82
- </div>
83
- <div
84
- v-for="col in quotaColumns"
85
- :key="`val-${plan.planId}-${col.key}`"
86
- class="sa-pv-list__th--num"
87
- >
88
- {{ formatQuotaValue(plan.quotas[col.key], col) }}
89
- </div>
90
- <div class="sa-pv-list__th--num">{{ plan.features.length }}</div>
91
- </div>
92
- </slot>
93
- </template>
94
- </div>
95
-
96
- <div v-if="snapshot.kind === 'drafts'" class="sa-pv-list__draft-hint">
97
- <q-icon name="info" size="18px" />
98
- <div>
99
- <strong>{{ msg.list.draftHintLabel }}</strong> {{ msg.list.draftHintBefore }}
100
- <em>{{ msg.list.draftHintEditButton }}</em> {{ msg.list.draftHintMiddle }}
101
- <strong>{{ msg.list.draftHintPublishFlow }}</strong>
102
- {{ msg.list.draftHintAfter }}
103
- </div>
104
- </div>
105
- </div>
106
- </template>
107
-
108
- <script setup lang="ts">
109
- import { computed, ref } from 'vue';
110
- import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
111
- import { formatMessage } from '../../client/i18n/format.js';
112
- import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
113
- import PlanVersionsKpi from './PlanVersionsKpi.vue';
114
- import { fmtEuro } from './format.js';
115
-
116
- /**
117
- * Configuration of a quota column (e.g. `users` → "Benutzer"). Optional
118
- * `fractional: true` → 1 decimal place (storage GB). Optional `unit` →
119
- * suffix like "GB" after the value. `-1` is rendered as ∞.
120
- */
121
- export interface QuotaColumnConfig {
122
- key: string;
123
- label: string;
124
- fractional?: boolean;
125
- unit?: string;
126
- }
127
-
128
- const props = defineProps<{
129
- snapshot: CatalogSnapshot;
130
- /**
131
- * Optional: all expected plan IDs. When set, the list shows a
132
- * warning with "create draft" buttons for missing plans.
133
- */
134
- expectedPlanIds?: readonly string[];
135
- /**
136
- * Optional: additional quota columns between "Preis" and "Features".
137
- * Example: `[{ key: 'users', label: 'Benutzer' },
138
- * { key: 'members', label: 'Mitglieder' }, { key: 'storageGb',
139
- * label: 'Speicher (GB)', fractional: true }, { key: 'resources',
140
- * label: 'Plätze' }]`. Default: no additional columns
141
- * (backwards-compatible).
142
- */
143
- quotaColumns?: ReadonlyArray<QuotaColumnConfig>;
144
- }>();
145
-
146
- const emit = defineEmits<{
147
- (e: 'editPlanDraft', draftId: string): void;
148
- (e: 'createPlanDraft', planId: string): void;
149
- }>();
150
-
151
- const msg = useSaMessages('planVersions');
152
- const { locale } = useSuperAdminI18n();
153
-
154
- const openId = ref<string | null>(null);
155
- const canEdit = computed(() => props.snapshot.kind === 'drafts');
156
-
157
- const totalPlansLabel = computed(() =>
158
- formatMessage(msg.value.list.kpiOfTotal, { count: props.snapshot.plans.length }),
159
- );
160
-
161
- const marketedCount = computed(() => props.snapshot.plans.filter((p) => p.marketed).length);
162
- const totalFeatures = computed(() => new Set(props.snapshot.plans.flatMap((p) => p.features)).size);
163
-
164
- const missingPlans = computed<string[]>(() => {
165
- if (!props.expectedPlanIds) return [];
166
- const present = new Set(props.snapshot.plans.map((p) => p.planId));
167
- return props.expectedPlanIds.filter((id) => !present.has(id));
168
- });
169
-
170
- const quotaColumns = computed<ReadonlyArray<QuotaColumnConfig>>(() => props.quotaColumns ?? []);
171
-
172
- const rowGridStyle = computed(() => {
173
- if (quotaColumns.value.length === 0) {
174
- return { gridTemplateColumns: '1.6fr 1fr 0.6fr' };
175
- }
176
- // Name (1.6fr), price (1fr), N×quota (0.7fr), features (0.5fr)
177
- const quotaCols = Array(quotaColumns.value.length).fill('0.7fr').join(' ');
178
- return { gridTemplateColumns: `1.6fr 1fr ${quotaCols} 0.5fr` };
179
- });
180
-
181
- function onToggle(id: string): void {
182
- openId.value = openId.value === id ? null : id;
183
- }
184
-
185
- function createDraftLabel(planId: string): string {
186
- return formatMessage(msg.value.list.createDraftFor, { planId });
187
- }
188
-
189
- function formatEuro(n: number): string {
190
- return fmtEuro(n, locale.value);
191
- }
192
-
193
- function formatQuotaValue(value: number | undefined, col: QuotaColumnConfig): string {
194
- if (value === undefined) return '–';
195
- if (value === -1) return '∞';
196
- const formatted = col.fractional ? value.toFixed(1) : Math.round(value).toString();
197
- return col.unit ? `${formatted} ${col.unit}` : formatted;
198
- }
199
- </script>
200
-
201
- <style scoped>
202
- .sa-pv-list {
203
- padding: 20px 28px;
204
- }
205
- .sa-pv-list__kpis {
206
- display: grid;
207
- grid-template-columns: repeat(4, 1fr);
208
- gap: 12px;
209
- margin-bottom: 18px;
210
- }
211
- .sa-pv-list__missing {
212
- display: flex;
213
- align-items: flex-start;
214
- gap: 10px;
215
- background: var(--sa-warning-soft, rgba(245, 158, 11, 0.1));
216
- border: 1px solid var(--sa-amber-border, rgba(245, 158, 11, 0.3));
217
- border-radius: 10px;
218
- padding: 12px 16px;
219
- margin-bottom: 14px;
220
- font-size: 12.5px;
221
- color: #7c2d12;
222
- }
223
- .sa-pv-list__missing :deep(.q-icon) {
224
- color: var(--sa-warning, #b45309);
225
- }
226
- .sa-pv-list__missing-btn {
227
- background: #fff;
228
- border: 1px solid var(--sa-amber-border, rgba(245, 158, 11, 0.3));
229
- color: var(--sa-warning, #b45309);
230
- border-radius: 6px;
231
- padding: 3px 8px;
232
- font-size: 11.5px;
233
- font-weight: 600;
234
- cursor: pointer;
235
- margin-left: 6px;
236
- }
237
-
238
- .sa-pv-list__table {
239
- background: #fff;
240
- border: 1px solid var(--sa-border, #e2e8f0);
241
- border-radius: 12px;
242
- overflow: hidden;
243
- box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
244
- }
245
- .sa-pv-list__row {
246
- display: grid;
247
- align-items: center;
248
- gap: 12px;
249
- }
250
- .sa-pv-list__row--default {
251
- grid-template-columns: 1.6fr 1fr 0.6fr;
252
- padding: 10px 14px;
253
- }
254
- .sa-pv-list__head {
255
- background: #fafbfc;
256
- border-bottom: 1px solid var(--sa-border, #e2e8f0);
257
- font-size: 10.5px;
258
- font-weight: 700;
259
- letter-spacing: 0.08em;
260
- color: var(--sa-muted, #64748b);
261
- text-transform: uppercase;
262
- }
263
- .sa-pv-list__th--num {
264
- text-align: right;
265
- font-family: var(--sa-font-mono, ui-monospace, monospace);
266
- font-size: 13px;
267
- }
268
-
269
- .sa-pv-list__default-row {
270
- border-bottom: 1px solid var(--sa-border-soft, #f1f5f9);
271
- }
272
- .sa-pv-list__default-row:last-child {
273
- border-bottom: none;
274
- }
275
- .sa-pv-list__default-name {
276
- display: flex;
277
- align-items: center;
278
- gap: 8px;
279
- }
280
- .sa-pv-list__default-version {
281
- font-family: var(--sa-font-mono, ui-monospace, monospace);
282
- font-size: 11px;
283
- font-weight: 600;
284
- color: var(--sa-muted, #64748b);
285
- background: #f1f5f9;
286
- padding: 2px 6px;
287
- border-radius: 4px;
288
- }
289
- .sa-pv-list__default-chip {
290
- font-size: 9.5px;
291
- font-weight: 700;
292
- letter-spacing: 0.06em;
293
- padding: 2px 6px;
294
- border-radius: 4px;
295
- background: var(--sa-warning-soft, rgba(245, 158, 11, 0.1));
296
- color: var(--sa-warning, #b45309);
297
- }
298
-
299
- .sa-pv-list__draft-hint {
300
- margin-top: 14px;
301
- background: var(--sa-warning-soft, rgba(245, 158, 11, 0.1));
302
- border: 1px solid var(--sa-amber-border, rgba(245, 158, 11, 0.3));
303
- border-radius: 10px;
304
- padding: 12px 16px;
305
- display: flex;
306
- align-items: flex-start;
307
- gap: 12px;
308
- font-size: 12.5px;
309
- color: #7c2d12;
310
- line-height: 1.5;
311
- }
312
- .sa-pv-list__draft-hint :deep(.q-icon) {
313
- color: var(--sa-warning, #b45309);
314
- }
315
- </style>
@@ -1,261 +0,0 @@
1
- <template>
2
- <div class="sa-pv-matrix">
3
- <div class="sa-pv-matrix__card">
4
- <div class="sa-pv-matrix__head" :style="gridStyle">
5
- <div class="sa-pv-matrix__th">Feature</div>
6
- <div v-for="plan in snapshot.plans" :key="plan.planId" class="sa-pv-matrix__plan">
7
- <div class="sa-pv-matrix__plan-name">{{ plan.planId }}</div>
8
- <div class="sa-pv-matrix__plan-price">
9
- {{ fmtEuro(plan.monthlyNet) }}/Mo · v{{ plan.version }}
10
- </div>
11
- </div>
12
- </div>
13
-
14
- <template v-for="grp in groupsWithItems" :key="grp.name">
15
- <div v-if="grp.name" class="sa-pv-matrix__group">{{ grp.name }}</div>
16
- <div
17
- v-for="f in grp.items"
18
- :key="f.key"
19
- class="sa-pv-matrix__row"
20
- :style="gridStyle"
21
- >
22
- <div class="sa-pv-matrix__feat-cell">
23
- <div class="sa-pv-matrix__feat-icon">
24
- <q-icon :name="f.icon" size="14px" />
25
- </div>
26
- <div>
27
- <div class="sa-pv-matrix__feat-label">
28
- {{ f.label }}
29
- <span v-if="f.plannedOnly" class="sa-pv-matrix__planned"
30
- >PLANNED</span
31
- >
32
- </div>
33
- <div class="sa-pv-matrix__feat-key">{{ f.key }}</div>
34
- </div>
35
- </div>
36
- <div
37
- v-for="plan in snapshot.plans"
38
- :key="plan.planId"
39
- class="sa-pv-matrix__cell"
40
- :class="{ 'sa-pv-matrix__cell--off': !plan.features.includes(f.key) }"
41
- >
42
- <span v-if="plan.features.includes(f.key)" class="sa-pv-matrix__check">
43
- <q-icon name="check" size="14px" />
44
- </span>
45
- <span v-else class="sa-pv-matrix__dash" aria-hidden="true" />
46
- </div>
47
- </div>
48
- </template>
49
- </div>
50
- </div>
51
- </template>
52
-
53
- <script setup lang="ts">
54
- import { computed } from 'vue';
55
- import type { CatalogSnapshot } from '../../client/plan-versions-catalog.js';
56
- import { fmtEuro } from './format.js';
57
-
58
- // Generic feature matrix. Apps pass `featureRegistry` for label/icon/
59
- // plannedOnly hint and optionally `featureGroups` for the UX grouping of
60
- // the rows (e.g. Core, Billing, Data, …).
61
- //
62
- // Without `featureGroups` the matrix renders a single group without a header.
63
-
64
- interface FeatureMeta {
65
- label?: string;
66
- icon?: string;
67
- plannedOnly?: boolean;
68
- }
69
-
70
- interface FeatureGroupsConfig {
71
- /** Order of the group headers. */
72
- order: readonly string[];
73
- /** Map featureKey → group name. Unassigned features land in "Sonstige". */
74
- byKey: Record<string, string>;
75
- /** Default bucket for features without a `byKey` entry. Default: 'Sonstige'. */
76
- fallbackGroup?: string;
77
- }
78
-
79
- const props = defineProps<{
80
- snapshot: CatalogSnapshot;
81
- featureRegistry: Record<string, FeatureMeta>;
82
- featureGroups?: FeatureGroupsConfig;
83
- }>();
84
-
85
- const gridStyle = computed(() => ({
86
- gridTemplateColumns: `minmax(280px, 1.5fr) repeat(${props.snapshot.plans.length}, minmax(110px, 1fr))`,
87
- }));
88
-
89
- interface MatrixFeature {
90
- key: string;
91
- label: string;
92
- icon: string;
93
- plannedOnly: boolean;
94
- }
95
-
96
- const groupsWithItems = computed<Array<{ name: string | null; items: MatrixFeature[] }>>(() => {
97
- const allItems: MatrixFeature[] = Object.entries(props.featureRegistry).map(([key, meta]) => ({
98
- key,
99
- label: meta.label ?? key,
100
- icon: meta.icon ?? 'extension',
101
- plannedOnly: meta.plannedOnly === true,
102
- }));
103
-
104
- if (!props.featureGroups) {
105
- return allItems.length > 0 ? [{ name: null, items: allItems }] : [];
106
- }
107
-
108
- const fallback = props.featureGroups.fallbackGroup ?? 'Sonstige';
109
- const byGroup = new Map<string, MatrixFeature[]>();
110
- for (const item of allItems) {
111
- const group = props.featureGroups.byKey[item.key] ?? fallback;
112
- const list = byGroup.get(group) ?? [];
113
- list.push(item);
114
- byGroup.set(group, list);
115
- }
116
- const orderWithFallback = [
117
- ...props.featureGroups.order,
118
- ...(byGroup.has(fallback) && !props.featureGroups.order.includes(fallback)
119
- ? [fallback]
120
- : []),
121
- ];
122
- return orderWithFallback.flatMap((g) => {
123
- const items = byGroup.get(g);
124
- return items && items.length > 0 ? [{ name: g, items }] : [];
125
- });
126
- });
127
- </script>
128
-
129
- <style scoped>
130
- .sa-pv-matrix {
131
- padding: 20px 28px;
132
- }
133
- .sa-pv-matrix__card {
134
- background: #fff;
135
- border: 1px solid var(--sa-border, #e2e8f0);
136
- border-radius: 12px;
137
- overflow: hidden;
138
- }
139
-
140
- .sa-pv-matrix__head {
141
- position: sticky;
142
- top: 0;
143
- z-index: 2;
144
- display: grid;
145
- background: #fafbfc;
146
- border-bottom: 1px solid var(--sa-border, #e2e8f0);
147
- }
148
- .sa-pv-matrix__th {
149
- padding: 12px 16px;
150
- font-size: 11px;
151
- font-weight: 700;
152
- letter-spacing: 0.08em;
153
- color: var(--sa-muted, #64748b);
154
- text-transform: uppercase;
155
- border-right: 1px solid var(--sa-border, #e2e8f0);
156
- }
157
- .sa-pv-matrix__plan {
158
- padding: 12px 14px;
159
- text-align: center;
160
- border-right: 1px solid var(--sa-border, #e2e8f0);
161
- }
162
- .sa-pv-matrix__plan-name {
163
- font-size: 12.5px;
164
- font-weight: 700;
165
- color: var(--sa-heading, #0f172a);
166
- font-family: var(--sa-font-head, system-ui, sans-serif);
167
- }
168
- .sa-pv-matrix__plan-price {
169
- font-size: 10.5px;
170
- color: var(--sa-muted, #64748b);
171
- margin-top: 1px;
172
- font-family: var(--sa-font-mono, ui-monospace, monospace);
173
- }
174
-
175
- .sa-pv-matrix__group {
176
- padding: 8px 16px;
177
- background: var(--sa-border-soft, #f1f5f9);
178
- font-size: 10.5px;
179
- font-weight: 700;
180
- letter-spacing: 0.08em;
181
- color: var(--sa-muted, #64748b);
182
- text-transform: uppercase;
183
- border-bottom: 1px solid var(--sa-border, #e2e8f0);
184
- }
185
- .sa-pv-matrix__row {
186
- display: grid;
187
- border-bottom: 1px solid var(--sa-border-soft, #f1f5f9);
188
- }
189
-
190
- .sa-pv-matrix__feat-cell {
191
- padding: 10px 16px;
192
- display: flex;
193
- align-items: center;
194
- gap: 10px;
195
- border-right: 1px solid var(--sa-border-soft, #f1f5f9);
196
- }
197
- .sa-pv-matrix__feat-icon {
198
- width: 24px;
199
- height: 24px;
200
- border-radius: 6px;
201
- background: var(--sa-primary-soft, rgba(63, 107, 255, 0.08));
202
- display: flex;
203
- align-items: center;
204
- justify-content: center;
205
- flex-shrink: 0;
206
- }
207
- .sa-pv-matrix__feat-icon :deep(.q-icon) {
208
- color: var(--sa-primary, #3f6bff);
209
- }
210
- .sa-pv-matrix__feat-label {
211
- font-size: 12.5px;
212
- font-weight: 600;
213
- color: var(--sa-heading, #0f172a);
214
- display: flex;
215
- align-items: center;
216
- gap: 5px;
217
- }
218
- .sa-pv-matrix__planned {
219
- font-size: 9px;
220
- font-weight: 700;
221
- letter-spacing: 0.06em;
222
- background: #fef3c7;
223
- color: #92400e;
224
- padding: 1px 5px;
225
- border-radius: 3px;
226
- }
227
- .sa-pv-matrix__feat-key {
228
- font-size: 10.5px;
229
- color: var(--sa-muted, #64748b);
230
- margin-top: 1px;
231
- font-family: var(--sa-font-mono, ui-monospace, monospace);
232
- }
233
-
234
- .sa-pv-matrix__cell {
235
- display: flex;
236
- align-items: center;
237
- justify-content: center;
238
- border-right: 1px solid var(--sa-border-soft, #f1f5f9);
239
- }
240
- .sa-pv-matrix__cell--off {
241
- background: #fafbfc;
242
- }
243
- .sa-pv-matrix__check {
244
- width: 22px;
245
- height: 22px;
246
- border-radius: 50%;
247
- background: var(--sa-positive-soft, rgba(4, 120, 87, 0.1));
248
- display: flex;
249
- align-items: center;
250
- justify-content: center;
251
- }
252
- .sa-pv-matrix__check :deep(.q-icon) {
253
- color: var(--sa-positive, #047857);
254
- }
255
- .sa-pv-matrix__dash {
256
- width: 22px;
257
- height: 22px;
258
- border-radius: 50%;
259
- border: 1px dashed var(--sa-border, #e2e8f0);
260
- }
261
- </style>
@@ -1,33 +0,0 @@
1
- // Shared types of the plan version views.
2
- //
3
- // Phase 2c: ported from a consumer admin;
4
- // source type generic over the platform version rows.
5
-
6
- import type { PlanVersionRow } from '@saasicat/types';
7
-
8
- export type PlanVersionViewMode = 'list' | 'matrix' | 'audit';
9
-
10
- export type DraftKind = 'plan';
11
-
12
- export interface DraftEntry {
13
- kind: DraftKind;
14
- id: string;
15
- label: string;
16
- detail: string;
17
- source: PlanVersionRow;
18
- baseId: string | null;
19
- hasChangeNote: boolean;
20
- }
21
-
22
- export interface ValidationCheck {
23
- ok: boolean;
24
- label: string;
25
- detail: string;
26
- }
27
-
28
- export interface PublishResult {
29
- id: string;
30
- label: string;
31
- ok: boolean;
32
- error?: string;
33
- }