@saasicat/ui-vue 0.5.0 → 0.7.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 +17 -0
- package/dist/{chunk-EH7TOD4H.js → chunk-A3W2N26K.js} +73 -77
- package/dist/{chunk-3YP2ZAOD.js → chunk-QZOSDCNP.js} +36 -1
- package/dist/chunk-X5SQ5HMB.js +3231 -0
- package/dist/client/index.cjs +3279 -76
- package/dist/client/index.d.cts +31 -5
- package/dist/client/index.d.ts +31 -5
- package/dist/client/index.js +23 -5
- package/dist/index.cjs +3709 -465
- package/dist/index.d.cts +37 -54
- package/dist/index.d.ts +37 -54
- package/dist/index.js +347 -311
- package/dist/messages-7b0P8W75.d.cts +1652 -0
- package/dist/messages-7b0P8W75.d.ts +1652 -0
- package/dist/quasar/index.cjs +3185 -4
- package/dist/quasar/index.d.cts +11 -2
- package/dist/quasar/index.d.ts +11 -2
- package/dist/quasar/index.js +8 -3
- package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
- package/dist/testing-e2e/admin-pages-suite.js +14 -11
- package/dist/{ui-notify-COUzXEQ4.d.ts → use-super-admin-i18n-B3v3-SWZ.d.ts} +35 -3
- package/dist/{ui-notify-D_eAAwRh.d.cts → use-super-admin-i18n-DMktRVEt.d.cts} +35 -3
- package/package.json +2 -2
- package/src/client/action-registry.ts +4 -4
- package/src/client/batch-column-fetcher.ts +4 -4
- package/src/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 +2 -0
- package/src/client/manifest-loader.ts +4 -4
- package/src/client/nav-builder.ts +44 -51
- package/src/client/plan-versions-catalog.ts +42 -30
- package/src/components/BundleVersionPublishDialog.vue +35 -24
- 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 +20 -9
- 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 +1 -1
- package/src/pages-standard/AdminLayout.vue +42 -17
- 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 +29 -15
- package/src/pages-standard/DiscoveryPage.vue +40 -28
- package/src/pages-standard/EmailHistoryPage.vue +92 -59
- package/src/pages-standard/MarketingCatalogPage.vue +35 -16
- package/src/pages-standard/PilotsPage.vue +57 -41
- package/src/pages-standard/PlanVersionsPage.vue +7 -3
- package/src/pages-standard/PlansPage.vue +89 -57
- package/src/pages-standard/PlatformEmailPage.vue +115 -50
- package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
- package/src/pages-standard/PromoCodesPage.vue +46 -35
- package/src/pages-standard/SubscriptionsPage.vue +14 -9
- package/src/pages-standard/SuperAdminLoginPage.vue +16 -13
- package/src/pages-standard/SuperAdminSetupWizard.vue +51 -47
- package/src/pages-standard/TenantDetailPage.vue +50 -36
- package/src/pages-standard/TenantsPage.vue +54 -41
- package/src/pages-standard/UsersPage.vue +66 -42
- 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 +41 -25
- package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +45 -26
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +7 -5
- package/src/pages-standard/plan-versions/PlanVersionsList.vue +39 -16
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +24 -12
- 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 +72 -37
- package/src/pages-tenant/PlanChangeWizard.vue +8 -4
- package/src/pages-tenant/TenantPlanSection.vue +17 -13
- package/src/pages-tenant/default-i18n.ts +217 -1
- package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +8 -6
- package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +9 -10
- package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
- package/src/quasar/create-super-admin-app.ts +19 -0
- package/src/testing-e2e/admin-pages-suite.ts +14 -11
- package/src/vue/project-page-host.ts +5 -3
- package/src/vue/use-audit-entries.ts +3 -3
- package/src/vue/use-bulk-publish.ts +6 -4
- package/src/vue/use-bundle-versions-map.ts +6 -4
- package/src/vue/use-bundles.ts +13 -13
- package/src/vue/use-catalog-entries.ts +13 -10
- package/src/vue/use-discovery.ts +8 -5
- package/src/vue/use-entitlement.ts +3 -3
- package/src/vue/use-live-plan-versions.ts +4 -4
- package/src/vue/use-marketing-projections.ts +9 -6
- package/src/vue/use-nav.ts +8 -2
- package/src/vue/use-plan-editor.ts +2 -2
- package/src/vue/use-plan-versions.ts +3 -3
- package/src/vue/use-plans.ts +12 -12
- package/src/vue/use-platform-tenant-actions.ts +9 -6
- package/src/vue/use-promotions.ts +9 -10
- package/src/vue/use-subscription-draft.ts +2 -3
- package/src/vue/use-super-admin-context.ts +3 -3
- package/src/vue/use-super-admin-i18n.ts +82 -0
- package/src/vue/use-tenant-action-flow.ts +11 -6
- package/src/vue/use-tenant-manifest.ts +1 -3
- package/src/vue/use-tenant-subscription-bundles.ts +4 -4
- package/src/vue/use-tenants.ts +3 -3
- package/dist/action-registry-CQ2wL3R-.d.cts +0 -85
- package/dist/action-registry-CQ2wL3R-.d.ts +0 -85
- package/dist/chunk-LVGUSD3T.js +0 -26
- 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/vue/use-business-types.ts +0 -269
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<q-layout view="lHh Lpr lFf">
|
|
3
3
|
<div v-if="isProduction" class="sa-admin-banner sa-admin-banner--prod">
|
|
4
4
|
<q-icon name="warning" size="14px" />
|
|
5
|
-
<strong>PRODUCTION</strong> —
|
|
5
|
+
<strong>PRODUCTION</strong> — {{ msg.header.productionWarning }}
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<q-header elevated class="sa-admin-header">
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
<q-btn flat dense round icon="menu" @click="leftDrawerOpen = !leftDrawerOpen" />
|
|
11
11
|
<q-toolbar-title class="text-weight-bold">
|
|
12
12
|
{{ currentPageTitle }}
|
|
13
|
-
<div class="sa-admin-header__sub">
|
|
13
|
+
<div class="sa-admin-header__sub">{{ msg.header.subtitle }}</div>
|
|
14
14
|
</q-toolbar-title>
|
|
15
15
|
<q-space />
|
|
16
16
|
<slot name="header-actions" />
|
|
17
|
-
<q-badge class="sa-admin-badge q-mr-sm">
|
|
17
|
+
<q-badge class="sa-admin-badge q-mr-sm">{{ msg.header.roleBadge }}</q-badge>
|
|
18
18
|
<div class="sa-admin-user">
|
|
19
19
|
<q-avatar size="32px" class="sa-admin-user__avatar">
|
|
20
20
|
{{ initials }}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<div class="text-caption sa-admin-user__email">{{ userEmail }}</div>
|
|
25
25
|
</div>
|
|
26
26
|
<q-btn flat round dense icon="logout" size="sm" @click="emit('logout')">
|
|
27
|
-
<q-tooltip>
|
|
27
|
+
<q-tooltip>{{ msg.header.logout }}</q-tooltip>
|
|
28
28
|
</q-btn>
|
|
29
29
|
</div>
|
|
30
30
|
</q-toolbar>
|
|
@@ -39,10 +39,12 @@
|
|
|
39
39
|
>
|
|
40
40
|
<div class="sa-admin-drawer__stack">
|
|
41
41
|
<div class="sa-admin-drawer__brand">
|
|
42
|
-
<div class="sa-admin-drawer__logo">{{
|
|
42
|
+
<div class="sa-admin-drawer__logo">{{ brand.logoText }}</div>
|
|
43
43
|
<div>
|
|
44
|
-
<div class="sa-admin-drawer__brand-name">{{
|
|
45
|
-
<div class="sa-admin-drawer__brand-tag">
|
|
44
|
+
<div class="sa-admin-drawer__brand-name">{{ brand.name }}</div>
|
|
45
|
+
<div class="sa-admin-drawer__brand-tag">
|
|
46
|
+
{{ brand.tag }} v{{ adminUiVersion }}
|
|
47
|
+
</div>
|
|
46
48
|
</div>
|
|
47
49
|
</div>
|
|
48
50
|
|
|
@@ -75,7 +77,7 @@
|
|
|
75
77
|
target="_blank"
|
|
76
78
|
rel="noopener"
|
|
77
79
|
>
|
|
78
|
-
<q-icon name="menu_book" size="14px" />
|
|
80
|
+
<q-icon name="menu_book" size="14px" /> {{ msg.drawer.docs }}
|
|
79
81
|
</a>
|
|
80
82
|
</slot>
|
|
81
83
|
</div>
|
|
@@ -91,10 +93,12 @@
|
|
|
91
93
|
</template>
|
|
92
94
|
|
|
93
95
|
<script setup lang="ts">
|
|
94
|
-
import { computed, ref } from 'vue';
|
|
96
|
+
import { computed, inject, ref } from 'vue';
|
|
95
97
|
import { useRoute } from 'vue-router';
|
|
96
98
|
import type { AdminManifest, StandardPageKey } from '@saasicat/types';
|
|
97
|
-
import { buildRoutes, buildSidebar,
|
|
99
|
+
import { buildRoutes, buildSidebar, defaultSectionOrder } from '../client/nav-builder.js';
|
|
100
|
+
import { SUPER_ADMIN_BRAND_KEY, SUPER_ADMIN_MANIFEST_KEY } from '../vue/super-admin-context.js';
|
|
101
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
98
102
|
import { ADMIN_UI_VERSION } from '../client/version.js';
|
|
99
103
|
|
|
100
104
|
// SuperAdmin layout — universal platform shell for all consumer apps.
|
|
@@ -131,8 +135,13 @@ export interface SidebarItem {
|
|
|
131
135
|
|
|
132
136
|
const props = withDefaults(
|
|
133
137
|
defineProps<{
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Branding. Optional — when the layout is mounted as a route component
|
|
140
|
+
* inside `createSuperAdminApp({ brand })`, these default to the
|
|
141
|
+
* provided brand and only need passing to override it.
|
|
142
|
+
*/
|
|
143
|
+
brandLogoText?: string;
|
|
144
|
+
brandName?: string;
|
|
136
145
|
brandTag?: string;
|
|
137
146
|
manifest?: AdminManifest | null;
|
|
138
147
|
staticNavFallback?: readonly SidebarItem[];
|
|
@@ -164,13 +173,11 @@ const props = withDefaults(
|
|
|
164
173
|
adminPathPrefix?: string;
|
|
165
174
|
}>(),
|
|
166
175
|
{
|
|
167
|
-
brandTag: 'SuperAdmin',
|
|
168
176
|
staticNavFallback: () => [],
|
|
169
177
|
localItems: () => [],
|
|
170
178
|
localItemsSection: null,
|
|
171
179
|
adminPathPrefix: '/admin',
|
|
172
180
|
isProduction: false,
|
|
173
|
-
sectionOrder: () => DEFAULT_SECTION_ORDER,
|
|
174
181
|
},
|
|
175
182
|
);
|
|
176
183
|
|
|
@@ -179,6 +186,23 @@ const emit = defineEmits<{
|
|
|
179
186
|
}>();
|
|
180
187
|
|
|
181
188
|
const route = useRoute();
|
|
189
|
+
const msg = useSaMessages('shell');
|
|
190
|
+
const { locale } = useSuperAdminI18n();
|
|
191
|
+
// Provided by createSuperAdminApp(); null when the layout is mounted
|
|
192
|
+
// stand-alone. Grouped into one object so the names cannot shadow the
|
|
193
|
+
// same-named props inside the template.
|
|
194
|
+
const injectedBrand = inject(SUPER_ADMIN_BRAND_KEY, null);
|
|
195
|
+
// Same idea for the manifest: createSuperAdminApp({ manifestGuard: {
|
|
196
|
+
// getManifest } }) provides an accessor, so pages mounted straight as route
|
|
197
|
+
// components do not have to be wrapped just to thread the prop through.
|
|
198
|
+
const injectedManifest = inject(SUPER_ADMIN_MANIFEST_KEY, null);
|
|
199
|
+
const activeManifest = computed(() => props.manifest ?? injectedManifest?.() ?? null);
|
|
200
|
+
const brand = computed(() => ({
|
|
201
|
+
logoText: props.brandLogoText ?? injectedBrand?.logoText ?? '',
|
|
202
|
+
name: props.brandName ?? injectedBrand?.name ?? '',
|
|
203
|
+
tag: props.brandTag ?? injectedBrand?.tag ?? 'SuperAdmin',
|
|
204
|
+
}));
|
|
205
|
+
|
|
182
206
|
const leftDrawerOpen = ref(false);
|
|
183
207
|
|
|
184
208
|
const adminUiVersion = ADMIN_UI_VERSION;
|
|
@@ -195,18 +219,19 @@ interface NavSection {
|
|
|
195
219
|
}
|
|
196
220
|
|
|
197
221
|
const navSections = computed<NavSection[]>(() => {
|
|
198
|
-
const m =
|
|
222
|
+
const m = activeManifest.value;
|
|
199
223
|
if (!m) {
|
|
200
224
|
// Pre-manifest: static fallback + local items as one unnamed
|
|
201
225
|
// section, so that the UI has no flicker before manifest load.
|
|
202
226
|
return [{ title: null, items: [...props.staticNavFallback, ...props.localItems] }];
|
|
203
227
|
}
|
|
204
228
|
const routes = buildRoutes(m, {
|
|
229
|
+
locale: locale.value,
|
|
205
230
|
standardPageRoutes: props.standardPageRoutes,
|
|
206
231
|
standardPageNavSection: props.standardPageNavSection,
|
|
207
232
|
availableExtensions: props.availableExtensions,
|
|
208
233
|
});
|
|
209
|
-
const sections = buildSidebar(routes, props.sectionOrder);
|
|
234
|
+
const sections = buildSidebar(routes, props.sectionOrder ?? defaultSectionOrder(locale.value));
|
|
210
235
|
const result: NavSection[] = sections.map((s) => ({
|
|
211
236
|
title: s.section,
|
|
212
237
|
items: s.items.map((item) => ({
|
|
@@ -241,7 +266,7 @@ const currentPageTitle = computed(() => {
|
|
|
241
266
|
}
|
|
242
267
|
// Default: sidebar item label that matches the active route.
|
|
243
268
|
const item = resolvedNav.value.find((i) => i.to === route.path);
|
|
244
|
-
return item?.label ?? `${
|
|
269
|
+
return item?.label ?? `${brand.value.name} SuperAdmin`;
|
|
245
270
|
});
|
|
246
271
|
</script>
|
|
247
272
|
|
|
@@ -3,21 +3,16 @@
|
|
|
3
3
|
<q-card class="sa-manifest-error__card">
|
|
4
4
|
<q-card-section class="sa-manifest-error__head">
|
|
5
5
|
<q-icon name="cloud_off" size="32px" color="negative" />
|
|
6
|
-
<h1 class="sa-manifest-error__title">
|
|
6
|
+
<h1 class="sa-manifest-error__title">{{ msg.manifestError.title }}</h1>
|
|
7
7
|
</q-card-section>
|
|
8
8
|
<q-card-section>
|
|
9
|
-
<p class="sa-manifest-error__lead">
|
|
10
|
-
Das Plattform-Manifest konnte nicht geladen werden. Ohne Manifest sind Sidebar,
|
|
11
|
-
Capabilities und Action-Buttons nicht zuverlässig — die UI würde sonst falsche
|
|
12
|
-
oder unerreichbare Buttons rendern. Der Router hat dich deshalb hierher
|
|
13
|
-
geschickt (fail-closed).
|
|
14
|
-
</p>
|
|
9
|
+
<p class="sa-manifest-error__lead">{{ msg.manifestError.lead }}</p>
|
|
15
10
|
<p v-if="errorMessage" class="sa-manifest-error__detail">
|
|
16
|
-
<strong>
|
|
11
|
+
<strong>{{ msg.manifestError.detailLabel }}</strong> {{ errorMessage }}
|
|
17
12
|
</p>
|
|
18
13
|
<div class="sa-manifest-error__actions">
|
|
19
|
-
<q-btn unelevated color="primary" label="
|
|
20
|
-
<q-btn flat label="
|
|
14
|
+
<q-btn unelevated color="primary" :label="common.reload" @click="onRetry" />
|
|
15
|
+
<q-btn flat :label="msg.header.logout" @click="onLogout" />
|
|
21
16
|
</div>
|
|
22
17
|
</q-card-section>
|
|
23
18
|
</q-card>
|
|
@@ -25,11 +20,16 @@
|
|
|
25
20
|
</template>
|
|
26
21
|
|
|
27
22
|
<script setup lang="ts">
|
|
23
|
+
import { useSaMessages } from '../vue/use-super-admin-i18n.js';
|
|
24
|
+
|
|
28
25
|
// Platform standard error page for `manifestGuard.errorRoute`.
|
|
29
26
|
// Consumers pass `onRetry`/`onLogout` in as callbacks and decide
|
|
30
27
|
// themselves what happens (e.g. manifestStore.clearCache() + ensureLoaded,
|
|
31
28
|
// authStore.logout, router.replace).
|
|
32
29
|
|
|
30
|
+
const msg = useSaMessages('shell');
|
|
31
|
+
const common = useSaMessages('common');
|
|
32
|
+
|
|
33
33
|
defineProps<{
|
|
34
34
|
errorMessage?: string | null;
|
|
35
35
|
onRetry: () => Promise<void> | void;
|
|
@@ -2,18 +2,51 @@
|
|
|
2
2
|
<div class="sa-audit">
|
|
3
3
|
<header class="sa-audit__head">
|
|
4
4
|
<div>
|
|
5
|
-
<h1 class="sa-audit__title">
|
|
6
|
-
<p class="sa-audit__sub">
|
|
5
|
+
<h1 class="sa-audit__title">{{ msg.title }}</h1>
|
|
6
|
+
<p class="sa-audit__sub">
|
|
7
|
+
{{ formatMessage(msg.subtitle, { count: rows.length }) }}
|
|
8
|
+
</p>
|
|
7
9
|
</div>
|
|
8
|
-
<q-btn
|
|
10
|
+
<q-btn
|
|
11
|
+
unelevated
|
|
12
|
+
color="primary"
|
|
13
|
+
icon="refresh"
|
|
14
|
+
:label="msg.filterButton"
|
|
15
|
+
@click="reload"
|
|
16
|
+
/>
|
|
9
17
|
</header>
|
|
10
18
|
|
|
11
19
|
<div class="sa-audit__body">
|
|
12
20
|
<div class="sa-audit__filter">
|
|
13
|
-
<q-input
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
<q-input
|
|
22
|
+
v-model="filter.actor"
|
|
23
|
+
outlined
|
|
24
|
+
dense
|
|
25
|
+
:label="msg.filters.actor"
|
|
26
|
+
clearable
|
|
27
|
+
/>
|
|
28
|
+
<q-input
|
|
29
|
+
v-model="filter.action"
|
|
30
|
+
outlined
|
|
31
|
+
dense
|
|
32
|
+
:label="msg.filters.action"
|
|
33
|
+
clearable
|
|
34
|
+
/>
|
|
35
|
+
<q-input
|
|
36
|
+
v-model="filter.entity"
|
|
37
|
+
outlined
|
|
38
|
+
dense
|
|
39
|
+
:label="msg.filters.entity"
|
|
40
|
+
clearable
|
|
41
|
+
/>
|
|
42
|
+
<q-input
|
|
43
|
+
v-model="filter.since"
|
|
44
|
+
outlined
|
|
45
|
+
dense
|
|
46
|
+
type="date"
|
|
47
|
+
:label="msg.filters.since"
|
|
48
|
+
clearable
|
|
49
|
+
/>
|
|
17
50
|
</div>
|
|
18
51
|
|
|
19
52
|
<div class="sa-audit__card">
|
|
@@ -29,7 +62,7 @@
|
|
|
29
62
|
<template #body-cell-changes="{ row }">
|
|
30
63
|
<q-td>
|
|
31
64
|
<q-btn flat dense icon="code" color="primary" @click="openDetail(row)">
|
|
32
|
-
<q-tooltip>
|
|
65
|
+
<q-tooltip>{{ common.details }}</q-tooltip>
|
|
33
66
|
</q-btn>
|
|
34
67
|
</q-td>
|
|
35
68
|
</template>
|
|
@@ -42,7 +75,7 @@
|
|
|
42
75
|
<q-card-section>
|
|
43
76
|
<div class="text-h6">{{ detail?.action }} · {{ detail?.entity }}</div>
|
|
44
77
|
<div class="text-caption text-grey-7">
|
|
45
|
-
{{ detail ? formatTs(detail.createdAt) : '' }} ·
|
|
78
|
+
{{ detail ? formatTs(detail.createdAt) : '' }} · {{ msg.detailActorPrefix }}
|
|
46
79
|
{{ detail ? actorEmail(detail) : '—' }}
|
|
47
80
|
</div>
|
|
48
81
|
</q-card-section>
|
|
@@ -52,7 +85,7 @@
|
|
|
52
85
|
}}</pre>
|
|
53
86
|
</q-card-section>
|
|
54
87
|
<q-card-actions align="right">
|
|
55
|
-
<q-btn v-close-popup flat label="
|
|
88
|
+
<q-btn v-close-popup flat :label="common.close" />
|
|
56
89
|
</q-card-actions>
|
|
57
90
|
</q-card>
|
|
58
91
|
</q-dialog>
|
|
@@ -60,7 +93,9 @@
|
|
|
60
93
|
</template>
|
|
61
94
|
|
|
62
95
|
<script setup lang="ts">
|
|
63
|
-
import { onMounted, reactive, ref } from 'vue';
|
|
96
|
+
import { computed, onMounted, reactive, ref } from 'vue';
|
|
97
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
98
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
64
99
|
|
|
65
100
|
// Platform standard page: audit trail. Data-agnostic — the app passes
|
|
66
101
|
// `loadAudit({ actor, action, entity, since, limit })` through.
|
|
@@ -89,36 +124,40 @@ const props = defineProps<{
|
|
|
89
124
|
pageSize?: number;
|
|
90
125
|
}>();
|
|
91
126
|
|
|
127
|
+
const msg = useSaMessages('audit');
|
|
128
|
+
const common = useSaMessages('common');
|
|
129
|
+
const { intlLocale } = useSuperAdminI18n();
|
|
130
|
+
|
|
92
131
|
const rows = ref<AuditRow[]>([]);
|
|
93
132
|
const loading = ref(false);
|
|
94
133
|
const filter = reactive({ actor: '', action: '', entity: '', since: '' });
|
|
95
134
|
const detailOpen = ref(false);
|
|
96
135
|
const detail = ref<AuditRow | null>(null);
|
|
97
136
|
|
|
98
|
-
const columns = [
|
|
137
|
+
const columns = computed(() => [
|
|
99
138
|
{
|
|
100
139
|
name: 'createdAt',
|
|
101
|
-
label:
|
|
140
|
+
label: msg.value.columns.time,
|
|
102
141
|
field: (r: AuditRow) => formatTs(r.createdAt),
|
|
103
142
|
align: 'left' as const,
|
|
104
143
|
sortable: true,
|
|
105
144
|
},
|
|
106
145
|
{
|
|
107
146
|
name: 'actor',
|
|
108
|
-
label:
|
|
147
|
+
label: msg.value.columns.actor,
|
|
109
148
|
field: actorEmail,
|
|
110
149
|
align: 'left' as const,
|
|
111
150
|
},
|
|
112
|
-
{ name: 'action', label:
|
|
113
|
-
{ name: 'entity', label:
|
|
151
|
+
{ name: 'action', label: msg.value.columns.action, field: 'action', align: 'left' as const },
|
|
152
|
+
{ name: 'entity', label: msg.value.columns.entity, field: 'entity', align: 'left' as const },
|
|
114
153
|
{
|
|
115
154
|
name: 'entityId',
|
|
116
|
-
label:
|
|
155
|
+
label: msg.value.columns.id,
|
|
117
156
|
field: (r: AuditRow) => r.entityId.slice(0, 8),
|
|
118
157
|
align: 'left' as const,
|
|
119
158
|
},
|
|
120
159
|
{ name: 'changes', label: '', field: 'id', align: 'right' as const },
|
|
121
|
-
];
|
|
160
|
+
]);
|
|
122
161
|
|
|
123
162
|
async function reload() {
|
|
124
163
|
loading.value = true;
|
|
@@ -153,7 +192,7 @@ function actorEmail(r: AuditRow): string {
|
|
|
153
192
|
function formatTs(iso: string | null | undefined): string {
|
|
154
193
|
if (!iso) return '—';
|
|
155
194
|
try {
|
|
156
|
-
return new Date(iso).toLocaleString(
|
|
195
|
+
return new Date(iso).toLocaleString(intlLocale.value, {
|
|
157
196
|
day: '2-digit',
|
|
158
197
|
month: 'short',
|
|
159
198
|
year: 'numeric',
|
|
@@ -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
111
|
import type { HttpClient } from '../client/types.js';
|
|
112
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',
|