@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.
- package/dist/{catalog-JXdKdIqG.d.cts → catalog-DJcfm5N2.d.cts} +21 -7
- package/dist/{catalog-JXdKdIqG.d.ts → catalog-DJcfm5N2.d.ts} +21 -7
- package/dist/{chunk-DPTIOQXX.js → chunk-F7NRM6KI.js} +56 -28
- package/dist/{chunk-RVG6LQHL.js → chunk-LY52T4N4.js} +21 -2
- package/dist/{chunk-ORX3ZG47.js → chunk-XMMFN2WJ.js} +74 -3
- package/dist/client/index.cjs +134 -29
- package/dist/client/index.d.cts +53 -3
- package/dist/client/index.d.ts +53 -3
- package/dist/client/index.js +14 -2
- package/dist/index.cjs +321 -162
- package/dist/index.d.cts +30 -8
- package/dist/index.d.ts +30 -8
- package/dist/index.js +176 -133
- package/dist/quasar/index.cjs +78 -29
- package/dist/quasar/index.d.cts +2 -2
- package/dist/quasar/index.d.ts +2 -2
- package/dist/quasar/index.js +6 -2
- package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
- package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
- package/dist/{use-super-admin-i18n-BlqcD0N7.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
- package/dist/{use-super-admin-i18n-1nCOkvgi.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
- package/package.json +18 -8
- package/src/client/i18n/messages/bundles.ts +0 -2
- package/src/client/i18n/messages/common.ts +24 -0
- package/src/client/i18n/messages/discovery.ts +2 -2
- package/src/client/i18n/messages/marketing.ts +2 -4
- package/src/client/i18n/messages/plan-detail.ts +0 -4
- package/src/client/i18n/messages/plans.ts +20 -8
- package/src/client/i18n/messages/tenants.ts +0 -2
- package/src/client/index.ts +1 -0
- package/src/client/manifest-loader.ts +1 -1
- package/src/client/resolve-plans.ts +144 -0
- package/src/components/BundleVersionPublishDialog.vue +6 -6
- package/src/components/KvBlock.vue +4 -4
- package/src/components/MarketingPromotionsTab.vue +34 -48
- package/src/components/MfaPromptDialog.vue +8 -2
- package/src/components/TenantActionConfirmDialog.vue +4 -4
- package/src/components/VersionDiffPreview.vue +7 -7
- package/src/components/admin-page/AdminBody.vue +40 -0
- package/src/components/admin-page/AdminFilters.vue +12 -0
- package/src/components/admin-page/AdminHero.vue +49 -0
- package/src/components/admin-page/AdminKpi.vue +84 -0
- package/src/components/admin-page/AdminPage.vue +16 -0
- package/src/components/admin-page/AdminPaginator.vue +195 -0
- package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
- package/src/components/admin-page/AdminSection.vue +53 -0
- package/src/components/admin-page/AdminStatistics.vue +40 -0
- package/src/components/admin-page/AdminTable.vue +175 -0
- package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
- package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
- package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
- package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
- package/src/components/dialogs/PilotCreateDialog.vue +12 -12
- package/src/components/dialogs/PilotEditDialog.vue +7 -7
- package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
- package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
- package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
- package/src/components/plan/PlanGrid.vue +1 -1
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
- package/src/components/plan-detail/PlanAuditLog.vue +7 -8
- package/src/components/plan-detail/PlanDetail.vue +95 -292
- package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
- package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
- package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
- package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
- package/src/components/plan-list/PlanList.vue +206 -457
- package/src/components/plan-matrix/PlanMatrix.vue +66 -183
- package/src/components/plan-review/PlanReview.vue +191 -275
- package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
- package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
- package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
- package/src/index.ts +1 -0
- package/src/pages-standard/AdminLayout.vue +38 -12
- package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
- package/src/pages-standard/AuditPage.vue +65 -109
- package/src/pages-standard/BundlesPage.vue +146 -179
- package/src/pages-standard/DashboardPage.vue +59 -154
- package/src/pages-standard/DiscoveryPage.vue +172 -227
- package/src/pages-standard/EmailHistoryPage.vue +118 -159
- package/src/pages-standard/MarketingCatalogPage.vue +399 -440
- package/src/pages-standard/PilotsPage.vue +69 -117
- package/src/pages-standard/PlansPage.vue +240 -112
- package/src/pages-standard/PlatformEmailPage.vue +56 -91
- package/src/pages-standard/PromoCodeDetailPage.vue +49 -74
- package/src/pages-standard/PromoCodesPage.vue +95 -128
- package/src/pages-standard/SubscriptionsPage.vue +19 -52
- package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
- package/src/pages-standard/SuperAdminSetupWizard.vue +22 -18
- package/src/pages-standard/TenantDetailPage.vue +100 -191
- package/src/pages-standard/TenantsPage.vue +190 -295
- package/src/pages-standard/UsersPage.vue +89 -157
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
- package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
- package/src/pages-standard/bundles-page/BundlesToolbar.vue +63 -0
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
- package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +111 -73
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +43 -62
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
- package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
- package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
- package/src/pages-standard/sa-theme.css +552 -86
- package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
- package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
- package/src/pages-standard/tenant-detail/TenantUsers.vue +15 -0
- package/src/pages-standard/tenant-detail/types.ts +38 -0
- package/src/pages-standard/tenants/StatusPill.vue +2 -2
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
- package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
- package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
- package/src/pages-tenant/TenantPlanSection.vue +12 -20
- package/src/quasar/create-super-admin-app.ts +4 -0
- package/src/vue/entitlement-provider.ts +0 -1
- package/src/vue/feature-router-guard.ts +0 -1
- package/src/vue/super-admin-context.ts +106 -1
- package/src/vue/use-api-list.ts +2 -1
- package/src/vue/use-mfa-prompt.ts +72 -0
- package/src/vue/use-pagination.ts +63 -0
- package/src/vue/use-plans.ts +1 -2
- package/src/vue/use-sign-out.ts +90 -0
- package/src/vue/use-tenant-billing.ts +1 -1
- package/src/vue/use-tenant-manifest.ts +0 -1
- package/src/components/KpiCard.vue +0 -53
- package/src/components/plan-cockpit/PlanCockpit.vue +0 -1012
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -100
- package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
- package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
- package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
- package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -251
- package/src/components/plan-cockpit/types.ts +0 -72
- package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
- package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
- 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>
|