@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,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>
|
|
@@ -108,6 +114,8 @@ import { computed, onMounted, reactive, ref } from 'vue';
|
|
|
108
114
|
import { useQuasar } from 'quasar';
|
|
109
115
|
import { useSuperAdminNotify } from '../quasar/notify.js';
|
|
110
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';
|
|
111
119
|
|
|
112
120
|
// Platform standard page: user search. Data-agnostic.
|
|
113
121
|
//
|
|
@@ -166,7 +174,6 @@ const props = withDefaults(
|
|
|
166
174
|
submitDeactivate?: (id: string, reason: string, mfaCode?: string) => Promise<void>;
|
|
167
175
|
}>(),
|
|
168
176
|
{
|
|
169
|
-
title: 'Benutzer',
|
|
170
177
|
requireMfaForResetPassword: false,
|
|
171
178
|
requireMfaForDeactivate: false,
|
|
172
179
|
},
|
|
@@ -174,6 +181,12 @@ const props = withDefaults(
|
|
|
174
181
|
|
|
175
182
|
const q = useQuasar();
|
|
176
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);
|
|
177
190
|
const rows = ref<UserRow[]>([]);
|
|
178
191
|
const loading = ref(false);
|
|
179
192
|
const filter = reactive({ q: '', tenant: '' });
|
|
@@ -256,42 +269,42 @@ const statTiles = computed<
|
|
|
256
269
|
if (isSuperAdmin(r)) supr++;
|
|
257
270
|
}
|
|
258
271
|
return [
|
|
259
|
-
{ id: 'all', label:
|
|
260
|
-
{ id: 'active', label:
|
|
261
|
-
{ id: 'blocked', label:
|
|
262
|
-
{ id: 'never', label:
|
|
263
|
-
{ 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' },
|
|
264
277
|
];
|
|
265
278
|
});
|
|
266
279
|
|
|
267
|
-
const baseColumns = [
|
|
280
|
+
const baseColumns = computed(() => [
|
|
268
281
|
{
|
|
269
282
|
name: 'email',
|
|
270
|
-
label:
|
|
283
|
+
label: msg.value.columns.email,
|
|
271
284
|
field: 'email',
|
|
272
285
|
align: 'left' as const,
|
|
273
286
|
sortable: true,
|
|
274
287
|
},
|
|
275
288
|
{
|
|
276
289
|
name: 'name',
|
|
277
|
-
label:
|
|
290
|
+
label: common.value.name,
|
|
278
291
|
field: (r: UserRow) => `${r.firstName} ${r.lastName}`,
|
|
279
292
|
align: 'left' as const,
|
|
280
293
|
},
|
|
281
294
|
{
|
|
282
295
|
name: 'tenant',
|
|
283
|
-
label:
|
|
284
|
-
field: (r: UserRow) => r.tenantSlug ??
|
|
296
|
+
label: msg.value.columns.tenant,
|
|
297
|
+
field: (r: UserRow) => r.tenantSlug ?? msg.value.tenantFallback,
|
|
285
298
|
align: 'left' as const,
|
|
286
299
|
},
|
|
287
|
-
{ name: 'role', label:
|
|
288
|
-
{ 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 },
|
|
289
302
|
{
|
|
290
303
|
name: 'lastLogin',
|
|
291
|
-
label:
|
|
304
|
+
label: msg.value.columns.lastLogin,
|
|
292
305
|
field: (r: UserRow) =>
|
|
293
306
|
r.lastLoginAt
|
|
294
|
-
? new Date(r.lastLoginAt).toLocaleDateString(
|
|
307
|
+
? new Date(r.lastLoginAt).toLocaleDateString(intlLocale.value, {
|
|
295
308
|
day: '2-digit',
|
|
296
309
|
month: 'short',
|
|
297
310
|
year: 'numeric',
|
|
@@ -299,7 +312,7 @@ const baseColumns = [
|
|
|
299
312
|
: '—',
|
|
300
313
|
align: 'left' as const,
|
|
301
314
|
},
|
|
302
|
-
];
|
|
315
|
+
]);
|
|
303
316
|
|
|
304
317
|
// Built-in default actions — APPENDED to consumer actions, not replaced.
|
|
305
318
|
const bakedActions = computed<UserRowAction[]>(() => {
|
|
@@ -307,7 +320,7 @@ const bakedActions = computed<UserRowAction[]>(() => {
|
|
|
307
320
|
if (props.enableResetPassword && props.submitResetPassword) {
|
|
308
321
|
out.push({
|
|
309
322
|
id: 'reset-password',
|
|
310
|
-
label:
|
|
323
|
+
label: msg.value.resetPassword.action,
|
|
311
324
|
icon: 'lock_reset',
|
|
312
325
|
color: 'primary',
|
|
313
326
|
handler: (row) => onResetPasswordClick(row),
|
|
@@ -316,7 +329,7 @@ const bakedActions = computed<UserRowAction[]>(() => {
|
|
|
316
329
|
if (props.enableDeactivate && props.submitDeactivate) {
|
|
317
330
|
out.push({
|
|
318
331
|
id: 'deactivate',
|
|
319
|
-
label:
|
|
332
|
+
label: msg.value.deactivate.action,
|
|
320
333
|
icon: 'block',
|
|
321
334
|
color: 'negative',
|
|
322
335
|
condition: (row) => (row as UserRow & { status?: string }).status !== 'suspended',
|
|
@@ -332,7 +345,7 @@ const mergedActions = computed<readonly UserRowAction[]>(() => [
|
|
|
332
345
|
]);
|
|
333
346
|
|
|
334
347
|
const effectiveColumns = computed(() => {
|
|
335
|
-
const cols = [...baseColumns];
|
|
348
|
+
const cols = [...baseColumns.value];
|
|
336
349
|
if (mergedActions.value.length > 0) {
|
|
337
350
|
cols.push({
|
|
338
351
|
name: 'actions',
|
|
@@ -371,7 +384,7 @@ function errMsg(err: unknown): string {
|
|
|
371
384
|
return (
|
|
372
385
|
(err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
|
|
373
386
|
(err as Error)?.message ??
|
|
374
|
-
|
|
387
|
+
msg.value.errorAction
|
|
375
388
|
);
|
|
376
389
|
}
|
|
377
390
|
|
|
@@ -404,7 +417,7 @@ async function runAction<R>(
|
|
|
404
417
|
} catch (err) {
|
|
405
418
|
const status = (err as { response?: { status?: number } })?.response?.status;
|
|
406
419
|
if (status === 401) {
|
|
407
|
-
mfaError.value =
|
|
420
|
+
mfaError.value = shell.value.mfa.invalidCode;
|
|
408
421
|
continue;
|
|
409
422
|
}
|
|
410
423
|
showMfa.value = false;
|
|
@@ -418,15 +431,18 @@ function onResetPasswordClick(row: UserRow): void {
|
|
|
418
431
|
if (!props.submitResetPassword) return;
|
|
419
432
|
const submit = props.submitResetPassword;
|
|
420
433
|
q.dialog({
|
|
421
|
-
title:
|
|
422
|
-
message:
|
|
434
|
+
title: formatMessage(msg.value.resetPassword.dialogTitle, { email: row.email }),
|
|
435
|
+
message: msg.value.reasonPrompt,
|
|
423
436
|
prompt: { model: '', type: 'text' },
|
|
424
|
-
cancel:
|
|
425
|
-
ok: { label:
|
|
437
|
+
cancel: common.value.cancel,
|
|
438
|
+
ok: { label: common.value.reset, color: 'primary' },
|
|
426
439
|
}).onOk(async (reason: string) => {
|
|
427
440
|
if (!reason || reason.trim().length === 0) return;
|
|
428
441
|
await runAction(
|
|
429
|
-
|
|
442
|
+
formatMessage(msg.value.resetPassword.mfaDescription, {
|
|
443
|
+
email: row.email,
|
|
444
|
+
reason: reason.trim(),
|
|
445
|
+
}),
|
|
430
446
|
!!props.requireMfaForResetPassword,
|
|
431
447
|
(code) => submit(row.id, reason, code),
|
|
432
448
|
(data) => {
|
|
@@ -435,12 +451,14 @@ function onResetPasswordClick(row: UserRow): void {
|
|
|
435
451
|
// void → Notify only.
|
|
436
452
|
if (data && typeof data === 'object' && 'oneTimePassword' in data) {
|
|
437
453
|
q.dialog({
|
|
438
|
-
title:
|
|
439
|
-
message:
|
|
440
|
-
|
|
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 },
|
|
441
459
|
});
|
|
442
460
|
} else {
|
|
443
|
-
notify('positive',
|
|
461
|
+
notify('positive', msg.value.resetPassword.success);
|
|
444
462
|
}
|
|
445
463
|
void reload();
|
|
446
464
|
},
|
|
@@ -452,19 +470,25 @@ function onDeactivateClick(row: UserRow): void {
|
|
|
452
470
|
if (!props.submitDeactivate) return;
|
|
453
471
|
const submit = props.submitDeactivate;
|
|
454
472
|
q.dialog({
|
|
455
|
-
title:
|
|
456
|
-
message:
|
|
473
|
+
title: formatMessage(msg.value.deactivate.dialogTitle, { email: row.email }),
|
|
474
|
+
message: msg.value.reasonPrompt,
|
|
457
475
|
prompt: { model: '', type: 'text' },
|
|
458
|
-
cancel:
|
|
459
|
-
ok: { label:
|
|
476
|
+
cancel: common.value.cancel,
|
|
477
|
+
ok: { label: msg.value.deactivate.action, color: 'negative' },
|
|
460
478
|
}).onOk(async (reason: string) => {
|
|
461
479
|
if (!reason || reason.trim().length === 0) return;
|
|
462
480
|
await runAction(
|
|
463
|
-
|
|
481
|
+
formatMessage(msg.value.deactivate.mfaDescription, {
|
|
482
|
+
email: row.email,
|
|
483
|
+
reason: reason.trim(),
|
|
484
|
+
}),
|
|
464
485
|
!!props.requireMfaForDeactivate,
|
|
465
486
|
(code) => submit(row.id, reason, code),
|
|
466
487
|
() => {
|
|
467
|
-
notify(
|
|
488
|
+
notify(
|
|
489
|
+
'positive',
|
|
490
|
+
formatMessage(msg.value.deactivate.success, { email: row.email }),
|
|
491
|
+
);
|
|
468
492
|
void reload();
|
|
469
493
|
},
|
|
470
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
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="sa-bd-grid">
|
|
3
3
|
<div class="sa-bd-col">
|
|
4
|
-
<div class="sa-bd-section-label">
|
|
4
|
+
<div class="sa-bd-section-label">{{ msg.fields.masterData }}</div>
|
|
5
5
|
<div class="sa-bundles__form">
|
|
6
6
|
<q-input
|
|
7
7
|
:model-value="editForm.label"
|
|
8
8
|
outlined
|
|
9
9
|
dense
|
|
10
|
-
label="
|
|
10
|
+
:label="msg.fields.label"
|
|
11
11
|
@update:model-value="(value) => patchEditForm({ label: String(value ?? '') })"
|
|
12
12
|
/>
|
|
13
13
|
<q-input
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
dense
|
|
17
17
|
type="textarea"
|
|
18
18
|
autogrow
|
|
19
|
-
label="
|
|
19
|
+
:label="common.description"
|
|
20
20
|
@update:model-value="
|
|
21
21
|
(value) => patchEditForm({ description: String(value ?? '') })
|
|
22
22
|
"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
:model-value="editForm.icon"
|
|
26
26
|
outlined
|
|
27
27
|
dense
|
|
28
|
-
label="
|
|
28
|
+
:label="common.icon"
|
|
29
29
|
@update:model-value="(value) => patchEditForm({ icon: String(value ?? '') })"
|
|
30
30
|
/>
|
|
31
31
|
<q-input
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
outlined
|
|
34
34
|
dense
|
|
35
35
|
type="number"
|
|
36
|
-
label="
|
|
36
|
+
:label="common.sortOrder"
|
|
37
37
|
@update:model-value="
|
|
38
38
|
(value) => patchEditForm({ sortOrder: Number(value ?? 0) })
|
|
39
39
|
"
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
</div>
|
|
42
42
|
|
|
43
43
|
<div class="sa-bd-section-label sa-bd-section-label--mt">
|
|
44
|
-
<span
|
|
45
|
-
<span class="sa-bd-section-count">{{
|
|
44
|
+
<span>{{ msg.detail.translations }}</span>
|
|
45
|
+
<span class="sa-bd-section-count">{{ languageCount }}</span>
|
|
46
46
|
</div>
|
|
47
47
|
<div v-if="translatableLocales.length === 0" class="sa-bd-i18n-hint">
|
|
48
|
-
|
|
48
|
+
{{ msg.detail.noTranslatableLocales }}
|
|
49
49
|
</div>
|
|
50
50
|
<div v-for="lng in translatableLocales" :key="lng" class="sa-bd-i18n-block">
|
|
51
51
|
<div class="sa-bd-i18n-head">
|
|
@@ -54,14 +54,14 @@
|
|
|
54
54
|
v-if="!i18nDraft[lng]?.label || !i18nDraft[lng]?.description"
|
|
55
55
|
class="sa-bd-i18n-fallback"
|
|
56
56
|
>
|
|
57
|
-
|
|
57
|
+
{{ msg.detail.fallbackFromDe }}
|
|
58
58
|
</span>
|
|
59
59
|
</div>
|
|
60
60
|
<q-input
|
|
61
61
|
outlined
|
|
62
62
|
dense
|
|
63
63
|
:model-value="i18nDraft[lng]?.label || ''"
|
|
64
|
-
:placeholder="
|
|
64
|
+
:placeholder="labelPlaceholder"
|
|
65
65
|
@update:model-value="
|
|
66
66
|
(value) => emit('setI18n', lng, 'label', String(value ?? ''))
|
|
67
67
|
"
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
type="textarea"
|
|
73
73
|
autogrow
|
|
74
74
|
:model-value="i18nDraft[lng]?.description || ''"
|
|
75
|
-
:placeholder="
|
|
75
|
+
:placeholder="msg.detail.descriptionPlaceholder"
|
|
76
76
|
@update:model-value="
|
|
77
77
|
(value) => emit('setI18n', lng, 'description', String(value ?? ''))
|
|
78
78
|
"
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
class="sa-bd-save"
|
|
84
84
|
unelevated
|
|
85
85
|
color="primary"
|
|
86
|
-
label="
|
|
86
|
+
:label="msg.detail.save"
|
|
87
87
|
:loading="editSubmitting"
|
|
88
88
|
@click="emit('submitEdit')"
|
|
89
89
|
/>
|
|
@@ -112,10 +112,7 @@
|
|
|
112
112
|
@save="(data) => emit('inlineSave', bundle.id, selectedVersion!.id, data)"
|
|
113
113
|
@discard="emit('discardVersion', bundle.id, selectedVersion!.id)"
|
|
114
114
|
/>
|
|
115
|
-
<div v-else class="sa-bd-empty-row">
|
|
116
|
-
Noch keine Version. Lege eine neue Version an, um Features, Quotas & Pricing zu
|
|
117
|
-
kuratieren.
|
|
118
|
-
</div>
|
|
115
|
+
<div v-else class="sa-bd-empty-row">{{ msg.detail.noVersion }}</div>
|
|
119
116
|
|
|
120
117
|
<div class="sa-bd-version-actions">
|
|
121
118
|
<q-btn
|
|
@@ -123,7 +120,7 @@
|
|
|
123
120
|
unelevated
|
|
124
121
|
color="positive"
|
|
125
122
|
icon="rocket_launch"
|
|
126
|
-
label="
|
|
123
|
+
:label="msg.detail.publishVersion"
|
|
127
124
|
@click="emit('publishVersion', selectedVersion)"
|
|
128
125
|
/>
|
|
129
126
|
<q-btn
|
|
@@ -132,7 +129,7 @@
|
|
|
132
129
|
dense
|
|
133
130
|
color="negative"
|
|
134
131
|
icon="delete"
|
|
135
|
-
label="
|
|
132
|
+
:label="msg.detail.softDelete"
|
|
136
133
|
@click="emit('deleteBundle', bundle)"
|
|
137
134
|
/>
|
|
138
135
|
</div>
|
|
@@ -141,6 +138,7 @@
|
|
|
141
138
|
</template>
|
|
142
139
|
|
|
143
140
|
<script setup lang="ts">
|
|
141
|
+
import { computed } from 'vue';
|
|
144
142
|
import type {
|
|
145
143
|
BundleRow,
|
|
146
144
|
BundleVersionRow,
|
|
@@ -154,6 +152,8 @@ import BundleVersionInlineEditor from '../../components/bundle-editor/BundleVers
|
|
|
154
152
|
import BundleVersionStrip from '../../components/bundle-editor/BundleVersionStrip.vue';
|
|
155
153
|
import type { FeatureMeta } from '../../components/bundle-editor/BundleFeaturesEditor.vue';
|
|
156
154
|
import type { QuotaMeta } from '../../components/bundle-editor/catalog-i18n.js';
|
|
155
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
156
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
157
157
|
import type { BundleEditForm } from './types.js';
|
|
158
158
|
|
|
159
159
|
const props = defineProps<{
|
|
@@ -187,6 +187,16 @@ const emit = defineEmits<{
|
|
|
187
187
|
deleteBundle: [bundle: BundleRow];
|
|
188
188
|
}>();
|
|
189
189
|
|
|
190
|
+
const msg = useSaMessages('bundles');
|
|
191
|
+
const common = useSaMessages('common');
|
|
192
|
+
|
|
193
|
+
const languageCount = computed(() =>
|
|
194
|
+
formatMessage(msg.value.detail.languageCount, { count: props.translatableLocales.length }),
|
|
195
|
+
);
|
|
196
|
+
const labelPlaceholder = computed(() =>
|
|
197
|
+
formatMessage(msg.value.detail.labelPlaceholder, { label: props.editForm.label }),
|
|
198
|
+
);
|
|
199
|
+
|
|
190
200
|
function patchEditForm(patch: Partial<BundleEditForm>): void {
|
|
191
201
|
emit('update:editForm', { ...props.editForm, ...patch });
|
|
192
202
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
dense
|
|
6
6
|
outlined
|
|
7
7
|
clearable
|
|
8
|
-
placeholder="
|
|
8
|
+
:placeholder="msg.filterBar.searchPlaceholder"
|
|
9
9
|
class="sa-bundles__search"
|
|
10
10
|
@update:model-value="emitQuery"
|
|
11
11
|
>
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
<script setup lang="ts">
|
|
28
28
|
import type { BundlesStatusFilter, BundlesStatusFilterOption } from './types.js';
|
|
29
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
29
30
|
|
|
30
31
|
defineProps<{
|
|
31
32
|
query: string;
|
|
@@ -33,6 +34,8 @@ defineProps<{
|
|
|
33
34
|
statusFilterOptions: BundlesStatusFilterOption[];
|
|
34
35
|
}>();
|
|
35
36
|
|
|
37
|
+
const msg = useSaMessages('bundles');
|
|
38
|
+
|
|
36
39
|
const emit = defineEmits<{
|
|
37
40
|
'update:query': [value: string];
|
|
38
41
|
'update:statusFilter': [value: BundlesStatusFilter];
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<header class="sa-bundles__head">
|
|
3
3
|
<div>
|
|
4
|
-
<h1 class="sa-bundles__title">
|
|
5
|
-
<p class="sa-bundles__sub">
|
|
6
|
-
Produktgruppen aus Features & Quotas — verwendet in Plänen. Bundles werden im
|
|
7
|
-
SuperAdmin kuratiert.
|
|
8
|
-
</p>
|
|
4
|
+
<h1 class="sa-bundles__title">{{ msg.header.title }}</h1>
|
|
5
|
+
<p class="sa-bundles__sub">{{ msg.header.subtitle }}</p>
|
|
9
6
|
</div>
|
|
10
7
|
<div class="sa-bundles__head-actions">
|
|
11
8
|
<q-select
|
|
@@ -17,7 +14,7 @@
|
|
|
17
14
|
emit-value
|
|
18
15
|
map-options
|
|
19
16
|
class="sa-bundles__locale"
|
|
20
|
-
label="
|
|
17
|
+
:label="msg.header.displayLocale"
|
|
21
18
|
@update:model-value="(value) => emit('update:displayLocale', String(value))"
|
|
22
19
|
>
|
|
23
20
|
<template #prepend><q-icon name="translate" size="18px" /></template>
|
|
@@ -26,11 +23,11 @@
|
|
|
26
23
|
unelevated
|
|
27
24
|
color="primary"
|
|
28
25
|
icon="add"
|
|
29
|
-
label="
|
|
26
|
+
:label="msg.header.newBundle"
|
|
30
27
|
@click="emit('create')"
|
|
31
28
|
/>
|
|
32
29
|
<q-btn flat icon="refresh" :loading="loading" @click="emit('refresh')">
|
|
33
|
-
<q-tooltip>
|
|
30
|
+
<q-tooltip>{{ msg.header.reload }}</q-tooltip>
|
|
34
31
|
</q-btn>
|
|
35
32
|
</div>
|
|
36
33
|
</header>
|
|
@@ -39,6 +36,8 @@
|
|
|
39
36
|
<script setup lang="ts">
|
|
40
37
|
import { computed } from 'vue';
|
|
41
38
|
|
|
39
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
40
|
+
|
|
42
41
|
// The language selector controls which locale feature/quota labels are resolved
|
|
43
42
|
// on the create form and in the detail view (fallback locale → DE → key). The default is the
|
|
44
43
|
// default locale (DE); further options come from the active project locales.
|
|
@@ -55,6 +54,8 @@ const emit = defineEmits<{
|
|
|
55
54
|
'update:displayLocale': [locale: string];
|
|
56
55
|
}>();
|
|
57
56
|
|
|
57
|
+
const msg = useSaMessages('bundles');
|
|
58
|
+
|
|
58
59
|
const localeOptions = computed(() =>
|
|
59
60
|
props.locales.map((locale) => ({ label: locale.toUpperCase(), value: locale })),
|
|
60
61
|
);
|
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="sa-bundles__kpis">
|
|
3
3
|
<div class="sa-bundles__kpi">
|
|
4
|
-
<div class="sa-bundles__kpi-label">
|
|
4
|
+
<div class="sa-bundles__kpi-label">{{ msg.kpis.total }}</div>
|
|
5
5
|
<div class="sa-bundles__kpi-value">{{ bundlesTotal }}</div>
|
|
6
|
-
<div class="sa-bundles__kpi-sub">
|
|
7
|
-
{{ liveCount }} live · {{ scheduledBundlesCount }} mit geplanter Version
|
|
8
|
-
</div>
|
|
6
|
+
<div class="sa-bundles__kpi-sub">{{ totalSub }}</div>
|
|
9
7
|
</div>
|
|
10
8
|
<div class="sa-bundles__kpi">
|
|
11
|
-
<div class="sa-bundles__kpi-label">
|
|
9
|
+
<div class="sa-bundles__kpi-label">{{ msg.kpis.scheduled }}</div>
|
|
12
10
|
<div class="sa-bundles__kpi-value">{{ totalScheduledVersions }}</div>
|
|
13
|
-
<div class="sa-bundles__kpi-sub">
|
|
11
|
+
<div class="sa-bundles__kpi-sub">{{ msg.kpis.scheduledSub }}</div>
|
|
14
12
|
</div>
|
|
15
13
|
<div class="sa-bundles__kpi">
|
|
16
|
-
<div class="sa-bundles__kpi-label">
|
|
14
|
+
<div class="sa-bundles__kpi-label">{{ msg.kpis.drafts }}</div>
|
|
17
15
|
<div class="sa-bundles__kpi-value">{{ totalDraftVersions }}</div>
|
|
18
|
-
<div class="sa-bundles__kpi-sub">{{
|
|
16
|
+
<div class="sa-bundles__kpi-sub">{{ draftsSub }}</div>
|
|
19
17
|
</div>
|
|
20
18
|
<div class="sa-bundles__kpi">
|
|
21
|
-
<div class="sa-bundles__kpi-label">
|
|
19
|
+
<div class="sa-bundles__kpi-label">{{ msg.kpis.translated }}</div>
|
|
22
20
|
<div class="sa-bundles__kpi-value">{{ translatedCount }}</div>
|
|
23
|
-
<div class="sa-bundles__kpi-sub">{{
|
|
21
|
+
<div class="sa-bundles__kpi-sub">{{ translatedSub }}</div>
|
|
24
22
|
</div>
|
|
25
23
|
</div>
|
|
26
24
|
</template>
|
|
27
25
|
|
|
28
26
|
<script setup lang="ts">
|
|
29
|
-
|
|
27
|
+
import { computed } from 'vue';
|
|
28
|
+
|
|
29
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
30
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
31
|
+
|
|
32
|
+
const props = defineProps<{
|
|
30
33
|
bundlesTotal: number;
|
|
31
34
|
liveCount: number;
|
|
32
35
|
scheduledBundlesCount: number;
|
|
@@ -36,4 +39,19 @@ defineProps<{
|
|
|
36
39
|
translatedCount: number;
|
|
37
40
|
localesCount: number;
|
|
38
41
|
}>();
|
|
42
|
+
|
|
43
|
+
const msg = useSaMessages('bundles');
|
|
44
|
+
|
|
45
|
+
const totalSub = computed(() =>
|
|
46
|
+
formatMessage(msg.value.kpis.totalSub, {
|
|
47
|
+
live: props.liveCount,
|
|
48
|
+
scheduled: props.scheduledBundlesCount,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
const draftsSub = computed(() =>
|
|
52
|
+
formatMessage(msg.value.kpis.draftsSub, { count: props.draftBundlesCount }),
|
|
53
|
+
);
|
|
54
|
+
const translatedSub = computed(() =>
|
|
55
|
+
formatMessage(msg.value.kpis.translatedSub, { count: props.localesCount }),
|
|
56
|
+
);
|
|
39
57
|
</script>
|