@saasicat/ui-vue 0.4.0 → 0.6.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/README.md +64 -4
- package/dist/chunk-A3W2N26K.js +714 -0
- package/dist/chunk-QZOSDCNP.js +98 -0
- package/dist/chunk-X5SQ5HMB.js +3231 -0
- package/dist/client/index.cjs +3988 -0
- package/dist/client/index.d.cts +321 -0
- package/dist/client/index.d.ts +321 -0
- package/dist/client/index.js +70 -0
- package/dist/index.cjs +4446 -1249
- package/dist/index.d.cts +297 -888
- package/dist/index.d.ts +297 -888
- package/dist/index.js +609 -1348
- package/dist/messages-7b0P8W75.d.cts +1652 -0
- package/dist/messages-7b0P8W75.d.ts +1652 -0
- package/dist/quasar/index.cjs +3340 -0
- package/dist/quasar/index.d.cts +95 -0
- package/dist/quasar/index.d.ts +95 -0
- package/dist/quasar/index.js +96 -0
- package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
- package/dist/testing-e2e/admin-pages-suite.js +14 -11
- package/dist/use-super-admin-i18n-B3v3-SWZ.d.ts +210 -0
- package/dist/use-super-admin-i18n-DMktRVEt.d.cts +210 -0
- package/package.json +23 -3
- package/src/{action-registry.ts → client/action-registry.ts} +4 -4
- package/src/{batch-column-fetcher.ts → client/batch-column-fetcher.ts} +4 -4
- package/src/{boot-loader.ts → client/boot-loader.ts} +3 -3
- package/src/client/i18n/currency.ts +42 -0
- package/src/client/i18n/define.ts +59 -0
- package/src/client/i18n/format.ts +12 -0
- package/src/client/i18n/index.ts +14 -0
- package/src/client/i18n/locale.ts +15 -0
- package/src/client/i18n/messages/audit.ts +42 -0
- package/src/client/i18n/messages/bundles.ts +459 -0
- package/src/client/i18n/messages/common.ts +147 -0
- package/src/client/i18n/messages/dashboard.ts +16 -0
- package/src/client/i18n/messages/discovery.ts +229 -0
- package/src/client/i18n/messages/email.ts +125 -0
- package/src/client/i18n/messages/marketing.ts +270 -0
- package/src/client/i18n/messages/nav.ts +72 -0
- package/src/client/i18n/messages/pilots.ts +192 -0
- package/src/client/i18n/messages/plan-detail.ts +266 -0
- package/src/client/i18n/messages/plan-editor.ts +177 -0
- package/src/client/i18n/messages/plan-versions.ts +251 -0
- package/src/client/i18n/messages/plans.ts +503 -0
- package/src/client/i18n/messages/promos.ts +202 -0
- package/src/client/i18n/messages/shell.ts +145 -0
- package/src/client/i18n/messages/tenants.ts +104 -0
- package/src/client/i18n/messages/users.ts +78 -0
- package/src/client/i18n/messages.ts +84 -0
- package/src/client/index.ts +27 -0
- package/src/{manifest-loader.ts → client/manifest-loader.ts} +4 -4
- package/src/{nav-builder.ts → client/nav-builder.ts} +44 -51
- package/src/{plan-versions-catalog.ts → client/plan-versions-catalog.ts} +42 -30
- package/src/components/BundleVersionPublishDialog.vue +35 -24
- package/src/components/FeatureGate.vue +1 -1
- package/src/components/MarketingPromotionsTab.vue +112 -59
- package/src/components/MfaPromptDialog.vue +11 -11
- package/src/components/TenantActionConfirmDialog.vue +19 -17
- package/src/components/VersionDiffPreview.vue +72 -43
- package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
- package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
- package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
- package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
- package/src/components/bundle-editor/bundle-version-status.ts +29 -29
- package/src/components/dialogs/PilotCreateDialog.vue +58 -63
- package/src/components/dialogs/PilotEditDialog.vue +40 -34
- package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
- package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
- package/src/components/dialogs/types.ts +3 -13
- package/src/components/plan/PlanCycleToggle.vue +21 -10
- package/src/components/plan/PlanGrid.vue +2 -2
- package/src/components/plan/PriceSummary.vue +1 -1
- package/src/components/plan/PromoCodeInput.vue +1 -1
- package/src/components/plan/PublicBundleGrid.vue +1 -1
- package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
- package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
- package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
- package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
- package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
- package/src/components/plan-detail/PlanAuditLog.vue +11 -6
- package/src/components/plan-detail/PlanDetail.vue +21 -23
- package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
- package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
- package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
- package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
- package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
- package/src/components/plan-list/PlanList.vue +99 -57
- package/src/components/plan-matrix/PlanMatrix.vue +85 -38
- package/src/components/plan-review/PlanReview.vue +105 -80
- package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
- package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
- package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
- package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
- package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
- package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
- package/src/index.ts +82 -79
- package/src/pages-standard/AdminLayout.vue +43 -18
- package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
- package/src/pages-standard/AuditPage.vue +58 -19
- package/src/pages-standard/BundlesPage.vue +29 -18
- package/src/pages-standard/DashboardPage.vue +31 -17
- package/src/pages-standard/DiscoveryPage.vue +40 -28
- package/src/pages-standard/EmailHistoryPage.vue +98 -69
- package/src/pages-standard/MarketingCatalogPage.vue +40 -21
- package/src/pages-standard/PilotsPage.vue +64 -52
- package/src/pages-standard/PlanVersionsPage.vue +9 -5
- package/src/pages-standard/PlansPage.vue +91 -59
- package/src/pages-standard/PlatformEmailPage.vue +120 -53
- package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
- package/src/pages-standard/PromoCodesPage.vue +51 -54
- package/src/pages-standard/SubscriptionsPage.vue +14 -9
- package/src/pages-standard/SuperAdminLoginPage.vue +19 -16
- package/src/pages-standard/SuperAdminSetupWizard.vue +53 -49
- package/src/pages-standard/TenantDetailPage.vue +54 -40
- package/src/pages-standard/TenantsPage.vue +57 -44
- package/src/pages-standard/UsersPage.vue +70 -52
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
- package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
- package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
- package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
- package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
- package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
- package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
- package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
- package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
- package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
- package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
- package/src/pages-standard/plan-versions/PlanVersionHeader.vue +42 -26
- package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +46 -27
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +8 -6
- package/src/pages-standard/plan-versions/PlanVersionsList.vue +40 -17
- package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +1 -1
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +25 -13
- package/src/pages-standard/plan-versions/format.ts +33 -17
- package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
- package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
- package/src/pages-standard/tenants/format.ts +3 -2
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +73 -38
- package/src/pages-tenant/OnboardingConfigurator.vue +3 -3
- package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
- package/src/pages-tenant/PlanChangeWizard.vue +10 -6
- package/src/pages-tenant/TenantPlanSection.vue +20 -16
- package/src/pages-tenant/default-i18n.ts +217 -1
- package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +9 -7
- package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +11 -12
- package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
- package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +1 -1
- package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +1 -1
- package/src/quasar/create-super-admin-app.ts +192 -0
- package/src/quasar/index.ts +11 -0
- package/src/quasar/notify.ts +30 -0
- package/src/testing-e2e/admin-pages-suite.ts +14 -11
- package/src/{manifest-store-factory.ts → vue/manifest-store-factory.ts} +1 -1
- package/src/{platform-loaders.ts → vue/platform-loaders.ts} +4 -4
- package/src/{project-page-host.ts → vue/project-page-host.ts} +6 -4
- package/src/{create-super-admin-app.ts → vue/super-admin-context.ts} +20 -139
- package/src/vue/ui-notify.ts +30 -0
- package/src/{use-actions.ts → vue/use-actions.ts} +1 -1
- package/src/{use-api-list.ts → vue/use-api-list.ts} +1 -1
- package/src/{use-audit-entries.ts → vue/use-audit-entries.ts} +3 -3
- package/src/{use-batch-columns.ts → vue/use-batch-columns.ts} +1 -1
- package/src/{use-bulk-publish.ts → vue/use-bulk-publish.ts} +7 -5
- package/src/{use-bundle-versions-map.ts → vue/use-bundle-versions-map.ts} +7 -5
- package/src/{use-bundles.ts → vue/use-bundles.ts} +14 -14
- package/src/{use-catalog-entries.ts → vue/use-catalog-entries.ts} +14 -11
- package/src/{use-discovery.ts → vue/use-discovery.ts} +9 -6
- package/src/{use-entitlement.ts → vue/use-entitlement.ts} +4 -4
- package/src/{use-live-plan-versions.ts → vue/use-live-plan-versions.ts} +5 -5
- package/src/{use-manifest.ts → vue/use-manifest.ts} +1 -1
- package/src/{use-marketing-projections.ts → vue/use-marketing-projections.ts} +10 -7
- package/src/{use-nav.ts → vue/use-nav.ts} +9 -3
- package/src/{use-plan-editor.ts → vue/use-plan-editor.ts} +2 -2
- package/src/{use-plan-versions.ts → vue/use-plan-versions.ts} +3 -3
- package/src/{use-plans.ts → vue/use-plans.ts} +13 -13
- package/src/{use-platform-tenant-actions.ts → vue/use-platform-tenant-actions.ts} +9 -6
- package/src/{use-promotions.ts → vue/use-promotions.ts} +10 -11
- package/src/{use-public-boot.ts → vue/use-public-boot.ts} +1 -1
- package/src/{use-subscription-draft.ts → vue/use-subscription-draft.ts} +2 -3
- package/src/{use-super-admin-context.ts → vue/use-super-admin-context.ts} +5 -5
- package/src/vue/use-super-admin-i18n.ts +82 -0
- package/src/{use-tenant-action-flow.ts → vue/use-tenant-action-flow.ts} +12 -7
- package/src/{use-tenant-billing-catalog.ts → vue/use-tenant-billing-catalog.ts} +2 -2
- package/src/{use-tenant-billing.ts → vue/use-tenant-billing.ts} +2 -2
- package/src/{use-tenant-manifest.ts → vue/use-tenant-manifest.ts} +2 -4
- package/src/{use-tenant-subscription-bundles.ts → vue/use-tenant-subscription-bundles.ts} +5 -5
- package/src/{use-tenants.ts → vue/use-tenants.ts} +3 -3
- package/dist/testing-e2e/admin-pages-suite.d.cts +0 -37
- package/dist/testing-e2e/admin-pages-suite.d.ts +0 -37
- package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
- package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
- package/src/pages-standard/BusinessTypesPage.vue +0 -662
- package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
- package/src/use-business-types.ts +0 -269
- /package/src/{http-json.ts → client/http-json.ts} +0 -0
- /package/src/{types.ts → client/types.ts} +0 -0
- /package/src/{version.ts → client/version.ts} +0 -0
- /package/src/{create-admin-routes.ts → vue/create-admin-routes.ts} +0 -0
- /package/src/{entitlement-provider.ts → vue/entitlement-provider.ts} +0 -0
- /package/src/{feature-router-guard.ts → vue/feature-router-guard.ts} +0 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
<q-banner v-if="error" class="sa-bundles__error" rounded>
|
|
13
13
|
<template #avatar><q-icon name="warning" color="negative" /></template>
|
|
14
|
-
|
|
14
|
+
{{ loadErrorText }}
|
|
15
15
|
</q-banner>
|
|
16
16
|
|
|
17
17
|
<BundlesKpis
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
rounded
|
|
55
55
|
>
|
|
56
56
|
<template #avatar><q-icon name="info" color="info" /></template>
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
{{ msg.page.emptyBefore }} <strong>{{ msg.header.newBundle }}</strong>
|
|
58
|
+
{{ msg.page.emptyAfter }}
|
|
59
59
|
</q-banner>
|
|
60
60
|
|
|
61
61
|
<BundleAccordionList
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
rounded
|
|
105
105
|
>
|
|
106
106
|
<template #avatar><q-icon name="warning" color="warning" /></template>
|
|
107
|
-
<strong>{{
|
|
107
|
+
<strong>{{ strictWarningsText }}</strong>
|
|
108
108
|
<ul class="sa-bundles__warnings-list">
|
|
109
109
|
<li v-for="(w, i) in lastWarnings" :key="i">
|
|
110
110
|
<code>{{ w.code }}</code>
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
</li>
|
|
116
116
|
</ul>
|
|
117
117
|
<template #action>
|
|
118
|
-
<q-btn flat dense label="
|
|
118
|
+
<q-btn flat dense :label="common.close" @click="lastWarnings = []" />
|
|
119
119
|
</template>
|
|
120
120
|
</q-banner>
|
|
121
121
|
|
|
@@ -169,6 +169,8 @@ import {
|
|
|
169
169
|
bundleVersionsSorted,
|
|
170
170
|
type BundleAggregateStatus,
|
|
171
171
|
} from '../components/bundle-editor/bundle-version-status';
|
|
172
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
173
|
+
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
172
174
|
import BundleAccordionList from './bundles-page/BundleAccordionList.vue';
|
|
173
175
|
import BundleDetailPanel from './bundles-page/BundleDetailPanel.vue';
|
|
174
176
|
import BundlesFilterBar from './bundles-page/BundlesFilterBar.vue';
|
|
@@ -263,6 +265,9 @@ const props = withDefaults(
|
|
|
263
265
|
},
|
|
264
266
|
);
|
|
265
267
|
|
|
268
|
+
const msg = useSaMessages('bundles');
|
|
269
|
+
const common = useSaMessages('common');
|
|
270
|
+
|
|
266
271
|
const locales = computed(() => props.activeLocales ?? [DEFAULT_LOCALE]);
|
|
267
272
|
const translatableLocales = computed(() => locales.value.filter((l) => l !== DEFAULT_LOCALE));
|
|
268
273
|
|
|
@@ -350,14 +355,14 @@ const createOpen = ref(false);
|
|
|
350
355
|
/** For the bundle-key conflict check in the wizard. */
|
|
351
356
|
const existingBundleKeys = computed(() => props.bundles.map((b) => b.bundleKey));
|
|
352
357
|
|
|
353
|
-
const statusFilterOptions
|
|
354
|
-
{ label:
|
|
355
|
-
{ label:
|
|
356
|
-
{ label:
|
|
357
|
-
{ label:
|
|
358
|
-
{ label:
|
|
359
|
-
{ label:
|
|
360
|
-
];
|
|
358
|
+
const statusFilterOptions = computed<BundlesStatusFilterOption[]>(() => [
|
|
359
|
+
{ label: msg.value.filter.all, value: 'all' },
|
|
360
|
+
{ label: msg.value.filter.live, value: 'live' },
|
|
361
|
+
{ label: msg.value.filter.scheduled, value: 'scheduled' },
|
|
362
|
+
{ label: msg.value.filter.draft, value: 'draft' },
|
|
363
|
+
{ label: msg.value.filter.superseded, value: 'superseded' },
|
|
364
|
+
{ label: msg.value.filter.retired, value: 'retired' },
|
|
365
|
+
]);
|
|
361
366
|
|
|
362
367
|
function openCreatePanel(): void {
|
|
363
368
|
createOpen.value = true;
|
|
@@ -446,8 +451,7 @@ async function submitEdit(): Promise<void> {
|
|
|
446
451
|
|
|
447
452
|
async function confirmDelete(bundle: BundleRow): Promise<void> {
|
|
448
453
|
const ok = window.confirm(
|
|
449
|
-
|
|
450
|
-
`published BundleVersions geschützt.`,
|
|
454
|
+
formatMessage(msg.value.page.confirmSoftDelete, { bundleKey: bundle.bundleKey }),
|
|
451
455
|
);
|
|
452
456
|
if (!ok) return;
|
|
453
457
|
await props.softDelete(bundle.id);
|
|
@@ -466,6 +470,13 @@ watch(
|
|
|
466
470
|
// ─── Strict mode warnings ───
|
|
467
471
|
const lastWarnings = ref<StrictModeWarning[]>([]);
|
|
468
472
|
|
|
473
|
+
const loadErrorText = computed(() =>
|
|
474
|
+
formatMessage(msg.value.page.loadError, { message: props.error?.message ?? '' }),
|
|
475
|
+
);
|
|
476
|
+
const strictWarningsText = computed(() =>
|
|
477
|
+
formatMessage(msg.value.page.strictWarnings, { count: lastWarnings.value.length }),
|
|
478
|
+
);
|
|
479
|
+
|
|
469
480
|
// ─── Inline editor (selected version per bundle) ───
|
|
470
481
|
const selectedVersionIdByBundle = ref<Record<string, string | null>>({});
|
|
471
482
|
const inlineEditorSaving = ref(false);
|
|
@@ -565,10 +576,10 @@ async function onInlineSave(
|
|
|
565
576
|
async function onDiscardVersion(bundleId: string, versionId: string): Promise<void> {
|
|
566
577
|
if (!props.discardDraft) {
|
|
567
578
|
inlineEditorError.value =
|
|
568
|
-
'Discard
|
|
579
|
+
'Discard is not wired up in the wrapper — add the `discardDraft` prop.';
|
|
569
580
|
return;
|
|
570
581
|
}
|
|
571
|
-
const ok = window.confirm(
|
|
582
|
+
const ok = window.confirm(msg.value.page.confirmDiscardVersion);
|
|
572
583
|
if (!ok) return;
|
|
573
584
|
inlineEditorSaving.value = true;
|
|
574
585
|
inlineEditorError.value = null;
|
|
@@ -614,7 +625,7 @@ async function onPublishSubmit(opts: {
|
|
|
614
625
|
validUntil?: string | null;
|
|
615
626
|
}): Promise<BundleVersionMutationResult> {
|
|
616
627
|
if (!publishDraft.value) {
|
|
617
|
-
throw new Error('BundlesPage: publish submit
|
|
628
|
+
throw new Error('BundlesPage: publish submit without draft context');
|
|
618
629
|
}
|
|
619
630
|
return props.publish(publishDraft.value.id, opts);
|
|
620
631
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="sa-dashboard">
|
|
3
3
|
<header class="sa-page-head sa-dashboard__head">
|
|
4
4
|
<div>
|
|
5
|
-
<h1 class="sa-page-head__title">
|
|
5
|
+
<h1 class="sa-page-head__title">{{ msg.title }}</h1>
|
|
6
6
|
<p v-if="subtitle" class="sa-page-head__sub">{{ subtitle }}</p>
|
|
7
7
|
</div>
|
|
8
8
|
<div class="sa-page-head__actions">
|
|
@@ -11,22 +11,22 @@
|
|
|
11
11
|
dense
|
|
12
12
|
icon="refresh"
|
|
13
13
|
:loading="loading"
|
|
14
|
-
aria-label="
|
|
14
|
+
:aria-label="common.reload"
|
|
15
15
|
@click="reload"
|
|
16
16
|
/>
|
|
17
17
|
</div>
|
|
18
18
|
</header>
|
|
19
19
|
|
|
20
20
|
<q-banner v-if="error" class="bg-red-1 text-red-9 q-mb-md" rounded>
|
|
21
|
-
<strong>
|
|
21
|
+
<strong>{{ common.error }}:</strong> {{ error.message }}
|
|
22
22
|
</q-banner>
|
|
23
23
|
|
|
24
24
|
<div v-if="loading && !cards.length" class="sa-dashboard__loading">
|
|
25
|
-
<q-spinner size="32px" />
|
|
25
|
+
<q-spinner size="32px" /> {{ common.loadingData }}
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
28
|
<div v-else-if="!cards.length" class="sa-dashboard__empty">
|
|
29
|
-
|
|
29
|
+
{{ msg.emptyKpiCards }}
|
|
30
30
|
</div>
|
|
31
31
|
|
|
32
32
|
<div v-else class="sa-dashboard__strip">
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
class="sa-dashboard__card sa-dashboard__shortcuts"
|
|
84
84
|
>
|
|
85
85
|
<header class="sa-dashboard__row-head">
|
|
86
|
-
<h2>
|
|
86
|
+
<h2>{{ msg.shortcutsTitle }}</h2>
|
|
87
87
|
</header>
|
|
88
88
|
<div class="sa-dashboard__shortcut-grid">
|
|
89
89
|
<a
|
|
@@ -106,10 +106,14 @@
|
|
|
106
106
|
</template>
|
|
107
107
|
|
|
108
108
|
<script setup lang="ts">
|
|
109
|
-
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
|
109
|
+
import { computed, inject, onMounted, reactive, ref, watch } from 'vue';
|
|
110
110
|
import type { AdminManifest, KpiCardDef } from '@saasicat/types';
|
|
111
|
-
import type { HttpClient } from '../types.js';
|
|
112
|
-
import { buildRoutes } from '../nav-builder.js';
|
|
111
|
+
import type { HttpClient } from '../client/types.js';
|
|
112
|
+
import { buildRoutes } from '../client/nav-builder.js';
|
|
113
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
114
|
+
import { SUPER_ADMIN_MANIFEST_KEY } from '../vue/super-admin-context.js';
|
|
115
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
116
|
+
import { useSuperAdminHttp } from '../vue/use-super-admin-context.js';
|
|
113
117
|
|
|
114
118
|
// Platform standard page: Dashboard.
|
|
115
119
|
//
|
|
@@ -204,7 +208,18 @@ interface KpiCardState {
|
|
|
204
208
|
value: string | number | null;
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
const
|
|
211
|
+
const msg = useSaMessages('dashboard');
|
|
212
|
+
const common = useSaMessages('common');
|
|
213
|
+
const { locale, intlLocale } = useSuperAdminI18n();
|
|
214
|
+
|
|
215
|
+
// Provided by createSuperAdminApp({ manifestGuard: { getManifest } }) — lets
|
|
216
|
+
// the page work as a plain route component without a wrapper.
|
|
217
|
+
const injectedManifest = inject(SUPER_ADMIN_MANIFEST_KEY, null);
|
|
218
|
+
// The KPI endpoints need the app's auth. Without this the page would fall back
|
|
219
|
+
// to a bare fetch() and every card would render as an em dash.
|
|
220
|
+
const shellHttp = useSuperAdminHttp();
|
|
221
|
+
|
|
222
|
+
const manifestRef = ref<AdminManifest | null>(props.manifest ?? injectedManifest?.() ?? null);
|
|
208
223
|
const cards = reactive<KpiCardState[]>([]);
|
|
209
224
|
const loading = ref(false);
|
|
210
225
|
const error = ref<Error | null>(null);
|
|
@@ -238,7 +253,7 @@ const resolvedShortcuts = computed<ShortcutDef[]>(() => {
|
|
|
238
253
|
} else if (!manifestRef.value) {
|
|
239
254
|
list = [];
|
|
240
255
|
} else {
|
|
241
|
-
const routes = buildRoutes(manifestRef.value);
|
|
256
|
+
const routes = buildRoutes(manifestRef.value, { locale: locale.value });
|
|
242
257
|
list = routes.map((r) => ({
|
|
243
258
|
id: r.id,
|
|
244
259
|
label: r.label,
|
|
@@ -325,9 +340,8 @@ function defaultFormat(card: KpiCardDef, body: unknown): KpiFormatted {
|
|
|
325
340
|
}
|
|
326
341
|
|
|
327
342
|
async function callHttp(url: string): Promise<{ status: number; json: () => Promise<unknown> }> {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
343
|
+
const http = props.http ?? shellHttp;
|
|
344
|
+
if (http) return http(url, { method: 'GET' });
|
|
331
345
|
const headers: Record<string, string> = {};
|
|
332
346
|
const token = props.getAuthToken?.();
|
|
333
347
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
@@ -354,7 +368,7 @@ function extractSub(
|
|
|
354
368
|
}
|
|
355
369
|
if (hintType === 'value+delta' && typeof body.delta === 'number') {
|
|
356
370
|
const sign = body.delta > 0 ? '+' : '';
|
|
357
|
-
return `${sign}${body.delta}
|
|
371
|
+
return formatMessage(msg.value.deltaVsPreviousPeriod, { delta: `${sign}${body.delta}` });
|
|
358
372
|
}
|
|
359
373
|
if (typeof body.sub === 'string') return body.sub;
|
|
360
374
|
return undefined;
|
|
@@ -363,14 +377,14 @@ function extractSub(
|
|
|
363
377
|
function formatValue(card: KpiCardState): string {
|
|
364
378
|
const v = card.value;
|
|
365
379
|
if (v === null || v === undefined || v === '') return '—';
|
|
366
|
-
if (typeof v === 'number') return v.toLocaleString(
|
|
380
|
+
if (typeof v === 'number') return v.toLocaleString(intlLocale.value);
|
|
367
381
|
if (typeof v === 'string') return v;
|
|
368
382
|
return String(v);
|
|
369
383
|
}
|
|
370
384
|
|
|
371
385
|
function formatTimestamp(iso: string): string {
|
|
372
386
|
try {
|
|
373
|
-
return new Date(iso).toLocaleString(
|
|
387
|
+
return new Date(iso).toLocaleString(intlLocale.value, {
|
|
374
388
|
day: '2-digit',
|
|
375
389
|
month: 'short',
|
|
376
390
|
year: 'numeric',
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
<q-banner v-if="error" class="sa-discovery__error" rounded>
|
|
13
13
|
<template #avatar><q-icon name="warning" color="negative" /></template>
|
|
14
|
-
|
|
14
|
+
{{ common.error }}: {{ error.message }}
|
|
15
15
|
</q-banner>
|
|
16
16
|
|
|
17
17
|
<DiscoveryKpis
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
/>
|
|
26
26
|
|
|
27
27
|
<q-tabs v-model="activeTab" align="left" dense class="sa-discovery__tabs">
|
|
28
|
-
<q-tab name="features" :label="
|
|
29
|
-
<q-tab name="quotas" :label="
|
|
28
|
+
<q-tab name="features" :label="featuresTabLabel" />
|
|
29
|
+
<q-tab name="quotas" :label="quotasTabLabel" />
|
|
30
30
|
</q-tabs>
|
|
31
31
|
|
|
32
32
|
<q-tab-panels v-model="activeTab" animated class="sa-discovery__panels">
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
dense
|
|
38
38
|
outlined
|
|
39
39
|
clearable
|
|
40
|
-
placeholder="
|
|
40
|
+
:placeholder="msg.searchPlaceholder"
|
|
41
41
|
class="sa-discovery__search"
|
|
42
42
|
>
|
|
43
43
|
<template #prepend><q-icon name="search" /></template>
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
outlined
|
|
49
49
|
emit-value
|
|
50
50
|
map-options
|
|
51
|
-
:options="
|
|
51
|
+
:options="statusFilterOptions"
|
|
52
52
|
class="sa-discovery__filter"
|
|
53
53
|
/>
|
|
54
54
|
</div>
|
|
@@ -76,21 +76,20 @@
|
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
<div v-if="filteredFeatures.length === 0" class="sa-discovery__empty-row">
|
|
79
|
-
|
|
79
|
+
{{ msg.noFeaturesMatchFilters }}
|
|
80
80
|
</div>
|
|
81
81
|
|
|
82
82
|
<div v-if="orphanCaps.length" class="sa-discovery__group">
|
|
83
83
|
<div class="sa-discovery__group-head">
|
|
84
84
|
<span class="sa-discovery__group-title sa-discovery__group-title--orphan">
|
|
85
85
|
<q-icon name="warning" size="14px" />
|
|
86
|
-
|
|
86
|
+
{{ msg.orphansTitle }}
|
|
87
87
|
</span>
|
|
88
88
|
<span class="sa-discovery__group-count">{{ orphanCaps.length }}</span>
|
|
89
89
|
</div>
|
|
90
90
|
<p class="sa-discovery__orphan-hint">
|
|
91
|
-
|
|
92
|
-
<code>@ImplementsCapability</code
|
|
93
|
-
nachziehen, sonst sind sie nicht verkaufbar.
|
|
91
|
+
{{ msg.orphanHint.before }} <code>feature:</code>{{ msg.orphanHint.middle }}
|
|
92
|
+
<code>@ImplementsCapability</code>{{ msg.orphanHint.after }}
|
|
94
93
|
</p>
|
|
95
94
|
<DiscoveryCapList
|
|
96
95
|
:capabilities="orphanCaps"
|
|
@@ -113,7 +112,7 @@
|
|
|
113
112
|
@quota-locale="onQuotaLocale"
|
|
114
113
|
/>
|
|
115
114
|
<div v-if="quotas.length === 0" class="sa-discovery__empty-row">
|
|
116
|
-
|
|
115
|
+
{{ msg.noQuotasDeclared }}
|
|
117
116
|
</div>
|
|
118
117
|
</div>
|
|
119
118
|
</q-tab-panel>
|
|
@@ -140,7 +139,9 @@ import DiscoveryHeader from './discovery-page/DiscoveryHeader.vue';
|
|
|
140
139
|
import DiscoveryKpis from './discovery-page/DiscoveryKpis.vue';
|
|
141
140
|
import DiscoveryMetaBanner from './discovery-page/DiscoveryMetaBanner.vue';
|
|
142
141
|
import DiscoveryQuotaCard from './discovery-page/DiscoveryQuotaCard.vue';
|
|
143
|
-
import {
|
|
142
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
143
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
144
|
+
import { statusLabel } from './discovery-page/discovery-ui.js';
|
|
144
145
|
|
|
145
146
|
// Platform standard page: Discovery review, feature-centric (#20).
|
|
146
147
|
// Two tabs (Features + Quotas); each entry is an expandable card with
|
|
@@ -174,18 +175,31 @@ const props = defineProps<{
|
|
|
174
175
|
}>();
|
|
175
176
|
|
|
176
177
|
const activeTab = ref<'features' | 'quotas'>('features');
|
|
178
|
+
|
|
179
|
+
const featuresTabLabel = computed(() =>
|
|
180
|
+
formatMessage(msg.value.tabFeatures, { count: props.features.length }),
|
|
181
|
+
);
|
|
182
|
+
const quotasTabLabel = computed(() =>
|
|
183
|
+
formatMessage(msg.value.tabQuotas, { count: props.quotas.length }),
|
|
184
|
+
);
|
|
177
185
|
const featureQuery = ref('');
|
|
178
186
|
const statusFilter = ref<DiscoveryStatus | 'all'>('all');
|
|
179
187
|
const expandedFeature = ref<string | null>(null);
|
|
180
188
|
const expandedQuota = ref<string | null>(null);
|
|
181
189
|
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
190
|
+
const msg = useSaMessages('discovery');
|
|
191
|
+
const common = useSaMessages('common');
|
|
192
|
+
const { locale, intlLocale } = useSuperAdminI18n();
|
|
193
|
+
|
|
194
|
+
const statusFilterOptions = computed<Array<{ label: string; value: DiscoveryStatus | 'all' }>>(
|
|
195
|
+
() => [
|
|
196
|
+
{ label: msg.value.statusFilterAll, value: 'all' },
|
|
197
|
+
...(['pending', 'approved', 'outdated', 'obsolete'] as const).map((status) => ({
|
|
198
|
+
label: statusLabel(status, locale.value),
|
|
199
|
+
value: status,
|
|
200
|
+
})),
|
|
201
|
+
],
|
|
202
|
+
);
|
|
189
203
|
|
|
190
204
|
const appKey = computed(() => props.snapshot?.app.key ?? '—');
|
|
191
205
|
const appLabel = computed(() => {
|
|
@@ -194,9 +208,9 @@ const appLabel = computed(() => {
|
|
|
194
208
|
});
|
|
195
209
|
const appVersion = computed(() => props.snapshot?.app.version ?? '0.0.0');
|
|
196
210
|
const scanLabel = computed(() => {
|
|
197
|
-
if (!props.snapshot?.scannedAt) return
|
|
211
|
+
if (!props.snapshot?.scannedAt) return msg.value.notScannedYet;
|
|
198
212
|
try {
|
|
199
|
-
return new Date(props.snapshot.scannedAt).toLocaleString(
|
|
213
|
+
return new Date(props.snapshot.scannedAt).toLocaleString(intlLocale.value);
|
|
200
214
|
} catch {
|
|
201
215
|
return props.snapshot.scannedAt;
|
|
202
216
|
}
|
|
@@ -277,21 +291,19 @@ const filteredFeatures = computed(() => {
|
|
|
277
291
|
});
|
|
278
292
|
});
|
|
279
293
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
/** Grouping by primary owner (#14); "Ohne Owner" last. */
|
|
294
|
+
/** Grouping by primary owner (#14); the ownerless group goes last. */
|
|
283
295
|
const featureGroups = computed<Array<{ label: string; features: FeatureCatalogEntryRow[] }>>(() => {
|
|
284
296
|
const groups = new Map<string, FeatureCatalogEntryRow[]>();
|
|
285
297
|
for (const f of filteredFeatures.value) {
|
|
286
|
-
const owner = ownersByFeature.value.get(f.featureKey)?.[0] ??
|
|
298
|
+
const owner = ownersByFeature.value.get(f.featureKey)?.[0] ?? msg.value.noOwner;
|
|
287
299
|
const list = groups.get(owner);
|
|
288
300
|
if (list) list.push(f);
|
|
289
301
|
else groups.set(owner, [f]);
|
|
290
302
|
}
|
|
291
303
|
return [...groups.entries()]
|
|
292
304
|
.sort((a, b) => {
|
|
293
|
-
if (a[0] ===
|
|
294
|
-
if (b[0] ===
|
|
305
|
+
if (a[0] === msg.value.noOwner) return 1;
|
|
306
|
+
if (b[0] === msg.value.noOwner) return -1;
|
|
295
307
|
return a[0].localeCompare(b[0]);
|
|
296
308
|
})
|
|
297
309
|
.map(([label, features]) => ({ label, features }));
|
|
@@ -340,7 +352,7 @@ function persist(p: Promise<unknown>): void {
|
|
|
340
352
|
p.catch((err) => {
|
|
341
353
|
// Auth renewal/redirect is handled by the HTTP client; here just log
|
|
342
354
|
// so a failed save stays visible.
|
|
343
|
-
console.error('
|
|
355
|
+
console.error('Failed to save catalog entry', err);
|
|
344
356
|
});
|
|
345
357
|
}
|
|
346
358
|
|