@saasicat/ui-vue 0.19.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 (145) hide show
  1. package/dist/{catalog-JXdKdIqG.d.cts → catalog-DJcfm5N2.d.cts} +21 -7
  2. package/dist/{catalog-JXdKdIqG.d.ts → catalog-DJcfm5N2.d.ts} +21 -7
  3. package/dist/{chunk-DPTIOQXX.js → chunk-F7NRM6KI.js} +56 -28
  4. package/dist/{chunk-RVG6LQHL.js → chunk-LY52T4N4.js} +21 -2
  5. package/dist/{chunk-ORX3ZG47.js → chunk-XMMFN2WJ.js} +74 -3
  6. package/dist/client/index.cjs +134 -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 +321 -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 +78 -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-BlqcD0N7.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
  21. package/dist/{use-super-admin-i18n-1nCOkvgi.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 +24 -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/AdminBody.vue +40 -0
  40. package/src/components/admin-page/AdminFilters.vue +12 -0
  41. package/src/components/admin-page/AdminHero.vue +49 -0
  42. package/src/components/admin-page/AdminKpi.vue +84 -0
  43. package/src/components/admin-page/AdminPage.vue +16 -0
  44. package/src/components/admin-page/AdminPaginator.vue +195 -0
  45. package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
  46. package/src/components/admin-page/AdminSection.vue +53 -0
  47. package/src/components/admin-page/AdminStatistics.vue +40 -0
  48. package/src/components/admin-page/AdminTable.vue +175 -0
  49. package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
  50. package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
  51. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
  52. package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
  53. package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
  54. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
  55. package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
  56. package/src/components/dialogs/PilotCreateDialog.vue +12 -12
  57. package/src/components/dialogs/PilotEditDialog.vue +7 -7
  58. package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
  59. package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
  60. package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
  61. package/src/components/plan/PlanGrid.vue +1 -1
  62. package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
  63. package/src/components/plan-detail/PlanAuditLog.vue +7 -8
  64. package/src/components/plan-detail/PlanDetail.vue +95 -292
  65. package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
  66. package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
  67. package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
  68. package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
  69. package/src/components/plan-list/PlanList.vue +206 -457
  70. package/src/components/plan-matrix/PlanMatrix.vue +66 -183
  71. package/src/components/plan-review/PlanReview.vue +191 -275
  72. package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
  73. package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
  74. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
  75. package/src/index.ts +1 -0
  76. package/src/pages-standard/AdminLayout.vue +38 -12
  77. package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
  78. package/src/pages-standard/AuditPage.vue +65 -109
  79. package/src/pages-standard/BundlesPage.vue +146 -179
  80. package/src/pages-standard/DashboardPage.vue +59 -154
  81. package/src/pages-standard/DiscoveryPage.vue +172 -227
  82. package/src/pages-standard/EmailHistoryPage.vue +118 -159
  83. package/src/pages-standard/MarketingCatalogPage.vue +399 -440
  84. package/src/pages-standard/PilotsPage.vue +69 -117
  85. package/src/pages-standard/PlansPage.vue +240 -112
  86. package/src/pages-standard/PlatformEmailPage.vue +56 -91
  87. package/src/pages-standard/PromoCodeDetailPage.vue +49 -74
  88. package/src/pages-standard/PromoCodesPage.vue +95 -128
  89. package/src/pages-standard/SubscriptionsPage.vue +19 -52
  90. package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
  91. package/src/pages-standard/SuperAdminSetupWizard.vue +22 -18
  92. package/src/pages-standard/TenantDetailPage.vue +100 -191
  93. package/src/pages-standard/TenantsPage.vue +190 -295
  94. package/src/pages-standard/UsersPage.vue +89 -157
  95. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
  96. package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
  97. package/src/pages-standard/bundles-page/BundlesToolbar.vue +63 -0
  98. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
  99. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
  100. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
  101. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
  102. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
  103. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +111 -73
  104. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +43 -62
  105. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
  106. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
  107. package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
  108. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
  109. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
  110. package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
  111. package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
  112. package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
  113. package/src/pages-standard/sa-theme.css +552 -86
  114. package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
  115. package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
  116. package/src/pages-standard/tenant-detail/TenantUsers.vue +15 -0
  117. package/src/pages-standard/tenant-detail/types.ts +38 -0
  118. package/src/pages-standard/tenants/StatusPill.vue +2 -2
  119. package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
  120. package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
  121. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  122. package/src/pages-tenant/TenantPlanSection.vue +12 -20
  123. package/src/quasar/create-super-admin-app.ts +4 -0
  124. package/src/vue/entitlement-provider.ts +0 -1
  125. package/src/vue/feature-router-guard.ts +0 -1
  126. package/src/vue/super-admin-context.ts +106 -1
  127. package/src/vue/use-api-list.ts +2 -1
  128. package/src/vue/use-mfa-prompt.ts +72 -0
  129. package/src/vue/use-pagination.ts +63 -0
  130. package/src/vue/use-plans.ts +1 -2
  131. package/src/vue/use-sign-out.ts +90 -0
  132. package/src/vue/use-tenant-billing.ts +1 -1
  133. package/src/vue/use-tenant-manifest.ts +0 -1
  134. package/src/components/KpiCard.vue +0 -53
  135. package/src/components/plan-cockpit/PlanCockpit.vue +0 -1012
  136. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
  137. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -100
  138. package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
  139. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
  140. package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
  141. package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -251
  142. package/src/components/plan-cockpit/types.ts +0 -72
  143. package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
  144. package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
  145. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +0 -34
@@ -1,251 +0,0 @@
1
- <template>
2
- <section class="pc-card pc-card--left">
3
- <div class="pc-card-head">
4
- <div class="pc-card-head-text">
5
- <div class="pc-card-title">{{ msg.versions.title }}</div>
6
- <div class="pc-card-sub">
7
- {{ msg.versions.subtitle }}
8
- <code class="pc-mono">supersededAt</code>
9
- </div>
10
- </div>
11
- <button
12
- class="pc-btn pc-btn--sm pc-btn--primary"
13
- type="button"
14
- :disabled="hasOpenDraft"
15
- @click="emit('createDraft')"
16
- >
17
- <svg
18
- width="12"
19
- height="12"
20
- viewBox="0 0 24 24"
21
- fill="none"
22
- stroke="currentColor"
23
- stroke-width="2.5"
24
- >
25
- <path d="M12 5v14M5 12h14" />
26
- </svg>
27
- <span>{{ newDraftButtonLabel }}</span>
28
- </button>
29
- </div>
30
-
31
- <div v-if="versions.length > 0" class="pc-vtimeline">
32
- <div class="pc-vtl-track">
33
- <div
34
- v-for="seg in timelineSegments"
35
- :key="seg.key"
36
- :class="['pc-vtl-seg', `pc-vtl-${seg.status}`]"
37
- :style="{ flex: seg.flex }"
38
- >
39
- v{{ seg.version
40
- }}<template v-if="seg.status === 'draft'">{{
41
- msg.versions.timelineDraftSuffix
42
- }}</template>
43
- </div>
44
- </div>
45
- <div class="pc-vtl-axis">
46
- <span v-for="(label, i) in timelineAxis" :key="i">{{ label }}</span>
47
- </div>
48
- </div>
49
-
50
- <div class="pc-vrow pc-vrow--head">
51
- <div class="pc-vrow-version pc-vrow-headlabel">{{ msg.versions.colVersion }}</div>
52
- <div class="pc-vrow-cell pc-vrow-headlabel">{{ msg.versions.colValidity }}</div>
53
- <div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">
54
- {{ msg.versions.colPricing }}
55
- </div>
56
- <div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">
57
- {{ msg.versions.colImpact }}
58
- </div>
59
- <div class="pc-vrow-note pc-vrow-headlabel">{{ msg.versions.colChangeNoteShort }}</div>
60
- <div class="pc-vrow-actions" />
61
- </div>
62
- <div v-if="versions.length === 0" class="pc-empty">
63
- {{ msg.versions.empty }}
64
- </div>
65
- <div
66
- v-for="row in sortedVersions"
67
- :key="row.id"
68
- :class="['pc-vrow', row.publishedAt === null ? 'pc-vrow--active' : '']"
69
- >
70
- <div class="pc-vrow-version">
71
- <span class="pc-vrow-vlabel">v{{ row.version }}</span>
72
- <span :class="['pc-chip pc-chip--dot', statusChipClass(row)]">{{
73
- statusLabel(row)
74
- }}</span>
75
- </div>
76
- <div class="pc-vrow-cell">
77
- <div class="pc-vrow-validity">
78
- {{ row.validFrom ? row.validFrom.slice(0, 10) : '—' }}
79
- <span class="pc-vrow-arrow">→</span>
80
- <template v-if="row.validUntil">{{ row.validUntil.slice(0, 10) }}</template>
81
- <span v-else class="pc-vrow-inf">∞</span>
82
- </div>
83
- <div class="pc-vrow-sub">{{ updatedLabel(row) }}</div>
84
- </div>
85
- <div class="pc-vrow-cell pc-vrow-cell--right">
86
- <div class="pc-vrow-price">
87
- {{ formatMoney(row.monthlyNet)
88
- }}<span class="pc-vrow-price-unit">{{ msg.versions.perMonthUnit }}</span>
89
- </div>
90
- <div class="pc-vrow-sub">
91
- {{ formatMoney(row.yearlyNet) }} {{ msg.versions.perYearUnit }}
92
- </div>
93
- </div>
94
- <div class="pc-vrow-cell pc-vrow-cell--right">
95
- <div
96
- class="pc-vrow-impact"
97
- :class="(impactByVersion[row.version] ?? 0) > 0 ? '' : 'pc-vrow-impact--zero'"
98
- >
99
- {{ impactByVersion[row.version] ?? 0 }}
100
- </div>
101
- <div class="pc-vrow-sub">{{ msg.versions.tenants }}</div>
102
- </div>
103
- <div class="pc-vrow-note" :title="row.changeNote ?? ''">
104
- {{ row.changeNote || '—' }}
105
- </div>
106
- <div class="pc-vrow-actions">
107
- <button
108
- v-if="row.publishedAt === null"
109
- class="pc-btn pc-btn--sm pc-btn--primary"
110
- type="button"
111
- @click="emit('publish', row)"
112
- >
113
- <svg
114
- width="12"
115
- height="12"
116
- viewBox="0 0 24 24"
117
- fill="none"
118
- stroke="currentColor"
119
- stroke-width="2"
120
- >
121
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
122
- </svg>
123
- <span>{{ msg.versions.publish }}</span>
124
- </button>
125
- <button
126
- v-if="editabilityOf(row).editable"
127
- class="pc-btn pc-btn--sm"
128
- type="button"
129
- :title="
130
- editabilityOf(row).reason === 'pre-active'
131
- ? msg.versions.preActiveTitle
132
- : msg.versions.editDraftTitle
133
- "
134
- @click="emit('editDraft', row)"
135
- >
136
- <svg
137
- width="12"
138
- height="12"
139
- viewBox="0 0 24 24"
140
- fill="none"
141
- stroke="currentColor"
142
- stroke-width="2"
143
- >
144
- <path d="M12 20h9M16.5 3.5a2.12 2.12 0 113 3L7 19l-4 1 1-4 12.5-12.5z" />
145
- </svg>
146
- <span>{{ common.edit }}</span>
147
- </button>
148
- <button
149
- v-if="row.publishedAt !== null && row.supersededAt === null && draftVersion"
150
- class="pc-btn pc-btn--sm"
151
- type="button"
152
- @click="emit('viewDiff', draftVersion, row)"
153
- >
154
- <svg
155
- width="12"
156
- height="12"
157
- viewBox="0 0 24 24"
158
- fill="none"
159
- stroke="currentColor"
160
- stroke-width="2"
161
- >
162
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
163
- <circle cx="12" cy="12" r="3" />
164
- </svg>
165
- <span>{{ msg.versions.viewDiff }}</span>
166
- </button>
167
- </div>
168
- </div>
169
- </section>
170
- </template>
171
-
172
- <script setup lang="ts">
173
- import { computed } from 'vue';
174
- import { isVersionEditable, type PlanVersionRow } from '@saasicat/types';
175
- import { formatMessage } from '../../client/i18n/format.js';
176
- import { formatCurrency } from '../../client/i18n/currency.js';
177
- import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
178
- import type { TimelineSegment } from './types';
179
-
180
- const props = defineProps<{
181
- versions: PlanVersionRow[];
182
- sortedVersions: PlanVersionRow[];
183
- timelineSegments: TimelineSegment[];
184
- timelineAxis: string[];
185
- hasOpenDraft: boolean;
186
- nextDraftVersion: number;
187
- impactByVersion: Record<number, number>;
188
- draftVersion: PlanVersionRow | null;
189
- }>();
190
-
191
- const emit = defineEmits<{
192
- (e: 'createDraft'): void;
193
- (e: 'publish', version: PlanVersionRow): void;
194
- (e: 'editDraft', version: PlanVersionRow): void;
195
- (e: 'viewDiff', from: PlanVersionRow, to: PlanVersionRow): void;
196
- }>();
197
-
198
- const msg = useSaMessages('planDetail');
199
- const { locale } = useSuperAdminI18n();
200
- const common = useSaMessages('common');
201
-
202
- const newDraftButtonLabel = computed(() =>
203
- formatMessage(msg.value.versions.newDraftButton, { version: props.nextDraftVersion }),
204
- );
205
-
206
- function editabilityOf(version: PlanVersionRow): {
207
- editable: boolean;
208
- reason: 'draft' | 'pre-active' | null;
209
- } {
210
- return isVersionEditable(version);
211
- }
212
-
213
- function statusChipClass(row: PlanVersionRow): string {
214
- if (row.publishedAt === null) return 'pc-chip--draft';
215
- if (row.supersededAt !== null) return 'pc-chip--supersed';
216
- return 'pc-chip--live';
217
- }
218
-
219
- function statusLabel(row: PlanVersionRow): string {
220
- if (row.publishedAt === null) return 'draft';
221
- if (row.supersededAt !== null) return 'superseded';
222
- return 'live';
223
- }
224
-
225
- function formatMoney(raw: string | number): string {
226
- const num = typeof raw === 'string' ? Number(raw) : raw;
227
- if (!Number.isFinite(num)) return String(raw);
228
- return formatCurrency(num, locale.value);
229
- }
230
-
231
- function updatedLabel(row: PlanVersionRow): string {
232
- const iso =
233
- row.publishedAt ??
234
- (row as unknown as { updatedAt?: string }).updatedAt ??
235
- (row as unknown as { createdAt?: string }).createdAt ??
236
- '';
237
- if (!iso) return '';
238
- const d = new Date(iso);
239
- if (Number.isNaN(d.getTime())) return '';
240
- const diffMs = Date.now() - d.getTime();
241
- const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
242
- if (days <= 0) return msg.value.versions.updatedToday;
243
- if (days < 30) return formatMessage(msg.value.versions.updatedDaysAgo, { days });
244
- if (days < 365) {
245
- return formatMessage(msg.value.versions.updatedMonthsAgo, {
246
- months: Math.floor(days / 30),
247
- });
248
- }
249
- return formatMessage(msg.value.versions.updatedYearsAgo, { years: Math.floor(days / 365) });
250
- }
251
- </script>
@@ -1,72 +0,0 @@
1
- import type { PlanVersionRow } from '@saasicat/types';
2
-
3
- export interface AuditRow {
4
- id: string;
5
- createdAt: string;
6
- action: string;
7
- entity: string;
8
- entityId: string;
9
- changes: Record<string, unknown> | null;
10
- user?: { email: string; firstName?: string; lastName?: string } | null;
11
- userEmail?: string | null;
12
- }
13
-
14
- export interface ImpactTenant {
15
- name: string;
16
- plan: string;
17
- state: 'auto' | 'review' | 'conflict';
18
- }
19
-
20
- export interface TenantImpact {
21
- auto: number;
22
- review: number;
23
- conflict: number;
24
- examples?: ImpactTenant[];
25
- }
26
-
27
- export interface DiscoveryQuota {
28
- quotaKey: string;
29
- label?: string | null;
30
- unit?: string | null;
31
- }
32
-
33
- export interface FeatureMeta {
34
- label?: string;
35
- }
36
-
37
- export interface TimelineSegment {
38
- key: string;
39
- version: number;
40
- status: 'draft' | 'supersed' | 'live';
41
- flex: number;
42
- }
43
-
44
- export interface PlanVersionPair {
45
- from: PlanVersionRow;
46
- to: PlanVersionRow;
47
- }
48
-
49
- export interface DiffSummary {
50
- addedFeatures: string[];
51
- removedFeatures: string[];
52
- addedQuotas: Array<{ key: string; value: number }>;
53
- removedQuotas: Array<{ key: string; value: number }>;
54
- changedQuotas: Array<{ key: string; from: number; to: number }>;
55
- priceChanged: boolean;
56
- priceFrom: string;
57
- priceTo: string;
58
- }
59
-
60
- export interface DiffRow {
61
- id: string;
62
- section: string;
63
- label: string;
64
- sub?: string;
65
- from?: string;
66
- to?: string;
67
- bg: string;
68
- border: string;
69
- color: string;
70
- sign: string;
71
- tag: string;
72
- }
@@ -1,194 +0,0 @@
1
- <template>
2
- <div class="pd-header">
3
- <div class="pd-header-left">
4
- <span class="pd-tier-chip">{{ plan.planKey }}</span>
5
- <div class="pd-header-titles">
6
- <div class="pd-title-wrap">
7
- <template v-if="editingName">
8
- <input
9
- ref="nameInput"
10
- v-model="nameDraft"
11
- class="pd-title-input"
12
- @keydown.enter="commitName"
13
- @keydown.escape="cancelName"
14
- @blur="commitName"
15
- />
16
- <span class="pd-title-hint">{{ msg.header.nameEditHint }}</span>
17
- </template>
18
- <template v-else>
19
- <h1 class="pd-title">{{ plan.label }}</h1>
20
- <button
21
- v-if="draftVersion"
22
- class="pd-title-edit-btn"
23
- type="button"
24
- :title="msg.header.editNameTitle"
25
- :aria-label="msg.header.editNameAria"
26
- @click="startEditName"
27
- >
28
- <svg
29
- width="14"
30
- height="14"
31
- viewBox="0 0 24 24"
32
- fill="none"
33
- stroke="currentColor"
34
- stroke-width="2"
35
- >
36
- <path
37
- d="M12 20h9M16.5 3.5a2.12 2.12 0 113 3L7 19l-4 1 1-4 12.5-12.5z"
38
- />
39
- </svg>
40
- </button>
41
- </template>
42
- </div>
43
- <div class="pd-desc">{{ plan.description || msg.header.noDescription }}</div>
44
- </div>
45
- </div>
46
- <div class="pd-actions">
47
- <button class="btn" type="button" @click="$emit('back')">
48
- <span class="pd-back-arrow" aria-hidden="true">
49
- <svg
50
- width="14"
51
- height="14"
52
- viewBox="0 0 24 24"
53
- fill="none"
54
- stroke="currentColor"
55
- stroke-width="2"
56
- >
57
- <path d="M5 12h14M13 5l7 7-7 7" />
58
- </svg>
59
- </span>
60
- <span>{{ common.back }}</span>
61
- </button>
62
- <button
63
- v-if="tenantTotal === 0 && !draftVersion && publishedCount === 0"
64
- class="btn danger"
65
- type="button"
66
- :title="msg.header.deletePlanTitle"
67
- @click="$emit('deletePlan')"
68
- >
69
- <svg
70
- width="14"
71
- height="14"
72
- viewBox="0 0 24 24"
73
- fill="none"
74
- stroke="currentColor"
75
- stroke-width="2"
76
- >
77
- <path
78
- d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"
79
- />
80
- </svg>
81
- <span>{{ msg.header.deletePlan }}</span>
82
- </button>
83
- <button class="btn" type="button" @click="$emit('clonePlan')">
84
- <svg
85
- width="14"
86
- height="14"
87
- viewBox="0 0 24 24"
88
- fill="none"
89
- stroke="currentColor"
90
- stroke-width="2"
91
- >
92
- <rect x="9" y="9" width="11" height="11" rx="2" />
93
- <path d="M5 15V5a2 2 0 012-2h10" />
94
- </svg>
95
- <span>{{ msg.header.clonePlan }}</span>
96
- </button>
97
- <button
98
- v-if="!draftVersion"
99
- class="btn primary"
100
- type="button"
101
- @click="$emit('createDraft')"
102
- >
103
- <svg
104
- width="14"
105
- height="14"
106
- viewBox="0 0 24 24"
107
- fill="none"
108
- stroke="currentColor"
109
- stroke-width="2.5"
110
- >
111
- <path d="M12 5v14M5 12h14" />
112
- </svg>
113
- <span>{{ msg.header.newDraftVersion }}</span>
114
- </button>
115
- <button
116
- v-else
117
- class="btn primary"
118
- type="button"
119
- @click="$emit('publish', draftVersion)"
120
- >
121
- <svg
122
- width="14"
123
- height="14"
124
- viewBox="0 0 24 24"
125
- fill="none"
126
- stroke="currentColor"
127
- stroke-width="2"
128
- >
129
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
130
- </svg>
131
- <span>{{ publishDraftLabel }}</span>
132
- </button>
133
- </div>
134
- </div>
135
- </template>
136
-
137
- <script setup lang="ts">
138
- import { computed, nextTick, ref, watch } from 'vue';
139
- import type { PlanRow, PlanVersionRow } from '@saasicat/types';
140
- import { formatMessage } from '../../client/i18n/format.js';
141
- import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
142
-
143
- const props = defineProps<{
144
- plan: PlanRow;
145
- draftVersion: PlanVersionRow | null;
146
- tenantTotal: number;
147
- publishedCount: number;
148
- }>();
149
-
150
- const emit = defineEmits<{
151
- (e: 'back'): void;
152
- (e: 'createDraft'): void;
153
- (e: 'publish', version: PlanVersionRow): void;
154
- (e: 'clonePlan'): void;
155
- (e: 'deletePlan'): void;
156
- (e: 'updatePlan', patch: { label: string }): void;
157
- }>();
158
-
159
- const msg = useSaMessages('planDetail');
160
- const common = useSaMessages('common');
161
-
162
- const publishDraftLabel = computed(() =>
163
- formatMessage(msg.value.header.publishDraft, { version: props.draftVersion?.version ?? '' }),
164
- );
165
-
166
- const editingName = ref(false);
167
- const nameDraft = ref(props.plan.label);
168
- const nameInput = ref<HTMLInputElement | null>(null);
169
-
170
- watch(
171
- () => props.plan.label,
172
- (label) => {
173
- if (!editingName.value) nameDraft.value = label;
174
- },
175
- );
176
-
177
- function startEditName(): void {
178
- nameDraft.value = props.plan.label;
179
- editingName.value = true;
180
- void nextTick(() => nameInput.value?.focus());
181
- }
182
-
183
- function commitName(): void {
184
- if (!editingName.value) return;
185
- const next = nameDraft.value.trim();
186
- editingName.value = false;
187
- if (next && next !== props.plan.label) emit('updatePlan', { label: next });
188
- }
189
-
190
- function cancelName(): void {
191
- nameDraft.value = props.plan.label;
192
- editingName.value = false;
193
- }
194
- </script>
@@ -1,68 +0,0 @@
1
- <template>
2
- <header class="sa-bundles__head">
3
- <div>
4
- <h1 class="sa-bundles__title">{{ msg.header.title }}</h1>
5
- <p class="sa-bundles__sub">{{ msg.header.subtitle }}</p>
6
- </div>
7
- <div class="sa-bundles__head-actions">
8
- <q-select
9
- v-if="locales.length > 1"
10
- :model-value="displayLocale"
11
- :options="localeOptions"
12
- outlined
13
- dense
14
- emit-value
15
- map-options
16
- class="sa-bundles__locale"
17
- :label="msg.header.displayLocale"
18
- @update:model-value="(value) => emit('update:displayLocale', String(value))"
19
- >
20
- <template #prepend><q-icon name="translate" size="18px" /></template>
21
- </q-select>
22
- <q-btn
23
- unelevated
24
- color="primary"
25
- icon="add"
26
- :label="msg.header.newBundle"
27
- @click="emit('create')"
28
- />
29
- <q-btn flat icon="refresh" :loading="loading" @click="emit('refresh')">
30
- <q-tooltip>{{ msg.header.reload }}</q-tooltip>
31
- </q-btn>
32
- </div>
33
- </header>
34
- </template>
35
-
36
- <script setup lang="ts">
37
- import { computed } from 'vue';
38
-
39
- import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
40
-
41
- // The language selector controls which locale feature/quota labels are resolved
42
- // on the create form and in the detail view (fallback locale → DE → key). The default is the
43
- // default locale (DE); further options come from the active project locales.
44
-
45
- const props = defineProps<{
46
- loading: boolean;
47
- displayLocale: string;
48
- locales: string[];
49
- }>();
50
-
51
- const emit = defineEmits<{
52
- create: [];
53
- refresh: [];
54
- 'update:displayLocale': [locale: string];
55
- }>();
56
-
57
- const msg = useSaMessages('bundles');
58
-
59
- const localeOptions = computed(() =>
60
- props.locales.map((locale) => ({ label: locale.toUpperCase(), value: locale })),
61
- );
62
- </script>
63
-
64
- <style scoped>
65
- .sa-bundles__locale {
66
- min-width: 150px;
67
- }
68
- </style>
@@ -1,34 +0,0 @@
1
- <template>
2
- <header class="sa-discovery__head">
3
- <div>
4
- <h1 class="sa-discovery__title">{{ msg.title }}</h1>
5
- <p class="sa-discovery__sub">
6
- {{ msg.subtitleLead }} <b>{{ msg.subtitleEmphasis }}</b> {{ msg.subtitleTail }}
7
- </p>
8
- </div>
9
- <div class="sa-discovery__head-actions">
10
- <q-btn
11
- unelevated
12
- color="primary"
13
- icon="bolt"
14
- :label="msg.runDiscovery"
15
- :loading="loading"
16
- @click="emit('runDiscovery')"
17
- />
18
- </div>
19
- </header>
20
- </template>
21
-
22
- <script setup lang="ts">
23
- import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
24
-
25
- defineProps<{
26
- loading: boolean;
27
- }>();
28
-
29
- const emit = defineEmits<{
30
- (e: 'runDiscovery'): void;
31
- }>();
32
-
33
- const msg = useSaMessages('discovery');
34
- </script>