@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,85 +1,66 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
:loading="loading"
|
|
65
|
-
hide-pagination
|
|
66
|
-
>
|
|
67
|
-
<template #body-cell-status="{ row }">
|
|
68
|
-
<q-td>
|
|
69
|
-
<q-badge
|
|
70
|
-
:color="row.isActive ? 'positive' : 'grey'"
|
|
71
|
-
:label="row.isActive ? msg.statusActive : msg.statusDeactivated"
|
|
72
|
-
/>
|
|
73
|
-
<q-badge
|
|
74
|
-
v-if="row.invitationStatus === 'PENDING'"
|
|
75
|
-
color="amber-7"
|
|
76
|
-
:label="msg.badgePending"
|
|
77
|
-
class="q-ml-xs"
|
|
78
|
-
/>
|
|
79
|
-
</q-td>
|
|
80
|
-
</template>
|
|
81
|
-
<template #body-cell-actions="{ row }">
|
|
82
|
-
<q-td>
|
|
2
|
+
<AdminPage class="sa-users">
|
|
3
|
+
<AdminHero :title="resolvedTitle" :subtitle="subtitle" />
|
|
4
|
+
|
|
5
|
+
<AdminBody>
|
|
6
|
+
<AdminSection>
|
|
7
|
+
<AdminStatistics :label="resolvedTitle">
|
|
8
|
+
<AdminKpi
|
|
9
|
+
v-for="tile in statTiles"
|
|
10
|
+
:key="tile.id"
|
|
11
|
+
:label="tile.label"
|
|
12
|
+
:value="tile.count"
|
|
13
|
+
:tone="tile.tone"
|
|
14
|
+
:selected="statusFilter === tile.id"
|
|
15
|
+
:action="() => (statusFilter = tile.id)"
|
|
16
|
+
/>
|
|
17
|
+
</AdminStatistics>
|
|
18
|
+
</AdminSection>
|
|
19
|
+
|
|
20
|
+
<AdminSection :title="common.results" class="sa-users__card">
|
|
21
|
+
<AdminFilters class="q-mb-lg">
|
|
22
|
+
<q-input
|
|
23
|
+
v-model="filter.q"
|
|
24
|
+
outlined
|
|
25
|
+
dense
|
|
26
|
+
:label="msg.filterQuery"
|
|
27
|
+
clearable
|
|
28
|
+
debounce="250"
|
|
29
|
+
@update:model-value="reload"
|
|
30
|
+
/>
|
|
31
|
+
<q-input
|
|
32
|
+
v-model="filter.tenant"
|
|
33
|
+
outlined
|
|
34
|
+
dense
|
|
35
|
+
:label="msg.filterTenant"
|
|
36
|
+
clearable
|
|
37
|
+
debounce="250"
|
|
38
|
+
@update:model-value="reload"
|
|
39
|
+
/>
|
|
40
|
+
<slot name="filters-extra" />
|
|
41
|
+
</AdminFilters>
|
|
42
|
+
|
|
43
|
+
<AdminTable
|
|
44
|
+
:rows="filteredRows"
|
|
45
|
+
:columns="effectiveColumns"
|
|
46
|
+
:loading="loading"
|
|
47
|
+
storage-key="users"
|
|
48
|
+
>
|
|
49
|
+
<template #body-cell-status="{ row }">
|
|
50
|
+
<q-td>
|
|
51
|
+
<q-badge
|
|
52
|
+
:color="row.isActive ? 'positive' : 'grey'"
|
|
53
|
+
:label="row.isActive ? msg.statusActive : msg.statusDeactivated"
|
|
54
|
+
/>
|
|
55
|
+
<q-badge
|
|
56
|
+
v-if="row.invitationStatus === 'PENDING'"
|
|
57
|
+
color="amber-7"
|
|
58
|
+
:label="msg.badgePending"
|
|
59
|
+
class="q-ml-xs"
|
|
60
|
+
/>
|
|
61
|
+
</q-td>
|
|
62
|
+
</template>
|
|
63
|
+
<template #row-actions="{ row }">
|
|
83
64
|
<slot name="row-actions" :row="row">
|
|
84
65
|
<q-btn
|
|
85
66
|
v-for="action in visibleActions(row)"
|
|
@@ -92,27 +73,36 @@
|
|
|
92
73
|
@click="action.handler(row)"
|
|
93
74
|
/>
|
|
94
75
|
</slot>
|
|
95
|
-
</
|
|
96
|
-
</
|
|
97
|
-
</
|
|
98
|
-
</
|
|
76
|
+
</template>
|
|
77
|
+
</AdminTable>
|
|
78
|
+
</AdminSection>
|
|
79
|
+
</AdminBody>
|
|
99
80
|
|
|
100
81
|
<MfaPromptDialog
|
|
101
82
|
v-if="needsMfaDialog"
|
|
102
|
-
:model-value="
|
|
103
|
-
:description="
|
|
104
|
-
:error="
|
|
83
|
+
:model-value="mfa.show.value"
|
|
84
|
+
:description="mfa.description.value"
|
|
85
|
+
:error="mfa.error.value"
|
|
105
86
|
:setup-hint="mfaSetupHint"
|
|
106
|
-
@update:model-value="
|
|
107
|
-
@confirm="
|
|
87
|
+
@update:model-value="mfa.onVisibility"
|
|
88
|
+
@confirm="mfa.onConfirm"
|
|
108
89
|
/>
|
|
109
|
-
</
|
|
90
|
+
</AdminPage>
|
|
110
91
|
</template>
|
|
111
92
|
|
|
112
93
|
<script setup lang="ts">
|
|
94
|
+
import AdminTable from '../components/admin-page/AdminTable.vue';
|
|
113
95
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
96
|
+
import { useMfaPrompt } from '../vue/use-mfa-prompt.js';
|
|
114
97
|
import { useQuasar } from 'quasar';
|
|
115
98
|
import { useSuperAdminNotify } from '../quasar/notify.js';
|
|
99
|
+
import AdminBody from '../components/admin-page/AdminBody.vue';
|
|
100
|
+
import AdminFilters from '../components/admin-page/AdminFilters.vue';
|
|
101
|
+
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
102
|
+
import AdminKpi from '../components/admin-page/AdminKpi.vue';
|
|
103
|
+
import AdminPage from '../components/admin-page/AdminPage.vue';
|
|
104
|
+
import AdminSection from '../components/admin-page/AdminSection.vue';
|
|
105
|
+
import AdminStatistics from '../components/admin-page/AdminStatistics.vue';
|
|
116
106
|
import MfaPromptDialog from '../components/MfaPromptDialog.vue';
|
|
117
107
|
import { formatMessage } from '../client/i18n/format.js';
|
|
118
108
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
@@ -191,40 +181,13 @@ const rows = ref<UserRow[]>([]);
|
|
|
191
181
|
const loading = ref(false);
|
|
192
182
|
const filter = reactive({ q: '', tenant: '' });
|
|
193
183
|
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
const showMfa = ref(false);
|
|
198
|
-
const mfaError = ref('');
|
|
199
|
-
const mfaDescription = ref('');
|
|
200
|
-
let pendingMfaResolve: ((code: string | null) => void) | null = null;
|
|
184
|
+
// Per-flow MFA for reset/deactivate: the action handler awaits `mfa.prompt`,
|
|
185
|
+
// which resolves with the code or with null when the user closes the dialog.
|
|
186
|
+
const mfa = useMfaPrompt();
|
|
201
187
|
const needsMfaDialog = computed(
|
|
202
188
|
() => props.requireMfaForResetPassword || props.requireMfaForDeactivate,
|
|
203
189
|
);
|
|
204
190
|
|
|
205
|
-
function promptMfa(description: string): Promise<string | null> {
|
|
206
|
-
return new Promise((resolve) => {
|
|
207
|
-
mfaDescription.value = description;
|
|
208
|
-
mfaError.value = '';
|
|
209
|
-
showMfa.value = true;
|
|
210
|
-
pendingMfaResolve = (code) => {
|
|
211
|
-
pendingMfaResolve = null;
|
|
212
|
-
resolve(code);
|
|
213
|
-
};
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function onMfaConfirm(code: string): void {
|
|
218
|
-
pendingMfaResolve?.(code);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function onMfaDialogVisibility(open: boolean): void {
|
|
222
|
-
showMfa.value = open;
|
|
223
|
-
if (!open && pendingMfaResolve) {
|
|
224
|
-
pendingMfaResolve(null);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
191
|
// Stat-pill filter (analogous to plan simulation users.jsx):
|
|
229
192
|
// all | active | blocked | never-logged-in | super-admin.
|
|
230
193
|
type StatusFilter = 'all' | 'active' | 'blocked' | 'never' | 'super';
|
|
@@ -346,14 +309,6 @@ const mergedActions = computed<readonly UserRowAction[]>(() => [
|
|
|
346
309
|
|
|
347
310
|
const effectiveColumns = computed(() => {
|
|
348
311
|
const cols = [...baseColumns.value];
|
|
349
|
-
if (mergedActions.value.length > 0) {
|
|
350
|
-
cols.push({
|
|
351
|
-
name: 'actions',
|
|
352
|
-
label: '',
|
|
353
|
-
field: 'id' as never,
|
|
354
|
-
align: 'right' as 'left',
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
312
|
return cols;
|
|
358
313
|
});
|
|
359
314
|
|
|
@@ -407,20 +362,20 @@ async function runAction<R>(
|
|
|
407
362
|
return;
|
|
408
363
|
}
|
|
409
364
|
while (true) {
|
|
410
|
-
const code = await
|
|
365
|
+
const code = await mfa.prompt(actionLabel);
|
|
411
366
|
if (code === null) return;
|
|
412
367
|
try {
|
|
413
368
|
const result = await invoke(code);
|
|
414
|
-
|
|
369
|
+
mfa.show.value = false;
|
|
415
370
|
onSuccess(result);
|
|
416
371
|
return;
|
|
417
372
|
} catch (err) {
|
|
418
373
|
const status = (err as { response?: { status?: number } })?.response?.status;
|
|
419
374
|
if (status === 401) {
|
|
420
|
-
|
|
375
|
+
mfa.error.value = shell.value.mfa.invalidCode;
|
|
421
376
|
continue;
|
|
422
377
|
}
|
|
423
|
-
|
|
378
|
+
mfa.show.value = false;
|
|
424
379
|
notify('negative', errMsg(err));
|
|
425
380
|
return;
|
|
426
381
|
}
|
|
@@ -496,27 +451,4 @@ function onDeactivateClick(row: UserRow): void {
|
|
|
496
451
|
}
|
|
497
452
|
</script>
|
|
498
453
|
|
|
499
|
-
<style scoped>
|
|
500
|
-
.sa-users {
|
|
501
|
-
min-height: calc(100vh - 56px);
|
|
502
|
-
background: var(--sa-bg-app, #f1f5f9);
|
|
503
|
-
padding: 20px 28px 28px;
|
|
504
|
-
}
|
|
505
|
-
.sa-users__filter {
|
|
506
|
-
display: flex;
|
|
507
|
-
gap: 10px;
|
|
508
|
-
margin-bottom: 12px;
|
|
509
|
-
align-items: center;
|
|
510
|
-
flex-wrap: wrap;
|
|
511
|
-
}
|
|
512
|
-
.sa-users__filter > * {
|
|
513
|
-
flex: 1;
|
|
514
|
-
min-width: 200px;
|
|
515
|
-
}
|
|
516
|
-
.sa-users__card {
|
|
517
|
-
background: #fff;
|
|
518
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
519
|
-
border-radius: 12px;
|
|
520
|
-
overflow: hidden;
|
|
521
|
-
}
|
|
522
|
-
</style>
|
|
454
|
+
<style scoped></style>
|
|
@@ -12,13 +12,16 @@
|
|
|
12
12
|
<div class="sa-bd-card__titlerow">
|
|
13
13
|
<span class="sa-bd-card__key">{{ bundle.bundleKey }}</span>
|
|
14
14
|
<span
|
|
15
|
-
class="sa-chip"
|
|
16
|
-
:class="`sa-chip--${statusClass(bundle)}`"
|
|
15
|
+
class="sa-bundle-chip"
|
|
16
|
+
:class="`sa-bundle-chip--${statusClass(bundle)}`"
|
|
17
17
|
:title="statusTooltip(bundle)"
|
|
18
18
|
>
|
|
19
19
|
{{ statusLabel(bundle) }}
|
|
20
20
|
</span>
|
|
21
|
-
<span
|
|
21
|
+
<span
|
|
22
|
+
v-if="i18nLocaleCount(bundle) > 0"
|
|
23
|
+
class="sa-bundle-chip sa-bundle-chip--info"
|
|
24
|
+
>
|
|
22
25
|
{{ translationCount(bundle) }}
|
|
23
26
|
</span>
|
|
24
27
|
</div>
|
|
@@ -93,34 +96,34 @@ function translationCount(bundle: BundleRow): string {
|
|
|
93
96
|
|
|
94
97
|
<style scoped>
|
|
95
98
|
/* Status chips co-located with the markup (analogous to PlanList .pl-chip*).
|
|
96
|
-
Deliberately scoped: the identically named global .sa-chip rule in
|
|
97
|
-
DiscoveryPage.vue would otherwise override .sa-chip--live (same specificity,
|
|
99
|
+
Deliberately scoped: the identically named global .sa-bundle-chip rule in
|
|
100
|
+
DiscoveryPage.vue would otherwise override .sa-bundle-chip--live (same specificity,
|
|
98
101
|
later source order) and render the "Live" tag grey instead of green. */
|
|
99
|
-
.sa-chip {
|
|
102
|
+
.sa-bundle-chip {
|
|
100
103
|
font-size: 10px;
|
|
101
104
|
font-weight: 600;
|
|
102
105
|
padding: 2px 7px;
|
|
103
106
|
border-radius: 6px;
|
|
104
|
-
background:
|
|
105
|
-
color:
|
|
107
|
+
background: var(--sa-border-soft);
|
|
108
|
+
color: var(--sa-muted-dark);
|
|
106
109
|
}
|
|
107
|
-
.sa-chip--info {
|
|
108
|
-
background:
|
|
109
|
-
color:
|
|
110
|
+
.sa-bundle-chip--info {
|
|
111
|
+
background: var(--sa-primary-50);
|
|
112
|
+
color: var(--sa-primary-strong);
|
|
110
113
|
}
|
|
111
|
-
.sa-chip--live {
|
|
114
|
+
.sa-bundle-chip--live {
|
|
112
115
|
background: #dcfce7;
|
|
113
116
|
color: #166534;
|
|
114
117
|
}
|
|
115
|
-
.sa-chip--draft {
|
|
118
|
+
.sa-bundle-chip--draft {
|
|
116
119
|
background: #fef3c7;
|
|
117
120
|
color: #92400e;
|
|
118
121
|
}
|
|
119
|
-
.sa-chip--supersed {
|
|
120
|
-
background:
|
|
121
|
-
color:
|
|
122
|
+
.sa-bundle-chip--supersed {
|
|
123
|
+
background: var(--sa-border);
|
|
124
|
+
color: var(--sa-muted);
|
|
122
125
|
}
|
|
123
|
-
.sa-chip--scheduled {
|
|
126
|
+
.sa-bundle-chip--scheduled {
|
|
124
127
|
background: #fef3c7;
|
|
125
128
|
color: #92400e;
|
|
126
129
|
}
|
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
</div>
|
|
13
|
-
<div class="sa-bundles__kpi">
|
|
14
|
-
<div class="sa-bundles__kpi-label">{{ msg.kpis.drafts }}</div>
|
|
15
|
-
<div class="sa-bundles__kpi-value">{{ totalDraftVersions }}</div>
|
|
16
|
-
<div class="sa-bundles__kpi-sub">{{ draftsSub }}</div>
|
|
17
|
-
</div>
|
|
18
|
-
<div class="sa-bundles__kpi">
|
|
19
|
-
<div class="sa-bundles__kpi-label">{{ msg.kpis.translated }}</div>
|
|
20
|
-
<div class="sa-bundles__kpi-value">{{ translatedCount }}</div>
|
|
21
|
-
<div class="sa-bundles__kpi-sub">{{ translatedSub }}</div>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
2
|
+
<AdminStatistics :columns="4">
|
|
3
|
+
<AdminKpi :label="msg.kpis.total" :value="bundlesTotal" :sub="totalSub" />
|
|
4
|
+
<AdminKpi
|
|
5
|
+
:label="msg.kpis.scheduled"
|
|
6
|
+
:value="totalScheduledVersions"
|
|
7
|
+
:sub="msg.kpis.scheduledSub"
|
|
8
|
+
/>
|
|
9
|
+
<AdminKpi :label="msg.kpis.drafts" :value="totalDraftVersions" :sub="draftsSub" />
|
|
10
|
+
<AdminKpi :label="msg.kpis.translated" :value="translatedCount" :sub="translatedSub" />
|
|
11
|
+
</AdminStatistics>
|
|
24
12
|
</template>
|
|
25
13
|
|
|
26
14
|
<script setup lang="ts">
|
|
27
15
|
import { computed } from 'vue';
|
|
28
16
|
|
|
17
|
+
import AdminKpi from '../../components/admin-page/AdminKpi.vue';
|
|
18
|
+
import AdminStatistics from '../../components/admin-page/AdminStatistics.vue';
|
|
19
|
+
|
|
29
20
|
import { formatMessage } from '../../client/i18n/format.js';
|
|
30
21
|
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
31
22
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="sa-bundles__toolbar">
|
|
3
|
+
<q-select
|
|
4
|
+
v-if="locales.length > 1"
|
|
5
|
+
:model-value="displayLocale"
|
|
6
|
+
:options="localeOptions"
|
|
7
|
+
outlined
|
|
8
|
+
dense
|
|
9
|
+
emit-value
|
|
10
|
+
map-options
|
|
11
|
+
class="sa-bundles__locale"
|
|
12
|
+
:label="msg.header.displayLocale"
|
|
13
|
+
@update:model-value="(value) => emit('update:displayLocale', String(value))"
|
|
14
|
+
>
|
|
15
|
+
<template #prepend><q-icon name="translate" size="18px" /></template>
|
|
16
|
+
</q-select>
|
|
17
|
+
<button class="sa-btn sa-btn--primary" type="button" @click="emit('create')">
|
|
18
|
+
<q-icon name="add" size="16px" />
|
|
19
|
+
<span>{{ msg.header.newBundle }}</span>
|
|
20
|
+
</button>
|
|
21
|
+
<AdminRefreshBtn :loading="loading" @refresh="emit('refresh')" />
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
import { computed } from 'vue';
|
|
27
|
+
|
|
28
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
29
|
+
import AdminRefreshBtn from '../../components/admin-page/AdminRefreshBtn.vue';
|
|
30
|
+
|
|
31
|
+
// The language selector controls which locale feature/quota labels are resolved
|
|
32
|
+
// on the create form and in the detail view (fallback locale → DE → key). The default is the
|
|
33
|
+
// default locale (DE); further options come from the active project locales.
|
|
34
|
+
|
|
35
|
+
const props = defineProps<{
|
|
36
|
+
loading: boolean;
|
|
37
|
+
displayLocale: string;
|
|
38
|
+
locales: string[];
|
|
39
|
+
}>();
|
|
40
|
+
|
|
41
|
+
const emit = defineEmits<{
|
|
42
|
+
create: [];
|
|
43
|
+
refresh: [];
|
|
44
|
+
'update:displayLocale': [locale: string];
|
|
45
|
+
}>();
|
|
46
|
+
|
|
47
|
+
const msg = useSaMessages('bundles');
|
|
48
|
+
|
|
49
|
+
const localeOptions = computed(() =>
|
|
50
|
+
props.locales.map((locale) => ({ label: locale.toUpperCase(), value: locale })),
|
|
51
|
+
);
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style scoped>
|
|
55
|
+
.sa-bundles__toolbar {
|
|
56
|
+
display: flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 12px;
|
|
59
|
+
}
|
|
60
|
+
.sa-bundles__locale {
|
|
61
|
+
min-width: 150px;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -171,13 +171,13 @@ function copyFromDefault(locale: string): void {
|
|
|
171
171
|
gap: 12px;
|
|
172
172
|
}
|
|
173
173
|
.sa-trans-lang {
|
|
174
|
-
border: 1px solid
|
|
174
|
+
border: 1px solid var(--sa-border);
|
|
175
175
|
border-radius: 10px;
|
|
176
176
|
background: #fff;
|
|
177
177
|
padding: 10px 12px;
|
|
178
178
|
}
|
|
179
179
|
.sa-trans-lang--source {
|
|
180
|
-
background:
|
|
180
|
+
background: var(--sa-bg-surface-2);
|
|
181
181
|
border-color: #cbd5e1;
|
|
182
182
|
}
|
|
183
183
|
.sa-trans-lang__head {
|
|
@@ -189,7 +189,7 @@ function copyFromDefault(locale: string): void {
|
|
|
189
189
|
.sa-trans-lang__code {
|
|
190
190
|
font-size: 10px;
|
|
191
191
|
font-weight: 700;
|
|
192
|
-
background:
|
|
192
|
+
background: var(--sa-heading);
|
|
193
193
|
color: #fff;
|
|
194
194
|
padding: 2px 6px;
|
|
195
195
|
border-radius: 5px;
|
|
@@ -205,13 +205,13 @@ function copyFromDefault(locale: string): void {
|
|
|
205
205
|
font-weight: 700;
|
|
206
206
|
text-transform: uppercase;
|
|
207
207
|
letter-spacing: 0.5px;
|
|
208
|
-
color:
|
|
209
|
-
background:
|
|
208
|
+
color: var(--sa-muted-dark);
|
|
209
|
+
background: var(--sa-border);
|
|
210
210
|
padding: 2px 6px;
|
|
211
211
|
border-radius: 5px;
|
|
212
212
|
}
|
|
213
213
|
.sa-trans-lang__copy {
|
|
214
|
-
color:
|
|
214
|
+
color: var(--sa-muted-dark);
|
|
215
215
|
}
|
|
216
216
|
.sa-trans-fields {
|
|
217
217
|
display: flex;
|
|
@@ -228,12 +228,12 @@ function copyFromDefault(locale: string): void {
|
|
|
228
228
|
font-weight: 600;
|
|
229
229
|
text-transform: uppercase;
|
|
230
230
|
letter-spacing: 0.3px;
|
|
231
|
-
color:
|
|
231
|
+
color: var(--sa-muted-light);
|
|
232
232
|
}
|
|
233
233
|
.sa-trans-locked {
|
|
234
234
|
font-size: 13px;
|
|
235
235
|
color: #334155;
|
|
236
|
-
background:
|
|
236
|
+
background: var(--sa-border-soft);
|
|
237
237
|
border: 1px dashed #cbd5e1;
|
|
238
238
|
border-radius: 6px;
|
|
239
239
|
padding: 6px 10px;
|
|
@@ -243,7 +243,7 @@ function copyFromDefault(locale: string): void {
|
|
|
243
243
|
}
|
|
244
244
|
.sa-trans-locked__hint {
|
|
245
245
|
font-size: 10px;
|
|
246
|
-
color:
|
|
246
|
+
color: var(--sa-muted-light);
|
|
247
247
|
text-transform: uppercase;
|
|
248
248
|
}
|
|
249
249
|
.sa-trans__empty {
|
|
@@ -251,7 +251,7 @@ function copyFromDefault(locale: string): void {
|
|
|
251
251
|
padding: 16px;
|
|
252
252
|
text-align: center;
|
|
253
253
|
font-size: 12px;
|
|
254
|
-
color:
|
|
254
|
+
color: var(--sa-muted-light);
|
|
255
255
|
border: 1px dashed #cbd5e1;
|
|
256
256
|
border-radius: 10px;
|
|
257
257
|
}
|
|
@@ -91,7 +91,7 @@ function isNew(cap: CapabilityCatalogEntryRow): boolean {
|
|
|
91
91
|
gap: 10px;
|
|
92
92
|
align-items: flex-start;
|
|
93
93
|
background: #fff;
|
|
94
|
-
border: 1px solid
|
|
94
|
+
border: 1px solid var(--sa-border);
|
|
95
95
|
border-radius: 8px;
|
|
96
96
|
padding: 8px 10px;
|
|
97
97
|
}
|
|
@@ -142,8 +142,8 @@ function isNew(cap: CapabilityCatalogEntryRow): boolean {
|
|
|
142
142
|
color: #b91c1c;
|
|
143
143
|
}
|
|
144
144
|
.sa-caps-row__flag--gone {
|
|
145
|
-
background:
|
|
146
|
-
color:
|
|
145
|
+
background: var(--sa-border);
|
|
146
|
+
color: var(--sa-muted);
|
|
147
147
|
}
|
|
148
148
|
.sa-caps-row.gone {
|
|
149
149
|
opacity: 0.6;
|
|
@@ -153,7 +153,7 @@ function isNew(cap: CapabilityCatalogEntryRow): boolean {
|
|
|
153
153
|
gap: 12px;
|
|
154
154
|
flex-wrap: wrap;
|
|
155
155
|
font-size: 11px;
|
|
156
|
-
color:
|
|
156
|
+
color: var(--sa-muted-dark);
|
|
157
157
|
margin-top: 1px;
|
|
158
158
|
}
|
|
159
159
|
.sa-caps-row__meta code {
|
|
@@ -165,7 +165,7 @@ function isNew(cap: CapabilityCatalogEntryRow): boolean {
|
|
|
165
165
|
gap: 6px;
|
|
166
166
|
font-size: 11px;
|
|
167
167
|
color: #b45309;
|
|
168
|
-
background:
|
|
168
|
+
background: var(--sa-warning-bg);
|
|
169
169
|
border: 1px dashed #fde68a;
|
|
170
170
|
border-radius: 8px;
|
|
171
171
|
padding: 10px 12px;
|