@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="sa-tenants">
|
|
3
3
|
<header class="sa-tenants__head">
|
|
4
4
|
<div>
|
|
5
|
-
<h1 class="sa-tenants__title">
|
|
5
|
+
<h1 class="sa-tenants__title">{{ msg.list.title }}</h1>
|
|
6
6
|
<p v-if="subtitle" class="sa-tenants__sub">{{ subtitle }}</p>
|
|
7
7
|
</div>
|
|
8
8
|
</header>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<q-icon name="search" size="15px" />
|
|
13
13
|
<input
|
|
14
14
|
v-model="searchInput"
|
|
15
|
-
placeholder="
|
|
15
|
+
:placeholder="msg.list.searchPlaceholder"
|
|
16
16
|
@input="reloadDebounced"
|
|
17
17
|
/>
|
|
18
18
|
<button v-if="searchInput" class="sa-tenants__clear" @click="onClearSearch">
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
</button>
|
|
21
21
|
</div>
|
|
22
22
|
<select v-model="statusFilter" class="sa-tenants__select" @change="applyFilter">
|
|
23
|
-
<option :value="null">
|
|
24
|
-
<option v-for="opt in
|
|
23
|
+
<option :value="null">{{ msg.list.allStatuses }}</option>
|
|
24
|
+
<option v-for="opt in resolvedStatusOptions" :key="opt.value" :value="opt.value">
|
|
25
25
|
{{ opt.label }}
|
|
26
26
|
</option>
|
|
27
27
|
</select>
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
class="sa-tenants__select"
|
|
32
32
|
@change="applyFilter"
|
|
33
33
|
>
|
|
34
|
-
<option :value="null">{{
|
|
34
|
+
<option :value="null">{{ resolvedPlanFilterLabel }}</option>
|
|
35
35
|
<option v-for="p in planOptions" :key="p" :value="p">{{ p }}</option>
|
|
36
36
|
</select>
|
|
37
37
|
<slot name="filters-extra" />
|
|
38
38
|
</div>
|
|
39
39
|
|
|
40
40
|
<q-banner v-if="error" class="bg-red-1 text-red-9 q-mb-md" rounded>
|
|
41
|
-
<strong>
|
|
41
|
+
<strong>{{ common.error }}:</strong> {{ error.message }}
|
|
42
42
|
</q-banner>
|
|
43
43
|
|
|
44
44
|
<div class="sa-tenants__card">
|
|
@@ -46,11 +46,13 @@
|
|
|
46
46
|
<table>
|
|
47
47
|
<thead>
|
|
48
48
|
<tr>
|
|
49
|
-
<th>
|
|
50
|
-
<th v-if="showPlanColumn">{{
|
|
51
|
-
<th>
|
|
52
|
-
<th v-if="usageFields.length > 0" class="num">
|
|
53
|
-
|
|
49
|
+
<th>{{ msg.tenant }}</th>
|
|
50
|
+
<th v-if="showPlanColumn">{{ resolvedPlanColumnLabel }}</th>
|
|
51
|
+
<th>{{ common.status }}</th>
|
|
52
|
+
<th v-if="usageFields.length > 0" class="num">
|
|
53
|
+
{{ msg.list.columnUsage }}
|
|
54
|
+
</th>
|
|
55
|
+
<th class="num">{{ msg.list.columnCreatedAt }}</th>
|
|
54
56
|
<th v-if="hasActions" class="num"></th>
|
|
55
57
|
</tr>
|
|
56
58
|
</thead>
|
|
@@ -100,7 +102,7 @@
|
|
|
100
102
|
</div>
|
|
101
103
|
</td>
|
|
102
104
|
<td class="num sa-tenants__mono">
|
|
103
|
-
{{
|
|
105
|
+
{{ formatCreatedAt(row) }}
|
|
104
106
|
</td>
|
|
105
107
|
<td v-if="hasActions" class="num sa-tenants__actions">
|
|
106
108
|
<slot name="row-actions" :row="row" :actions="visibleActions(row)">
|
|
@@ -127,17 +129,16 @@
|
|
|
127
129
|
</table>
|
|
128
130
|
<div v-if="items.length === 0 && !loading" class="sa-tenants__empty">
|
|
129
131
|
<q-icon name="search_off" size="32px" />
|
|
130
|
-
<div>
|
|
132
|
+
<div>{{ msg.list.empty }}</div>
|
|
131
133
|
</div>
|
|
132
134
|
<div v-if="loading" class="sa-tenants__loading">
|
|
133
|
-
<q-spinner size="20px" />
|
|
135
|
+
<q-spinner size="20px" /> {{ common.loading }}
|
|
134
136
|
</div>
|
|
135
137
|
</div>
|
|
136
138
|
</div>
|
|
137
139
|
|
|
138
140
|
<footer class="sa-tenants__foot">
|
|
139
|
-
{{
|
|
140
|
-
{{ Math.max(1, Math.ceil(total / pageSize)) }}
|
|
141
|
+
{{ paginationLabel }}
|
|
141
142
|
</footer>
|
|
142
143
|
|
|
143
144
|
<!-- Manifest-Driven Action Flow: only mounted when the `manifest` prop
|
|
@@ -166,27 +167,19 @@
|
|
|
166
167
|
|
|
167
168
|
<script setup lang="ts">
|
|
168
169
|
import { computed, ref, watch } from 'vue';
|
|
169
|
-
import type {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
} from '@saasicat/types';
|
|
175
|
-
import type { HttpClient } from '../types.js';
|
|
176
|
-
import { useTenants } from '../use-tenants.js';
|
|
170
|
+
import type { AdminManifest, TenantActionDef, TenantDto, TenantListFilter } from '@saasicat/types';
|
|
171
|
+
import type { HttpClient } from '../client/types.js';
|
|
172
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
173
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
174
|
+
import { useTenants } from '../vue/use-tenants.js';
|
|
177
175
|
import {
|
|
178
176
|
usePlatformTenantActions,
|
|
179
177
|
type PlatformTenantActionTone,
|
|
180
|
-
} from '../use-platform-tenant-actions.js';
|
|
178
|
+
} from '../vue/use-platform-tenant-actions.js';
|
|
181
179
|
import MfaPromptDialog from '../components/MfaPromptDialog.vue';
|
|
182
180
|
import TenantActionConfirmDialog from '../components/TenantActionConfirmDialog.vue';
|
|
183
181
|
import StatusPill, { type PillTone } from './tenants/StatusPill.vue';
|
|
184
|
-
import {
|
|
185
|
-
DEFAULT_PLAN_ACCENTS,
|
|
186
|
-
formatDateDe,
|
|
187
|
-
planAccent,
|
|
188
|
-
tenantInitials,
|
|
189
|
-
} from './tenants/format.js';
|
|
182
|
+
import { DEFAULT_PLAN_ACCENTS, formatDate, planAccent, tenantInitials } from './tenants/format.js';
|
|
190
183
|
|
|
191
184
|
// Platform standard page: tenant list.
|
|
192
185
|
//
|
|
@@ -196,7 +189,7 @@ import {
|
|
|
196
189
|
//
|
|
197
190
|
// App-specific bits via props:
|
|
198
191
|
// - `subtitle` : header subtitle
|
|
199
|
-
// - `statusOptions` : filter dropdown values (default:
|
|
192
|
+
// - `statusOptions` : filter dropdown values (default: active/suspended)
|
|
200
193
|
// - `planOptions` : filter dropdown values for plan; omitted = hides dropdown
|
|
201
194
|
// - `planAccents` : plan id → accent color (avatar + plan dot)
|
|
202
195
|
// - `planLabelField` : field for the plan display (e.g. "plan" or "bundleKey")
|
|
@@ -331,12 +324,6 @@ const props = withDefaults(
|
|
|
331
324
|
}>(),
|
|
332
325
|
{
|
|
333
326
|
pageSize: 25,
|
|
334
|
-
statusOptions: () => [
|
|
335
|
-
{ value: 'ACTIVE', label: 'Aktiv' },
|
|
336
|
-
{ value: 'INACTIVE', label: 'Suspendiert' },
|
|
337
|
-
],
|
|
338
|
-
planFilterLabel: 'Alle Pläne',
|
|
339
|
-
planColumnLabel: 'Plan',
|
|
340
327
|
showPlanColumn: true,
|
|
341
328
|
planLabelField: 'plan',
|
|
342
329
|
planAccents: () => DEFAULT_PLAN_ACCENTS,
|
|
@@ -345,6 +332,20 @@ const props = withDefaults(
|
|
|
345
332
|
},
|
|
346
333
|
);
|
|
347
334
|
|
|
335
|
+
const msg = useSaMessages('tenants');
|
|
336
|
+
const common = useSaMessages('common');
|
|
337
|
+
const { intlLocale } = useSuperAdminI18n();
|
|
338
|
+
|
|
339
|
+
const resolvedStatusOptions = computed<ReadonlyArray<{ value: string; label: string }>>(
|
|
340
|
+
() =>
|
|
341
|
+
props.statusOptions ?? [
|
|
342
|
+
{ value: 'ACTIVE', label: common.value.active },
|
|
343
|
+
{ value: 'INACTIVE', label: msg.value.list.suspended },
|
|
344
|
+
],
|
|
345
|
+
);
|
|
346
|
+
const resolvedPlanFilterLabel = computed(() => props.planFilterLabel ?? msg.value.list.allPlans);
|
|
347
|
+
const resolvedPlanColumnLabel = computed(() => props.planColumnLabel ?? msg.value.plan);
|
|
348
|
+
|
|
348
349
|
const filter = ref<TenantListFilter>({});
|
|
349
350
|
const searchInput = ref('');
|
|
350
351
|
const statusFilter = ref<string | null>(null);
|
|
@@ -360,6 +361,14 @@ const { items, page, total, loading, error, goToPage, setPageSize } = list;
|
|
|
360
361
|
|
|
361
362
|
setPageSize(props.pageSize);
|
|
362
363
|
|
|
364
|
+
const paginationLabel = computed(() =>
|
|
365
|
+
formatMessage(msg.value.list.pagination, {
|
|
366
|
+
total: total.value,
|
|
367
|
+
page: page.value,
|
|
368
|
+
pages: Math.max(1, Math.ceil(total.value / props.pageSize)),
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
|
|
363
372
|
let searchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
364
373
|
function reloadDebounced(): void {
|
|
365
374
|
if (searchTimer) clearTimeout(searchTimer);
|
|
@@ -427,7 +436,7 @@ if (manifestFlow && typeof window !== 'undefined') {
|
|
|
427
436
|
if (orphans.length > 0) {
|
|
428
437
|
// eslint-disable-next-line no-console
|
|
429
438
|
console.warn(
|
|
430
|
-
'[PlatformTenantsPage] Manifest
|
|
439
|
+
'[PlatformTenantsPage] Manifest actions without a handler in createSuperAdminApp({ actions }):',
|
|
431
440
|
orphans,
|
|
432
441
|
);
|
|
433
442
|
}
|
|
@@ -449,8 +458,8 @@ const combinedActions = computed<TenantRowAction[]>(() => {
|
|
|
449
458
|
if (typeof window !== 'undefined') {
|
|
450
459
|
// eslint-disable-next-line no-console
|
|
451
460
|
console.warn(
|
|
452
|
-
`[PlatformTenantsPage] Custom
|
|
453
|
-
`
|
|
461
|
+
`[PlatformTenantsPage] Custom action "${a.id}" (actionKey="${a.actionKey}") ` +
|
|
462
|
+
`collides with a manifest action — the manifest wins.`,
|
|
454
463
|
);
|
|
455
464
|
}
|
|
456
465
|
return false;
|
|
@@ -488,14 +497,18 @@ function usageStr(row: TenantRow, key: string): string | undefined {
|
|
|
488
497
|
return typeof v === 'string' ? v : undefined;
|
|
489
498
|
}
|
|
490
499
|
|
|
500
|
+
function formatCreatedAt(row: TenantRow): string {
|
|
501
|
+
return formatDate(usageStr(row, 'createdAt'), intlLocale.value);
|
|
502
|
+
}
|
|
503
|
+
|
|
491
504
|
function resolvedPills(row: TenantRow): StatusPillDef[] {
|
|
492
505
|
if (props.pillsForRow) return props.pillsForRow(row);
|
|
493
|
-
// Default: only
|
|
506
|
+
// Default: only active/suspended.
|
|
494
507
|
const isActive = (row as TenantDto).isActive;
|
|
495
508
|
return [
|
|
496
509
|
isActive
|
|
497
|
-
? { label:
|
|
498
|
-
: { label:
|
|
510
|
+
? { label: common.value.active, icon: 'check_circle', tone: 'positive' }
|
|
511
|
+
: { label: msg.value.list.suspended, icon: 'block', tone: 'negative' },
|
|
499
512
|
];
|
|
500
513
|
}
|
|
501
514
|
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
<div class="sa-users">
|
|
3
3
|
<header class="sa-page-head">
|
|
4
4
|
<div>
|
|
5
|
-
<h1 class="sa-page-head__title">{{
|
|
5
|
+
<h1 class="sa-page-head__title">{{ resolvedTitle }}</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">
|
|
9
|
-
<q-btn
|
|
9
|
+
<q-btn
|
|
10
|
+
unelevated
|
|
11
|
+
color="primary"
|
|
12
|
+
icon="search"
|
|
13
|
+
:label="common.search"
|
|
14
|
+
@click="reload"
|
|
15
|
+
/>
|
|
10
16
|
</div>
|
|
11
17
|
</header>
|
|
12
18
|
|
|
@@ -31,7 +37,7 @@
|
|
|
31
37
|
v-model="filter.q"
|
|
32
38
|
outlined
|
|
33
39
|
dense
|
|
34
|
-
label="
|
|
40
|
+
:label="msg.filterQuery"
|
|
35
41
|
clearable
|
|
36
42
|
@keyup.enter="reload"
|
|
37
43
|
@clear="reload"
|
|
@@ -40,7 +46,7 @@
|
|
|
40
46
|
v-model="filter.tenant"
|
|
41
47
|
outlined
|
|
42
48
|
dense
|
|
43
|
-
label="
|
|
49
|
+
:label="msg.filterTenant"
|
|
44
50
|
clearable
|
|
45
51
|
@keyup.enter="reload"
|
|
46
52
|
@clear="reload"
|
|
@@ -62,12 +68,12 @@
|
|
|
62
68
|
<q-td>
|
|
63
69
|
<q-badge
|
|
64
70
|
:color="row.isActive ? 'positive' : 'grey'"
|
|
65
|
-
:label="row.isActive ?
|
|
71
|
+
:label="row.isActive ? msg.statusActive : msg.statusDeactivated"
|
|
66
72
|
/>
|
|
67
73
|
<q-badge
|
|
68
74
|
v-if="row.invitationStatus === 'PENDING'"
|
|
69
75
|
color="amber-7"
|
|
70
|
-
label="
|
|
76
|
+
:label="msg.badgePending"
|
|
71
77
|
class="q-ml-xs"
|
|
72
78
|
/>
|
|
73
79
|
</q-td>
|
|
@@ -106,7 +112,10 @@
|
|
|
106
112
|
<script setup lang="ts">
|
|
107
113
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
108
114
|
import { useQuasar } from 'quasar';
|
|
115
|
+
import { useSuperAdminNotify } from '../quasar/notify.js';
|
|
109
116
|
import MfaPromptDialog from '../components/MfaPromptDialog.vue';
|
|
117
|
+
import { formatMessage } from '../client/i18n/format.js';
|
|
118
|
+
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
110
119
|
|
|
111
120
|
// Platform standard page: user search. Data-agnostic.
|
|
112
121
|
//
|
|
@@ -165,13 +174,19 @@ const props = withDefaults(
|
|
|
165
174
|
submitDeactivate?: (id: string, reason: string, mfaCode?: string) => Promise<void>;
|
|
166
175
|
}>(),
|
|
167
176
|
{
|
|
168
|
-
title: 'Benutzer',
|
|
169
177
|
requireMfaForResetPassword: false,
|
|
170
178
|
requireMfaForDeactivate: false,
|
|
171
179
|
},
|
|
172
180
|
);
|
|
173
181
|
|
|
174
182
|
const q = useQuasar();
|
|
183
|
+
const notify = useSuperAdminNotify();
|
|
184
|
+
const msg = useSaMessages('users');
|
|
185
|
+
const common = useSaMessages('common');
|
|
186
|
+
const shell = useSaMessages('shell');
|
|
187
|
+
const { intlLocale } = useSuperAdminI18n();
|
|
188
|
+
|
|
189
|
+
const resolvedTitle = computed(() => props.title ?? msg.value.title);
|
|
175
190
|
const rows = ref<UserRow[]>([]);
|
|
176
191
|
const loading = ref(false);
|
|
177
192
|
const filter = reactive({ q: '', tenant: '' });
|
|
@@ -254,42 +269,42 @@ const statTiles = computed<
|
|
|
254
269
|
if (isSuperAdmin(r)) supr++;
|
|
255
270
|
}
|
|
256
271
|
return [
|
|
257
|
-
{ id: 'all', label:
|
|
258
|
-
{ id: 'active', label:
|
|
259
|
-
{ id: 'blocked', label:
|
|
260
|
-
{ id: 'never', label:
|
|
261
|
-
{ id: 'super', label:
|
|
272
|
+
{ id: 'all', label: common.value.all, count: rows.value.length },
|
|
273
|
+
{ id: 'active', label: common.value.active, count: active, tone: 'positive' },
|
|
274
|
+
{ id: 'blocked', label: msg.value.tiles.blocked, count: blocked, tone: 'muted' },
|
|
275
|
+
{ id: 'never', label: msg.value.tiles.neverLoggedIn, count: never, tone: 'warn' },
|
|
276
|
+
{ id: 'super', label: msg.value.tiles.superAdmins, count: supr, tone: 'purple' },
|
|
262
277
|
];
|
|
263
278
|
});
|
|
264
279
|
|
|
265
|
-
const baseColumns = [
|
|
280
|
+
const baseColumns = computed(() => [
|
|
266
281
|
{
|
|
267
282
|
name: 'email',
|
|
268
|
-
label:
|
|
283
|
+
label: msg.value.columns.email,
|
|
269
284
|
field: 'email',
|
|
270
285
|
align: 'left' as const,
|
|
271
286
|
sortable: true,
|
|
272
287
|
},
|
|
273
288
|
{
|
|
274
289
|
name: 'name',
|
|
275
|
-
label:
|
|
290
|
+
label: common.value.name,
|
|
276
291
|
field: (r: UserRow) => `${r.firstName} ${r.lastName}`,
|
|
277
292
|
align: 'left' as const,
|
|
278
293
|
},
|
|
279
294
|
{
|
|
280
295
|
name: 'tenant',
|
|
281
|
-
label:
|
|
282
|
-
field: (r: UserRow) => r.tenantSlug ??
|
|
296
|
+
label: msg.value.columns.tenant,
|
|
297
|
+
field: (r: UserRow) => r.tenantSlug ?? msg.value.tenantFallback,
|
|
283
298
|
align: 'left' as const,
|
|
284
299
|
},
|
|
285
|
-
{ name: 'role', label:
|
|
286
|
-
{ name: 'status', label:
|
|
300
|
+
{ name: 'role', label: msg.value.columns.role, field: 'role', align: 'left' as const },
|
|
301
|
+
{ name: 'status', label: common.value.status, field: 'isActive', align: 'left' as const },
|
|
287
302
|
{
|
|
288
303
|
name: 'lastLogin',
|
|
289
|
-
label:
|
|
304
|
+
label: msg.value.columns.lastLogin,
|
|
290
305
|
field: (r: UserRow) =>
|
|
291
306
|
r.lastLoginAt
|
|
292
|
-
? new Date(r.lastLoginAt).toLocaleDateString(
|
|
307
|
+
? new Date(r.lastLoginAt).toLocaleDateString(intlLocale.value, {
|
|
293
308
|
day: '2-digit',
|
|
294
309
|
month: 'short',
|
|
295
310
|
year: 'numeric',
|
|
@@ -297,7 +312,7 @@ const baseColumns = [
|
|
|
297
312
|
: '—',
|
|
298
313
|
align: 'left' as const,
|
|
299
314
|
},
|
|
300
|
-
];
|
|
315
|
+
]);
|
|
301
316
|
|
|
302
317
|
// Built-in default actions — APPENDED to consumer actions, not replaced.
|
|
303
318
|
const bakedActions = computed<UserRowAction[]>(() => {
|
|
@@ -305,7 +320,7 @@ const bakedActions = computed<UserRowAction[]>(() => {
|
|
|
305
320
|
if (props.enableResetPassword && props.submitResetPassword) {
|
|
306
321
|
out.push({
|
|
307
322
|
id: 'reset-password',
|
|
308
|
-
label:
|
|
323
|
+
label: msg.value.resetPassword.action,
|
|
309
324
|
icon: 'lock_reset',
|
|
310
325
|
color: 'primary',
|
|
311
326
|
handler: (row) => onResetPasswordClick(row),
|
|
@@ -314,7 +329,7 @@ const bakedActions = computed<UserRowAction[]>(() => {
|
|
|
314
329
|
if (props.enableDeactivate && props.submitDeactivate) {
|
|
315
330
|
out.push({
|
|
316
331
|
id: 'deactivate',
|
|
317
|
-
label:
|
|
332
|
+
label: msg.value.deactivate.action,
|
|
318
333
|
icon: 'block',
|
|
319
334
|
color: 'negative',
|
|
320
335
|
condition: (row) => (row as UserRow & { status?: string }).status !== 'suspended',
|
|
@@ -330,7 +345,7 @@ const mergedActions = computed<readonly UserRowAction[]>(() => [
|
|
|
330
345
|
]);
|
|
331
346
|
|
|
332
347
|
const effectiveColumns = computed(() => {
|
|
333
|
-
const cols = [...baseColumns];
|
|
348
|
+
const cols = [...baseColumns.value];
|
|
334
349
|
if (mergedActions.value.length > 0) {
|
|
335
350
|
cols.push({
|
|
336
351
|
name: 'actions',
|
|
@@ -369,7 +384,7 @@ function errMsg(err: unknown): string {
|
|
|
369
384
|
return (
|
|
370
385
|
(err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
|
|
371
386
|
(err as Error)?.message ??
|
|
372
|
-
|
|
387
|
+
msg.value.errorAction
|
|
373
388
|
);
|
|
374
389
|
}
|
|
375
390
|
|
|
@@ -387,7 +402,7 @@ async function runAction<R>(
|
|
|
387
402
|
const result = await invoke('');
|
|
388
403
|
onSuccess(result);
|
|
389
404
|
} catch (err) {
|
|
390
|
-
|
|
405
|
+
notify('negative', errMsg(err));
|
|
391
406
|
}
|
|
392
407
|
return;
|
|
393
408
|
}
|
|
@@ -402,11 +417,11 @@ async function runAction<R>(
|
|
|
402
417
|
} catch (err) {
|
|
403
418
|
const status = (err as { response?: { status?: number } })?.response?.status;
|
|
404
419
|
if (status === 401) {
|
|
405
|
-
mfaError.value =
|
|
420
|
+
mfaError.value = shell.value.mfa.invalidCode;
|
|
406
421
|
continue;
|
|
407
422
|
}
|
|
408
423
|
showMfa.value = false;
|
|
409
|
-
|
|
424
|
+
notify('negative', errMsg(err));
|
|
410
425
|
return;
|
|
411
426
|
}
|
|
412
427
|
}
|
|
@@ -416,15 +431,18 @@ function onResetPasswordClick(row: UserRow): void {
|
|
|
416
431
|
if (!props.submitResetPassword) return;
|
|
417
432
|
const submit = props.submitResetPassword;
|
|
418
433
|
q.dialog({
|
|
419
|
-
title:
|
|
420
|
-
message:
|
|
434
|
+
title: formatMessage(msg.value.resetPassword.dialogTitle, { email: row.email }),
|
|
435
|
+
message: msg.value.reasonPrompt,
|
|
421
436
|
prompt: { model: '', type: 'text' },
|
|
422
|
-
cancel:
|
|
423
|
-
ok: { label:
|
|
437
|
+
cancel: common.value.cancel,
|
|
438
|
+
ok: { label: common.value.reset, color: 'primary' },
|
|
424
439
|
}).onOk(async (reason: string) => {
|
|
425
440
|
if (!reason || reason.trim().length === 0) return;
|
|
426
441
|
await runAction(
|
|
427
|
-
|
|
442
|
+
formatMessage(msg.value.resetPassword.mfaDescription, {
|
|
443
|
+
email: row.email,
|
|
444
|
+
reason: reason.trim(),
|
|
445
|
+
}),
|
|
428
446
|
!!props.requireMfaForResetPassword,
|
|
429
447
|
(code) => submit(row.id, reason, code),
|
|
430
448
|
(data) => {
|
|
@@ -433,16 +451,14 @@ function onResetPasswordClick(row: UserRow): void {
|
|
|
433
451
|
// void → Notify only.
|
|
434
452
|
if (data && typeof data === 'object' && 'oneTimePassword' in data) {
|
|
435
453
|
q.dialog({
|
|
436
|
-
title:
|
|
437
|
-
message:
|
|
438
|
-
|
|
454
|
+
title: msg.value.resetPassword.otpTitle,
|
|
455
|
+
message: formatMessage(msg.value.resetPassword.otpMessage, {
|
|
456
|
+
password: data.oneTimePassword,
|
|
457
|
+
}),
|
|
458
|
+
ok: { label: msg.value.resetPassword.otpAcknowledge },
|
|
439
459
|
});
|
|
440
460
|
} else {
|
|
441
|
-
|
|
442
|
-
type: 'positive',
|
|
443
|
-
message: 'Passwort-Reset ausgelöst.',
|
|
444
|
-
position: 'top',
|
|
445
|
-
});
|
|
461
|
+
notify('positive', msg.value.resetPassword.success);
|
|
446
462
|
}
|
|
447
463
|
void reload();
|
|
448
464
|
},
|
|
@@ -454,23 +470,25 @@ function onDeactivateClick(row: UserRow): void {
|
|
|
454
470
|
if (!props.submitDeactivate) return;
|
|
455
471
|
const submit = props.submitDeactivate;
|
|
456
472
|
q.dialog({
|
|
457
|
-
title:
|
|
458
|
-
message:
|
|
473
|
+
title: formatMessage(msg.value.deactivate.dialogTitle, { email: row.email }),
|
|
474
|
+
message: msg.value.reasonPrompt,
|
|
459
475
|
prompt: { model: '', type: 'text' },
|
|
460
|
-
cancel:
|
|
461
|
-
ok: { label:
|
|
476
|
+
cancel: common.value.cancel,
|
|
477
|
+
ok: { label: msg.value.deactivate.action, color: 'negative' },
|
|
462
478
|
}).onOk(async (reason: string) => {
|
|
463
479
|
if (!reason || reason.trim().length === 0) return;
|
|
464
480
|
await runAction(
|
|
465
|
-
|
|
481
|
+
formatMessage(msg.value.deactivate.mfaDescription, {
|
|
482
|
+
email: row.email,
|
|
483
|
+
reason: reason.trim(),
|
|
484
|
+
}),
|
|
466
485
|
!!props.requireMfaForDeactivate,
|
|
467
486
|
(code) => submit(row.id, reason, code),
|
|
468
487
|
() => {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
});
|
|
488
|
+
notify(
|
|
489
|
+
'positive',
|
|
490
|
+
formatMessage(msg.value.deactivate.success, { email: row.email }),
|
|
491
|
+
);
|
|
474
492
|
void reload();
|
|
475
493
|
},
|
|
476
494
|
);
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
{{ statusLabel(bundle) }}
|
|
20
20
|
</span>
|
|
21
21
|
<span v-if="i18nLocaleCount(bundle) > 0" class="sa-chip sa-chip--info">
|
|
22
|
-
{{
|
|
22
|
+
{{ translationCount(bundle) }}
|
|
23
23
|
</span>
|
|
24
24
|
</div>
|
|
25
25
|
<div class="sa-bd-card__name">{{ bundle.label }}</div>
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
</div>
|
|
39
39
|
|
|
40
40
|
<div v-if="bundlesTotal > 0 && filteredBundles.length === 0" class="sa-bd-empty-row">
|
|
41
|
-
|
|
41
|
+
{{ msg.list.emptyNoMatch }}
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
44
44
|
</template>
|
|
@@ -46,9 +46,11 @@
|
|
|
46
46
|
<script setup lang="ts">
|
|
47
47
|
import type { BundleRow } from '@saasicat/types';
|
|
48
48
|
import {
|
|
49
|
-
|
|
49
|
+
bundleStatusMeta,
|
|
50
50
|
type BundleAggregateStatus,
|
|
51
51
|
} from '../../components/bundle-editor/bundle-version-status.js';
|
|
52
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
53
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
52
54
|
|
|
53
55
|
const props = defineProps<{
|
|
54
56
|
filteredBundles: BundleRow[];
|
|
@@ -66,17 +68,27 @@ const emit = defineEmits<{
|
|
|
66
68
|
toggle: [bundle: BundleRow];
|
|
67
69
|
}>();
|
|
68
70
|
|
|
71
|
+
const msg = useSaMessages('bundles');
|
|
72
|
+
const { locale } = useSuperAdminI18n();
|
|
73
|
+
|
|
74
|
+
function statusMetaOf(bundle: BundleRow) {
|
|
75
|
+
return bundleStatusMeta(props.aggregateStatusOf(bundle), locale.value);
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
function statusClass(bundle: BundleRow): string {
|
|
70
|
-
return
|
|
79
|
+
return statusMetaOf(bundle).cls;
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
function statusLabel(bundle: BundleRow): string {
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
return statusMetaOf(bundle).label;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function statusTooltip(bundle: BundleRow): string {
|
|
87
|
+
return statusMetaOf(bundle).tooltip;
|
|
76
88
|
}
|
|
77
89
|
|
|
78
|
-
function
|
|
79
|
-
return
|
|
90
|
+
function translationCount(bundle: BundleRow): string {
|
|
91
|
+
return formatMessage(msg.value.list.translationCount, { count: props.i18nLocaleCount(bundle) });
|
|
80
92
|
}
|
|
81
93
|
</script>
|
|
82
94
|
|