@saasicat/ui-vue 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/{catalog-BY_t9prv.d.cts → catalog-DJcfm5N2.d.cts} +20 -7
  2. package/dist/{catalog-BY_t9prv.d.ts → catalog-DJcfm5N2.d.ts} +20 -7
  3. package/dist/{chunk-JN6HZGLV.js → chunk-F7NRM6KI.js} +54 -28
  4. package/dist/{chunk-7IFKVGZV.js → chunk-LY52T4N4.js} +21 -2
  5. package/dist/{chunk-OTP3OSSW.js → chunk-XMMFN2WJ.js} +74 -3
  6. package/dist/client/index.cjs +132 -29
  7. package/dist/client/index.d.cts +53 -3
  8. package/dist/client/index.d.ts +53 -3
  9. package/dist/client/index.js +14 -2
  10. package/dist/index.cjs +319 -162
  11. package/dist/index.d.cts +30 -8
  12. package/dist/index.d.ts +30 -8
  13. package/dist/index.js +176 -133
  14. package/dist/quasar/index.cjs +76 -29
  15. package/dist/quasar/index.d.cts +2 -2
  16. package/dist/quasar/index.d.ts +2 -2
  17. package/dist/quasar/index.js +6 -2
  18. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  19. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  20. package/dist/{use-super-admin-i18n-c_Eqsu9u.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
  21. package/dist/{use-super-admin-i18n-DlJRGIVq.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
  22. package/package.json +18 -8
  23. package/src/client/i18n/messages/bundles.ts +0 -2
  24. package/src/client/i18n/messages/common.ts +22 -0
  25. package/src/client/i18n/messages/discovery.ts +2 -2
  26. package/src/client/i18n/messages/marketing.ts +2 -4
  27. package/src/client/i18n/messages/plan-detail.ts +0 -4
  28. package/src/client/i18n/messages/plans.ts +20 -8
  29. package/src/client/i18n/messages/tenants.ts +0 -2
  30. package/src/client/index.ts +1 -0
  31. package/src/client/manifest-loader.ts +1 -1
  32. package/src/client/resolve-plans.ts +144 -0
  33. package/src/components/BundleVersionPublishDialog.vue +6 -6
  34. package/src/components/KvBlock.vue +4 -4
  35. package/src/components/MarketingPromotionsTab.vue +34 -48
  36. package/src/components/MfaPromptDialog.vue +8 -2
  37. package/src/components/TenantActionConfirmDialog.vue +4 -4
  38. package/src/components/VersionDiffPreview.vue +7 -7
  39. package/src/components/admin-page/AdminFilters.vue +12 -0
  40. package/src/components/admin-page/AdminHero.vue +22 -5
  41. package/src/components/admin-page/AdminKpi.vue +84 -0
  42. package/src/components/admin-page/AdminPaginator.vue +195 -0
  43. package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
  44. package/src/components/admin-page/AdminSection.vue +31 -16
  45. package/src/components/admin-page/AdminStatistics.vue +40 -0
  46. package/src/components/admin-page/AdminTable.vue +175 -0
  47. package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
  48. package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
  49. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
  50. package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
  51. package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
  52. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
  53. package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
  54. package/src/components/dialogs/PilotCreateDialog.vue +12 -12
  55. package/src/components/dialogs/PilotEditDialog.vue +7 -7
  56. package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
  57. package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
  58. package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
  59. package/src/components/plan/PlanGrid.vue +1 -1
  60. package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
  61. package/src/components/plan-detail/PlanDetail.vue +92 -302
  62. package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
  63. package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
  64. package/src/components/plan-list/PlanList.vue +72 -290
  65. package/src/components/plan-matrix/PlanMatrix.vue +66 -183
  66. package/src/components/plan-review/PlanReview.vue +191 -275
  67. package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
  68. package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
  69. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
  70. package/src/index.ts +1 -0
  71. package/src/pages-standard/AdminLayout.vue +34 -10
  72. package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
  73. package/src/pages-standard/AuditPage.vue +53 -72
  74. package/src/pages-standard/BundlesPage.vue +128 -144
  75. package/src/pages-standard/DashboardPage.vue +43 -107
  76. package/src/pages-standard/DiscoveryPage.vue +158 -200
  77. package/src/pages-standard/EmailHistoryPage.vue +98 -88
  78. package/src/pages-standard/MarketingCatalogPage.vue +237 -267
  79. package/src/pages-standard/PilotsPage.vue +44 -60
  80. package/src/pages-standard/PlansPage.vue +230 -105
  81. package/src/pages-standard/PlatformEmailPage.vue +37 -40
  82. package/src/pages-standard/PromoCodeDetailPage.vue +16 -30
  83. package/src/pages-standard/PromoCodesPage.vue +86 -112
  84. package/src/pages-standard/SubscriptionsPage.vue +10 -17
  85. package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
  86. package/src/pages-standard/SuperAdminSetupWizard.vue +20 -17
  87. package/src/pages-standard/TenantDetailPage.vue +45 -41
  88. package/src/pages-standard/TenantsPage.vue +184 -264
  89. package/src/pages-standard/UsersPage.vue +69 -104
  90. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
  91. package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
  92. package/src/pages-standard/bundles-page/BundlesToolbar.vue +6 -10
  93. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
  94. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
  95. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
  96. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
  97. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
  98. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +1 -1
  99. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +2 -13
  100. package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
  101. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
  102. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
  103. package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
  104. package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
  105. package/src/pages-standard/plans-page/PlansPageToast.vue +1 -1
  106. package/src/pages-standard/sa-theme.css +481 -71
  107. package/src/pages-standard/tenant-detail/TenantUsers.vue +2 -8
  108. package/src/pages-standard/tenants/StatusPill.vue +2 -2
  109. package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
  110. package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
  111. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  112. package/src/pages-tenant/TenantPlanSection.vue +12 -20
  113. package/src/quasar/create-super-admin-app.ts +4 -0
  114. package/src/vue/entitlement-provider.ts +0 -1
  115. package/src/vue/feature-router-guard.ts +0 -1
  116. package/src/vue/super-admin-context.ts +106 -1
  117. package/src/vue/use-api-list.ts +2 -1
  118. package/src/vue/use-pagination.ts +63 -0
  119. package/src/vue/use-plans.ts +1 -2
  120. package/src/vue/use-sign-out.ts +90 -0
  121. package/src/vue/use-tenant-billing.ts +1 -1
  122. package/src/vue/use-tenant-manifest.ts +0 -1
  123. package/src/components/KpiCard.vue +0 -53
  124. package/src/components/plan-cockpit/PlanCockpit.vue +0 -1027
  125. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
  126. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -99
  127. package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
  128. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
  129. package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
  130. package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -249
  131. package/src/components/plan-cockpit/types.ts +0 -72
  132. package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
@@ -1,249 +0,0 @@
1
- <template>
2
- <AdminSection :title="msg.versions.title" class="pc-card pc-card--left">
3
- <template #subtitle>
4
- {{ msg.versions.subtitle }}
5
- <code class="pc-mono">supersededAt</code>
6
- </template>
7
- <template #actions>
8
- <button
9
- class="pc-btn pc-btn--sm pc-btn--primary"
10
- type="button"
11
- :disabled="hasOpenDraft"
12
- @click="emit('createDraft')"
13
- >
14
- <svg
15
- width="12"
16
- height="12"
17
- viewBox="0 0 24 24"
18
- fill="none"
19
- stroke="currentColor"
20
- stroke-width="2.5"
21
- >
22
- <path d="M12 5v14M5 12h14" />
23
- </svg>
24
- <span>{{ newDraftButtonLabel }}</span>
25
- </button>
26
- </template>
27
-
28
- <div v-if="versions.length > 0" class="pc-vtimeline">
29
- <div class="pc-vtl-track">
30
- <div
31
- v-for="seg in timelineSegments"
32
- :key="seg.key"
33
- :class="['pc-vtl-seg', `pc-vtl-${seg.status}`]"
34
- :style="{ flex: seg.flex }"
35
- >
36
- v{{ seg.version
37
- }}<template v-if="seg.status === 'draft'">{{
38
- msg.versions.timelineDraftSuffix
39
- }}</template>
40
- </div>
41
- </div>
42
- <div class="pc-vtl-axis">
43
- <span v-for="(label, i) in timelineAxis" :key="i">{{ label }}</span>
44
- </div>
45
- </div>
46
-
47
- <div class="pc-vrow pc-vrow--head">
48
- <div class="pc-vrow-version pc-vrow-headlabel">{{ msg.versions.colVersion }}</div>
49
- <div class="pc-vrow-cell pc-vrow-headlabel">{{ msg.versions.colValidity }}</div>
50
- <div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">
51
- {{ msg.versions.colPricing }}
52
- </div>
53
- <div class="pc-vrow-cell pc-vrow-headlabel pc-vrow-cell--right">
54
- {{ msg.versions.colImpact }}
55
- </div>
56
- <div class="pc-vrow-note pc-vrow-headlabel">{{ msg.versions.colChangeNoteShort }}</div>
57
- <div class="pc-vrow-actions" />
58
- </div>
59
- <div v-if="versions.length === 0" class="pc-empty">
60
- {{ msg.versions.empty }}
61
- </div>
62
- <div
63
- v-for="row in sortedVersions"
64
- :key="row.id"
65
- :class="['pc-vrow', row.publishedAt === null ? 'pc-vrow--active' : '']"
66
- >
67
- <div class="pc-vrow-version">
68
- <span class="pc-vrow-vlabel">v{{ row.version }}</span>
69
- <span :class="['pc-chip pc-chip--dot', statusChipClass(row)]">{{
70
- statusLabel(row)
71
- }}</span>
72
- </div>
73
- <div class="pc-vrow-cell">
74
- <div class="pc-vrow-validity">
75
- {{ row.validFrom ? row.validFrom.slice(0, 10) : '—' }}
76
- <span class="pc-vrow-arrow">→</span>
77
- <template v-if="row.validUntil">{{ row.validUntil.slice(0, 10) }}</template>
78
- <span v-else class="pc-vrow-inf">∞</span>
79
- </div>
80
- <div class="pc-vrow-sub">{{ updatedLabel(row) }}</div>
81
- </div>
82
- <div class="pc-vrow-cell pc-vrow-cell--right">
83
- <div class="pc-vrow-price">
84
- {{ formatMoney(row.monthlyNet)
85
- }}<span class="pc-vrow-price-unit">{{ msg.versions.perMonthUnit }}</span>
86
- </div>
87
- <div class="pc-vrow-sub">
88
- {{ formatMoney(row.yearlyNet) }} {{ msg.versions.perYearUnit }}
89
- </div>
90
- </div>
91
- <div class="pc-vrow-cell pc-vrow-cell--right">
92
- <div
93
- class="pc-vrow-impact"
94
- :class="(impactByVersion[row.version] ?? 0) > 0 ? '' : 'pc-vrow-impact--zero'"
95
- >
96
- {{ impactByVersion[row.version] ?? 0 }}
97
- </div>
98
- <div class="pc-vrow-sub">{{ msg.versions.tenants }}</div>
99
- </div>
100
- <div class="pc-vrow-note" :title="row.changeNote ?? ''">
101
- {{ row.changeNote || '—' }}
102
- </div>
103
- <div class="pc-vrow-actions">
104
- <button
105
- v-if="row.publishedAt === null"
106
- class="pc-btn pc-btn--sm pc-btn--primary"
107
- type="button"
108
- @click="emit('publish', row)"
109
- >
110
- <svg
111
- width="12"
112
- height="12"
113
- viewBox="0 0 24 24"
114
- fill="none"
115
- stroke="currentColor"
116
- stroke-width="2"
117
- >
118
- <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
119
- </svg>
120
- <span>{{ msg.versions.publish }}</span>
121
- </button>
122
- <button
123
- v-if="editabilityOf(row).editable"
124
- class="pc-btn pc-btn--sm"
125
- type="button"
126
- :title="
127
- editabilityOf(row).reason === 'pre-active'
128
- ? msg.versions.preActiveTitle
129
- : msg.versions.editDraftTitle
130
- "
131
- @click="emit('editDraft', row)"
132
- >
133
- <svg
134
- width="12"
135
- height="12"
136
- viewBox="0 0 24 24"
137
- fill="none"
138
- stroke="currentColor"
139
- stroke-width="2"
140
- >
141
- <path d="M12 20h9M16.5 3.5a2.12 2.12 0 113 3L7 19l-4 1 1-4 12.5-12.5z" />
142
- </svg>
143
- <span>{{ common.edit }}</span>
144
- </button>
145
- <button
146
- v-if="row.publishedAt !== null && row.supersededAt === null && draftVersion"
147
- class="pc-btn pc-btn--sm"
148
- type="button"
149
- @click="emit('viewDiff', draftVersion, row)"
150
- >
151
- <svg
152
- width="12"
153
- height="12"
154
- viewBox="0 0 24 24"
155
- fill="none"
156
- stroke="currentColor"
157
- stroke-width="2"
158
- >
159
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
160
- <circle cx="12" cy="12" r="3" />
161
- </svg>
162
- <span>{{ msg.versions.viewDiff }}</span>
163
- </button>
164
- </div>
165
- </div>
166
- </AdminSection>
167
- </template>
168
-
169
- <script setup lang="ts">
170
- import { computed } from 'vue';
171
- import AdminSection from '../admin-page/AdminSection.vue';
172
- import { isVersionEditable, type PlanVersionRow } from '@saasicat/types';
173
- import { formatMessage } from '../../client/i18n/format.js';
174
- import { formatCurrency } from '../../client/i18n/currency.js';
175
- import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
176
- import type { TimelineSegment } from './types';
177
-
178
- const props = defineProps<{
179
- versions: PlanVersionRow[];
180
- sortedVersions: PlanVersionRow[];
181
- timelineSegments: TimelineSegment[];
182
- timelineAxis: string[];
183
- hasOpenDraft: boolean;
184
- nextDraftVersion: number;
185
- impactByVersion: Record<number, number>;
186
- draftVersion: PlanVersionRow | null;
187
- }>();
188
-
189
- const emit = defineEmits<{
190
- (e: 'createDraft'): void;
191
- (e: 'publish', version: PlanVersionRow): void;
192
- (e: 'editDraft', version: PlanVersionRow): void;
193
- (e: 'viewDiff', from: PlanVersionRow, to: PlanVersionRow): void;
194
- }>();
195
-
196
- const msg = useSaMessages('planDetail');
197
- const { locale } = useSuperAdminI18n();
198
- const common = useSaMessages('common');
199
-
200
- const newDraftButtonLabel = computed(() =>
201
- formatMessage(msg.value.versions.newDraftButton, { version: props.nextDraftVersion }),
202
- );
203
-
204
- function editabilityOf(version: PlanVersionRow): {
205
- editable: boolean;
206
- reason: 'draft' | 'pre-active' | null;
207
- } {
208
- return isVersionEditable(version);
209
- }
210
-
211
- function statusChipClass(row: PlanVersionRow): string {
212
- if (row.publishedAt === null) return 'pc-chip--draft';
213
- if (row.supersededAt !== null) return 'pc-chip--supersed';
214
- return 'pc-chip--live';
215
- }
216
-
217
- function statusLabel(row: PlanVersionRow): string {
218
- if (row.publishedAt === null) return 'draft';
219
- if (row.supersededAt !== null) return 'superseded';
220
- return 'live';
221
- }
222
-
223
- function formatMoney(raw: string | number): string {
224
- const num = typeof raw === 'string' ? Number(raw) : raw;
225
- if (!Number.isFinite(num)) return String(raw);
226
- return formatCurrency(num, locale.value);
227
- }
228
-
229
- function updatedLabel(row: PlanVersionRow): string {
230
- const iso =
231
- row.publishedAt ??
232
- (row as unknown as { updatedAt?: string }).updatedAt ??
233
- (row as unknown as { createdAt?: string }).createdAt ??
234
- '';
235
- if (!iso) return '';
236
- const d = new Date(iso);
237
- if (Number.isNaN(d.getTime())) return '';
238
- const diffMs = Date.now() - d.getTime();
239
- const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
240
- if (days <= 0) return msg.value.versions.updatedToday;
241
- if (days < 30) return formatMessage(msg.value.versions.updatedDaysAgo, { days });
242
- if (days < 365) {
243
- return formatMessage(msg.value.versions.updatedMonthsAgo, {
244
- months: Math.floor(days / 30),
245
- });
246
- }
247
- return formatMessage(msg.value.versions.updatedYearsAgo, { years: Math.floor(days / 365) });
248
- }
249
- </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>