@saasicat/ui-vue 0.7.0 → 0.9.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 (41) hide show
  1. package/dist/{chunk-A3W2N26K.js → chunk-66BUSVC5.js} +68 -217
  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 +180 -463
  5. package/dist/client/index.d.cts +32 -57
  6. package/dist/client/index.d.ts +32 -57
  7. package/dist/client/index.js +4 -6
  8. package/dist/index.cjs +273 -580
  9. package/dist/index.d.cts +10 -55
  10. package/dist/index.d.ts +10 -55
  11. package/dist/index.js +76 -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 +5 -5
  21. package/src/client/admin-resource-client.ts +112 -0
  22. package/src/client/i18n/messages/nav.ts +0 -2
  23. package/src/client/i18n/messages/plan-versions.ts +0 -143
  24. package/src/client/index.ts +1 -2
  25. package/src/client/nav-builder.ts +9 -4
  26. package/src/index.ts +0 -1
  27. package/src/pages-standard/AdminLayout.vue +1 -1
  28. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +1 -1
  29. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +1 -1
  30. package/src/pages-standard/plan-versions/catalog-history.types.ts +37 -0
  31. package/src/pages-standard/plan-versions/format.ts +2 -38
  32. package/src/client/plan-versions-catalog.ts +0 -380
  33. package/src/pages-standard/PlanVersionsPage.vue +0 -220
  34. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +0 -309
  35. package/src/pages-standard/plan-versions/PlanVersionStatusBadge.vue +0 -48
  36. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +0 -326
  37. package/src/pages-standard/plan-versions/PlanVersionsKpi.vue +0 -65
  38. package/src/pages-standard/plan-versions/PlanVersionsList.vue +0 -315
  39. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +0 -261
  40. package/src/pages-standard/plan-versions/types.ts +0 -33
  41. package/src/vue/use-plan-versions.ts +0 -83
@@ -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
- }
@@ -1,83 +0,0 @@
1
- // usePlanVersionsCatalog — composable for the plan-versions page
2
- // (`<app> /admin/plans`).
3
- //
4
- // **Endpoint is mandatory** and is supplied by the app consumer, because
5
- // apps have different `globalPrefix` conventions
6
- // (e.g. `/api/admin/...` or `/api/v1/admin/...`).
7
- //
8
- // Platform pure functions (`assertDraftPublishable` etc. from
9
- // `@saasicat/nest/billing`) validate the drafts before the
10
- // publish POST.
11
-
12
- import { ref, type Ref } from 'vue';
13
- import { useApiList, type UseApiListOptions, type UseApiListResult } from './use-api-list.js';
14
-
15
- /**
16
- * Generic filter shape for versions lists.
17
- * `state`: 'draft' (publishedAt null), 'live' (publishedAt set, supersededAt null),
18
- * 'superseded' (supersededAt set), 'all' (default).
19
- */
20
- export interface PlanVersionListFilter {
21
- state?: 'draft' | 'live' | 'superseded' | 'all';
22
- /** PlanId (`'BASIC'`, `'STANDARD'`, …). */
23
- planId?: string;
24
- }
25
-
26
- /**
27
- * Generic versions row shape. Depending on the endpoint, the consumer backend
28
- * returns slightly different fields; the platform composable takes the universal ones.
29
- */
30
- export interface PlanVersionRow {
31
- id: string;
32
- version: number;
33
- publishedAt: string | null;
34
- supersededAt: string | null;
35
- nonRegressive: boolean;
36
- changeNote?: string;
37
- [extra: string]: unknown;
38
- }
39
-
40
- export interface VersionsOptions {
41
- /**
42
- * Fully qualified versions-list endpoint including the app globalPrefix
43
- * (`/api/admin/plan-versions`, `/api/v1/admin/plan-versions`, …). Mandatory.
44
- */
45
- endpoint: string;
46
- filter?: Ref<PlanVersionListFilter>;
47
- http?: UseApiListOptions<Record<string, unknown>>['http'];
48
- getAuthToken?: () => string | null;
49
- autoLoad?: boolean;
50
- }
51
-
52
- export interface VersionsResult extends UseApiListResult<PlanVersionRow> {
53
- filter: Ref<PlanVersionListFilter>;
54
- }
55
-
56
- function buildVersionsComposable(label: string) {
57
- return function (options: VersionsOptions): VersionsResult {
58
- if (!options?.endpoint) {
59
- throw new Error(
60
- `${label}: \`endpoint\` is required (e.g. "/api/admin/${label}" ` +
61
- `or "/api/v1/admin/${label}"). The platform has no default ` +
62
- `because apps use different globalPrefix conventions.`,
63
- );
64
- }
65
- const filter = options.filter ?? ref<PlanVersionListFilter>({});
66
- const list = useApiList<PlanVersionRow>({
67
- endpoint: options.endpoint,
68
- filter: filter as unknown as Ref<Record<string, unknown>>,
69
- http: options.http,
70
- getAuthToken: options.getAuthToken,
71
- autoLoad: options.autoLoad,
72
- });
73
- return { ...list, filter };
74
- };
75
- }
76
-
77
- /**
78
- * Read-only catalog view for PlanVersions (lift-and-shift from a
79
- * consumer admin).
80
- * Called `usePlanVersionsCatalog` since M6 Pack 2a, because `usePlanVersions`
81
- * from `use-plans.ts` is now the lifecycle editor (createDraft/publish).
82
- */
83
- export const usePlanVersionsCatalog = buildVersionsComposable('plan-versions');