@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
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
<div class="sa-trans-lang__head">
|
|
6
6
|
<span class="sa-trans-lang__code">{{ defaultLocale.toUpperCase() }}</span>
|
|
7
7
|
<span class="sa-trans-lang__name">{{ localeFull(defaultLocale) }}</span>
|
|
8
|
-
<span class="sa-trans-lang__badge">
|
|
8
|
+
<span class="sa-trans-lang__badge">{{ msg.trans.sourceBadge }}</span>
|
|
9
9
|
</div>
|
|
10
10
|
<div class="sa-trans-fields">
|
|
11
11
|
<div v-for="f in fields" :key="f" class="sa-trans-field">
|
|
12
|
-
<label class="sa-trans-field__cap">{{ i18nFieldLabel(f) }}</label>
|
|
12
|
+
<label class="sa-trans-field__cap">{{ i18nFieldLabel(f, uiLocale) }}</label>
|
|
13
13
|
<q-input
|
|
14
14
|
v-if="f !== 'unit'"
|
|
15
15
|
dense
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
/>
|
|
22
22
|
<div v-else class="sa-trans-locked">
|
|
23
23
|
{{ entry.unit || '—' }}
|
|
24
|
-
<span class="sa-trans-locked__hint">
|
|
24
|
+
<span class="sa-trans-locked__hint">{{ msg.trans.unitFromCode }}</span>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
@@ -38,23 +38,23 @@
|
|
|
38
38
|
no-caps
|
|
39
39
|
size="sm"
|
|
40
40
|
icon="content_copy"
|
|
41
|
-
label="
|
|
41
|
+
:label="msg.trans.copyFromDefault"
|
|
42
42
|
class="sa-trans-lang__copy"
|
|
43
43
|
@click="copyFromDefault(lng)"
|
|
44
44
|
>
|
|
45
|
-
<q-tooltip>
|
|
45
|
+
<q-tooltip>{{ msg.trans.copyFromDefaultHint }}</q-tooltip>
|
|
46
46
|
</q-btn>
|
|
47
47
|
</div>
|
|
48
48
|
<div class="sa-trans-fields">
|
|
49
49
|
<div v-for="f in fields" :key="f" class="sa-trans-field">
|
|
50
|
-
<label class="sa-trans-field__cap">{{ i18nFieldLabel(f) }}</label>
|
|
50
|
+
<label class="sa-trans-field__cap">{{ i18nFieldLabel(f, uiLocale) }}</label>
|
|
51
51
|
<q-input
|
|
52
52
|
dense
|
|
53
53
|
outlined
|
|
54
54
|
:type="f === 'description' ? 'textarea' : 'text'"
|
|
55
55
|
:autogrow="f === 'description'"
|
|
56
56
|
:model-value="localeValue(lng, f)"
|
|
57
|
-
:placeholder="
|
|
57
|
+
:placeholder="fallbackPlaceholder(f)"
|
|
58
58
|
@update:model-value="(v) => onLocale(lng, f, String(v ?? ''))"
|
|
59
59
|
/>
|
|
60
60
|
</div>
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
</div>
|
|
63
63
|
|
|
64
64
|
<div v-if="targetLocales.length === 0" class="sa-trans__empty">
|
|
65
|
-
|
|
65
|
+
{{ msg.trans.noOtherLocales }}
|
|
66
66
|
</div>
|
|
67
67
|
</div>
|
|
68
68
|
</template>
|
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
<script setup lang="ts">
|
|
71
71
|
import { computed, reactive } from 'vue';
|
|
72
72
|
import type { CatalogEntryI18nFields } from '@saasicat/types';
|
|
73
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
74
|
+
import { useSaMessages, useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
73
75
|
import {
|
|
74
76
|
DISCOVERY_DEFAULT_LOCALE,
|
|
75
77
|
i18nFieldLabel,
|
|
@@ -103,6 +105,10 @@ const emit = defineEmits<{
|
|
|
103
105
|
'update:locale': [locale: string, patch: CatalogEntryI18nFields];
|
|
104
106
|
}>();
|
|
105
107
|
|
|
108
|
+
const msg = useSaMessages('discovery');
|
|
109
|
+
// Renamed: `locale` is the catalog-entry data locale everywhere else in this file.
|
|
110
|
+
const { locale: uiLocale } = useSuperAdminI18n();
|
|
111
|
+
|
|
106
112
|
const targetLocales = computed(() => props.activeLocales.filter((l) => l !== props.defaultLocale));
|
|
107
113
|
|
|
108
114
|
// Local input buffer: once a field has been edited, the draft wins over
|
|
@@ -124,6 +130,12 @@ function localeValue(locale: string, f: I18nField): string {
|
|
|
124
130
|
return props.entry.i18n?.[locale]?.[f] ?? '';
|
|
125
131
|
}
|
|
126
132
|
|
|
133
|
+
/** Shows the default-locale value that a blank target field falls back to. */
|
|
134
|
+
function fallbackPlaceholder(f: I18nField): string {
|
|
135
|
+
const base = baseValue(f);
|
|
136
|
+
return base ? formatMessage(msg.value.trans.fallbackPlaceholder, { value: base }) : '—';
|
|
137
|
+
}
|
|
138
|
+
|
|
127
139
|
function onBase(f: I18nField, value: string): void {
|
|
128
140
|
if (f === 'unit') return;
|
|
129
141
|
drafts[`b|${f}`] = value;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div v-if="capabilities.length === 0" class="sa-caps__empty">
|
|
3
3
|
<q-icon name="warning" size="14px" />
|
|
4
|
-
|
|
5
|
-
Plan-Publish.
|
|
4
|
+
{{ msg.capList.orphanFeature }}
|
|
6
5
|
</div>
|
|
7
6
|
<div v-else class="sa-caps">
|
|
8
7
|
<div
|
|
@@ -37,7 +36,7 @@
|
|
|
37
36
|
v-if="cap.codeStatus === 'retired'"
|
|
38
37
|
class="sa-caps-row__flag sa-caps-row__flag--gone"
|
|
39
38
|
>
|
|
40
|
-
|
|
39
|
+
{{ msg.capList.removedFromCode }}
|
|
41
40
|
</span>
|
|
42
41
|
</div>
|
|
43
42
|
<div class="sa-caps-row__meta">
|
|
@@ -59,6 +58,7 @@
|
|
|
59
58
|
<script setup lang="ts">
|
|
60
59
|
import type { CapabilityCatalogEntryRow } from '@saasicat/types';
|
|
61
60
|
import { kindStyle } from './discovery-ui.js';
|
|
61
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
62
62
|
|
|
63
63
|
// Read-only Capability list: code facts from the scan (#20). Embedded in the
|
|
64
64
|
// master-data subtab of the feature card as well as in the page's orphan bucket.
|
|
@@ -73,6 +73,8 @@ const props = defineProps<{
|
|
|
73
73
|
newSince?: string | null;
|
|
74
74
|
}>();
|
|
75
75
|
|
|
76
|
+
const msg = useSaMessages('discovery');
|
|
77
|
+
|
|
76
78
|
function isNew(cap: CapabilityCatalogEntryRow): boolean {
|
|
77
79
|
return Boolean(props.newSince && cap.createdAt > props.newSince);
|
|
78
80
|
}
|
|
@@ -7,34 +7,38 @@
|
|
|
7
7
|
<code class="sa-fc__key">{{ feature.featureKey }}</code>
|
|
8
8
|
<span class="sa-fc__label">{{ labelValue || feature.featureKey }}</span>
|
|
9
9
|
<span v-if="newCapsCount > 0" class="sa-fc__flag sa-fc__flag--new">
|
|
10
|
-
{{
|
|
10
|
+
{{ newCapsLabel }}
|
|
11
11
|
</span>
|
|
12
12
|
<span v-if="deprecatedCapsCount > 0" class="sa-fc__flag sa-fc__flag--dep">
|
|
13
|
-
{{
|
|
13
|
+
{{ deprecatedCapsLabel }}
|
|
14
14
|
</span>
|
|
15
15
|
<span
|
|
16
16
|
v-if="feature.successorKey"
|
|
17
17
|
class="sa-fc__flag sa-fc__flag--succ"
|
|
18
|
-
:title="
|
|
18
|
+
:title="replacedByLabel"
|
|
19
19
|
>
|
|
20
|
-
|
|
20
|
+
{{ replacedByLabel }}
|
|
21
21
|
</span>
|
|
22
22
|
<span
|
|
23
23
|
v-if="feature.replaces.length"
|
|
24
24
|
class="sa-fc__flag sa-fc__flag--repl"
|
|
25
|
-
:title="
|
|
25
|
+
:title="replacesLabel"
|
|
26
26
|
>
|
|
27
|
-
|
|
27
|
+
{{ replacesLabel }}
|
|
28
28
|
</span>
|
|
29
29
|
</div>
|
|
30
30
|
<div class="sa-fc__sub">
|
|
31
31
|
<template v-if="owners.length">
|
|
32
|
-
<span class="sa-muted">
|
|
32
|
+
<span class="sa-muted">{{ msg.owner }}</span>
|
|
33
33
|
{{ owners.join(', ') }}
|
|
34
34
|
<span class="sa-fc__dot">·</span>
|
|
35
35
|
</template>
|
|
36
36
|
{{ capabilities.length }}
|
|
37
|
-
|
|
37
|
+
{{
|
|
38
|
+
capabilities.length === 1
|
|
39
|
+
? msg.feature.capabilityOne
|
|
40
|
+
: msg.feature.capabilityMany
|
|
41
|
+
}}
|
|
38
42
|
<template v-if="tierValue">
|
|
39
43
|
<span class="sa-fc__dot">·</span>
|
|
40
44
|
<span class="sa-fc__tier">{{ tierValue }}</span>
|
|
@@ -65,34 +69,31 @@
|
|
|
65
69
|
<div v-if="feature.discoveryStatus === 'outdated'" class="sa-fc__banner warn">
|
|
66
70
|
<q-icon name="warning" size="16px" />
|
|
67
71
|
<span>
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
{{ msg.feature.outdatedBanner }} <b>{{ msg.reapproveEmphasis }}</b
|
|
73
|
+
>.
|
|
70
74
|
</span>
|
|
71
75
|
</div>
|
|
72
76
|
<div v-if="feature.discoveryStatus === 'obsolete'" class="sa-fc__banner mute">
|
|
73
77
|
<q-icon name="info" size="16px" />
|
|
74
|
-
<span>
|
|
75
|
-
Dieses Feature ist abgekündigt. Es sollte in neuen Plänen nicht mehr verwendet
|
|
76
|
-
werden.
|
|
77
|
-
</span>
|
|
78
|
+
<span>{{ msg.feature.obsoleteBanner }}</span>
|
|
78
79
|
</div>
|
|
79
80
|
|
|
80
81
|
<q-tabs v-model="sub" dense align="left" class="sa-fc__subtabs">
|
|
81
|
-
<q-tab name="stamm" label="
|
|
82
|
-
<q-tab name="i18n" label="
|
|
82
|
+
<q-tab name="stamm" :label="msg.feature.tabBaseData" />
|
|
83
|
+
<q-tab name="i18n" :label="msg.feature.tabTranslations" />
|
|
83
84
|
</q-tabs>
|
|
84
85
|
|
|
85
86
|
<div v-if="sub === 'stamm'" class="sa-fc__split">
|
|
86
87
|
<div class="sa-fc__split-col">
|
|
87
|
-
<div class="sa-fc__split-head">
|
|
88
|
+
<div class="sa-fc__split-head">{{ msg.feature.settings }}</div>
|
|
88
89
|
<div class="sa-fc__fields">
|
|
89
90
|
<div class="sa-fc-field">
|
|
90
|
-
<label class="sa-fc-field__cap">
|
|
91
|
+
<label class="sa-fc-field__cap">{{ msg.feature.iconLabel }}</label>
|
|
91
92
|
<q-input
|
|
92
93
|
dense
|
|
93
94
|
outlined
|
|
94
95
|
:model-value="iconValue"
|
|
95
|
-
placeholder="
|
|
96
|
+
:placeholder="msg.feature.iconPlaceholder"
|
|
96
97
|
@update:model-value="(v) => onField('icon', String(v ?? ''))"
|
|
97
98
|
>
|
|
98
99
|
<template #prepend>
|
|
@@ -101,7 +102,7 @@
|
|
|
101
102
|
</q-input>
|
|
102
103
|
</div>
|
|
103
104
|
<div class="sa-fc-field">
|
|
104
|
-
<label class="sa-fc-field__cap">
|
|
105
|
+
<label class="sa-fc-field__cap">{{ msg.feature.tierLabel }}</label>
|
|
105
106
|
<q-select
|
|
106
107
|
dense
|
|
107
108
|
outlined
|
|
@@ -117,10 +118,8 @@
|
|
|
117
118
|
</div>
|
|
118
119
|
<div class="sa-fc__split-col">
|
|
119
120
|
<div class="sa-fc__split-head">
|
|
120
|
-
|
|
121
|
-
<span class="sa-fc__split-count">
|
|
122
|
-
{{ capabilities.length }} · read-only
|
|
123
|
-
</span>
|
|
121
|
+
{{ msg.feature.codeCapabilities }}
|
|
122
|
+
<span class="sa-fc__split-count">{{ codeCapabilitiesCountLabel }}</span>
|
|
124
123
|
</div>
|
|
125
124
|
<DiscoveryCapList
|
|
126
125
|
:capabilities="capabilities"
|
|
@@ -156,6 +155,8 @@ import type {
|
|
|
156
155
|
import CatalogEntryTransPanel from './CatalogEntryTransPanel.vue';
|
|
157
156
|
import DiscoveryCapList from './DiscoveryCapList.vue';
|
|
158
157
|
import DiscoveryStatusControl from './DiscoveryStatusControl.vue';
|
|
158
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
159
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
159
160
|
import {
|
|
160
161
|
coverageClass,
|
|
161
162
|
coveragePct,
|
|
@@ -189,6 +190,8 @@ const emit = defineEmits<{
|
|
|
189
190
|
|
|
190
191
|
const TIER_OPTIONS = ['CORE', 'ADVANCED', 'PRO', 'ENTERPRISE'];
|
|
191
192
|
|
|
193
|
+
const msg = useSaMessages('discovery');
|
|
194
|
+
|
|
192
195
|
const sub = ref<'stamm' | 'i18n'>('stamm');
|
|
193
196
|
|
|
194
197
|
// Local input buffer: once a field has been edited, the draft wins over the
|
|
@@ -232,6 +235,22 @@ const deprecatedCapsCount = computed(
|
|
|
232
235
|
() => props.capabilities.filter((c) => c.codeStatus === 'deprecated').length,
|
|
233
236
|
);
|
|
234
237
|
|
|
238
|
+
const newCapsLabel = computed(() =>
|
|
239
|
+
formatMessage(msg.value.feature.newCaps, { count: newCapsCount.value }),
|
|
240
|
+
);
|
|
241
|
+
const deprecatedCapsLabel = computed(() =>
|
|
242
|
+
formatMessage(msg.value.feature.deprecatedCaps, { count: deprecatedCapsCount.value }),
|
|
243
|
+
);
|
|
244
|
+
const replacedByLabel = computed(() =>
|
|
245
|
+
formatMessage(msg.value.replacedBy, { key: props.feature.successorKey ?? '' }),
|
|
246
|
+
);
|
|
247
|
+
const replacesLabel = computed(() =>
|
|
248
|
+
formatMessage(msg.value.replaces, { keys: props.feature.replaces.join(', ') }),
|
|
249
|
+
);
|
|
250
|
+
const codeCapabilitiesCountLabel = computed(() =>
|
|
251
|
+
formatMessage(msg.value.feature.codeCapabilitiesCount, { count: props.capabilities.length }),
|
|
252
|
+
);
|
|
253
|
+
|
|
235
254
|
const targetLocales = computed(() =>
|
|
236
255
|
props.activeLocales.filter((l) => l !== DISCOVERY_DEFAULT_LOCALE),
|
|
237
256
|
);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<header class="sa-discovery__head">
|
|
3
3
|
<div>
|
|
4
|
-
<h1 class="sa-discovery__title">
|
|
4
|
+
<h1 class="sa-discovery__title">{{ msg.title }}</h1>
|
|
5
5
|
<p class="sa-discovery__sub">
|
|
6
|
-
|
|
7
|
-
die Freigabe für Produkt & Marketing erfolgt im Review.
|
|
6
|
+
{{ msg.subtitleLead }} <b>{{ msg.subtitleEmphasis }}</b> {{ msg.subtitleTail }}
|
|
8
7
|
</p>
|
|
9
8
|
</div>
|
|
10
9
|
<div class="sa-discovery__head-actions">
|
|
@@ -12,7 +11,7 @@
|
|
|
12
11
|
unelevated
|
|
13
12
|
color="primary"
|
|
14
13
|
icon="bolt"
|
|
15
|
-
label="
|
|
14
|
+
:label="msg.runDiscovery"
|
|
16
15
|
:loading="loading"
|
|
17
16
|
@click="emit('runDiscovery')"
|
|
18
17
|
/>
|
|
@@ -21,6 +20,8 @@
|
|
|
21
20
|
</template>
|
|
22
21
|
|
|
23
22
|
<script setup lang="ts">
|
|
23
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
24
|
+
|
|
24
25
|
defineProps<{
|
|
25
26
|
loading: boolean;
|
|
26
27
|
}>();
|
|
@@ -28,4 +29,6 @@ defineProps<{
|
|
|
28
29
|
const emit = defineEmits<{
|
|
29
30
|
(e: 'runDiscovery'): void;
|
|
30
31
|
}>();
|
|
32
|
+
|
|
33
|
+
const msg = useSaMessages('discovery');
|
|
31
34
|
</script>
|
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="sa-discovery__kpis">
|
|
3
3
|
<div class="sa-discovery__kpi">
|
|
4
|
-
<div class="sa-discovery__kpi-label">
|
|
4
|
+
<div class="sa-discovery__kpi-label">{{ msg.kpi.featuresTotal }}</div>
|
|
5
5
|
<div class="sa-discovery__kpi-value">{{ featuresCount }}</div>
|
|
6
|
-
<div class="sa-discovery__kpi-sub">
|
|
7
|
-
aggregiert aus {{ capabilitiesCount }} Capabilities
|
|
8
|
-
</div>
|
|
6
|
+
<div class="sa-discovery__kpi-sub">{{ featuresTotalSub }}</div>
|
|
9
7
|
</div>
|
|
10
8
|
<div class="sa-discovery__kpi good">
|
|
11
|
-
<div class="sa-discovery__kpi-label">
|
|
9
|
+
<div class="sa-discovery__kpi-label">{{ msg.kpi.approved }}</div>
|
|
12
10
|
<div class="sa-discovery__kpi-value">{{ approvedCount }}</div>
|
|
13
|
-
<div class="sa-discovery__kpi-sub">
|
|
11
|
+
<div class="sa-discovery__kpi-sub">{{ msg.kpi.approvedSub }}</div>
|
|
14
12
|
</div>
|
|
15
13
|
<div class="sa-discovery__kpi" :class="{ warn: pendingCount > 0 }">
|
|
16
|
-
<div class="sa-discovery__kpi-label">
|
|
14
|
+
<div class="sa-discovery__kpi-label">{{ msg.kpi.pending }}</div>
|
|
17
15
|
<div class="sa-discovery__kpi-value">{{ pendingCount }}</div>
|
|
18
|
-
<div class="sa-discovery__kpi-sub">
|
|
16
|
+
<div class="sa-discovery__kpi-sub">{{ msg.kpi.pendingSub }}</div>
|
|
19
17
|
</div>
|
|
20
18
|
<div class="sa-discovery__kpi">
|
|
21
|
-
<div class="sa-discovery__kpi-label">
|
|
19
|
+
<div class="sa-discovery__kpi-label">{{ msg.kpi.outdated }}</div>
|
|
22
20
|
<div class="sa-discovery__kpi-value">{{ outdatedCount + obsoleteCount }}</div>
|
|
23
|
-
<div class="sa-discovery__kpi-sub">
|
|
24
|
-
{{ outdatedCount }} outdated · {{ obsoleteCount }} obsolete
|
|
25
|
-
</div>
|
|
21
|
+
<div class="sa-discovery__kpi-sub">{{ outdatedSub }}</div>
|
|
26
22
|
</div>
|
|
27
23
|
<div class="sa-discovery__kpi" :class="{ bad: orphanCount > 0 }">
|
|
28
|
-
<div class="sa-discovery__kpi-label">
|
|
24
|
+
<div class="sa-discovery__kpi-label">{{ msg.kpi.orphans }}</div>
|
|
29
25
|
<div class="sa-discovery__kpi-value">{{ orphanCount }}</div>
|
|
30
|
-
<div class="sa-discovery__kpi-sub">
|
|
26
|
+
<div class="sa-discovery__kpi-sub">{{ msg.orphansTitle }}</div>
|
|
31
27
|
</div>
|
|
32
28
|
</div>
|
|
33
29
|
</template>
|
|
34
30
|
|
|
35
31
|
<script setup lang="ts">
|
|
32
|
+
import { computed } from 'vue';
|
|
33
|
+
|
|
34
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
35
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
36
|
+
|
|
36
37
|
// Feature-centric KPI bar (#20 Slice 1, sim layout): the approval
|
|
37
38
|
// counts refer to features, no longer to capability reviews.
|
|
38
|
-
defineProps<{
|
|
39
|
+
const props = defineProps<{
|
|
39
40
|
featuresCount: number;
|
|
40
41
|
capabilitiesCount: number;
|
|
41
42
|
approvedCount: number;
|
|
@@ -44,4 +45,16 @@ defineProps<{
|
|
|
44
45
|
obsoleteCount: number;
|
|
45
46
|
orphanCount: number;
|
|
46
47
|
}>();
|
|
48
|
+
|
|
49
|
+
const msg = useSaMessages('discovery');
|
|
50
|
+
|
|
51
|
+
const featuresTotalSub = computed(() =>
|
|
52
|
+
formatMessage(msg.value.kpi.featuresTotalSub, { count: props.capabilitiesCount }),
|
|
53
|
+
);
|
|
54
|
+
const outdatedSub = computed(() =>
|
|
55
|
+
formatMessage(msg.value.kpi.outdatedSub, {
|
|
56
|
+
outdated: props.outdatedCount,
|
|
57
|
+
obsolete: props.obsoleteCount,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
47
60
|
</script>
|
|
@@ -15,26 +15,25 @@
|
|
|
15
15
|
<span
|
|
16
16
|
v-if="quota.successorKey"
|
|
17
17
|
class="sa-qc__flag sa-qc__flag--succ"
|
|
18
|
-
:title="
|
|
18
|
+
:title="replacedByLabel"
|
|
19
19
|
>
|
|
20
|
-
|
|
20
|
+
{{ replacedByLabel }}
|
|
21
21
|
</span>
|
|
22
22
|
<span
|
|
23
23
|
v-if="quota.replaces.length"
|
|
24
24
|
class="sa-qc__flag sa-qc__flag--repl"
|
|
25
|
-
:title="
|
|
25
|
+
:title="replacesLabel"
|
|
26
26
|
>
|
|
27
|
-
|
|
27
|
+
{{ replacesLabel }}
|
|
28
28
|
</span>
|
|
29
29
|
</div>
|
|
30
30
|
<div class="sa-qc__sub">
|
|
31
|
-
|
|
31
|
+
{{ msg.unit }} <code>{{ quota.unit }}</code> · {{ msg.quota.usageProvider }}
|
|
32
32
|
<code v-if="quota.usageProvider">{{ quota.usageProvider }}</code>
|
|
33
|
-
<span v-else class="sa-qc__missing">
|
|
33
|
+
<span v-else class="sa-qc__missing">{{ msg.quota.usageProviderMissing }}</span>
|
|
34
34
|
</div>
|
|
35
35
|
<div v-if="!quota.usageProvider" class="sa-qc__warning">
|
|
36
|
-
|
|
37
|
-
§6.3).
|
|
36
|
+
{{ msg.quota.noUsageProviderWarning }}
|
|
38
37
|
</div>
|
|
39
38
|
</div>
|
|
40
39
|
|
|
@@ -61,8 +60,8 @@
|
|
|
61
60
|
<div v-if="quota.discoveryStatus === 'outdated'" class="sa-qc__banner">
|
|
62
61
|
<q-icon name="warning" size="16px" />
|
|
63
62
|
<span>
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
{{ msg.quota.outdatedBanner }} <b>{{ msg.reapproveEmphasis }}</b
|
|
64
|
+
>.
|
|
66
65
|
</span>
|
|
67
66
|
</div>
|
|
68
67
|
<CatalogEntryTransPanel
|
|
@@ -88,6 +87,8 @@ import type {
|
|
|
88
87
|
} from '@saasicat/types';
|
|
89
88
|
import CatalogEntryTransPanel from './CatalogEntryTransPanel.vue';
|
|
90
89
|
import DiscoveryStatusControl from './DiscoveryStatusControl.vue';
|
|
90
|
+
import { formatMessage } from '../../client/i18n/format.js';
|
|
91
|
+
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
91
92
|
import {
|
|
92
93
|
coverageClass,
|
|
93
94
|
coveragePct,
|
|
@@ -114,11 +115,20 @@ const emit = defineEmits<{
|
|
|
114
115
|
'quota-locale': [key: string, locale: string, patch: CatalogEntryI18nFields];
|
|
115
116
|
}>();
|
|
116
117
|
|
|
118
|
+
const msg = useSaMessages('discovery');
|
|
119
|
+
|
|
117
120
|
// Draft buffer for the header label — the fields themselves are buffered by
|
|
118
121
|
// the CatalogEntryTransPanel; here only so the title follows along while typing.
|
|
119
122
|
const drafts = reactive<{ label?: string }>({});
|
|
120
123
|
const labelValue = computed(() => drafts.label ?? props.quota.label ?? '');
|
|
121
124
|
|
|
125
|
+
const replacedByLabel = computed(() =>
|
|
126
|
+
formatMessage(msg.value.replacedBy, { key: props.quota.successorKey ?? '' }),
|
|
127
|
+
);
|
|
128
|
+
const replacesLabel = computed(() =>
|
|
129
|
+
formatMessage(msg.value.replaces, { keys: props.quota.replaces.join(', ') }),
|
|
130
|
+
);
|
|
131
|
+
|
|
122
132
|
/** Mirror base edits from the translation panel + bubble them up. */
|
|
123
133
|
function onTransBase(patch: { label?: string; description?: string }): void {
|
|
124
134
|
if (patch.label !== undefined) drafts.label = patch.label;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="sa-stc" @click.stop>
|
|
3
3
|
<span class="sa-review" :class="`sa-review--${status}`">
|
|
4
|
-
{{
|
|
5
|
-
<q-tooltip>{{
|
|
4
|
+
{{ statusLabel(status, locale) }}
|
|
5
|
+
<q-tooltip>{{ statusHint(status, locale) }}</q-tooltip>
|
|
6
6
|
</span>
|
|
7
7
|
<q-btn
|
|
8
8
|
dense
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
:loading="busy"
|
|
16
16
|
@click="emit('set-status', primary.to)"
|
|
17
17
|
>
|
|
18
|
-
<q-tooltip>{{
|
|
18
|
+
<q-tooltip>{{ statusHint(primary.to, locale) }}</q-tooltip>
|
|
19
19
|
</q-btn>
|
|
20
20
|
<q-btn v-if="menu.length" dense flat round size="sm" icon="more_horiz">
|
|
21
21
|
<q-menu auto-close anchor="bottom right" self="top right">
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
<script setup lang="ts">
|
|
40
40
|
import { computed } from 'vue';
|
|
41
41
|
import type { DiscoveryStatus } from '@saasicat/types';
|
|
42
|
-
import {
|
|
42
|
+
import { useSuperAdminI18n } from '../../vue/use-super-admin-i18n.js';
|
|
43
|
+
import { primaryReviewAction, reviewMenuActions, statusHint, statusLabel } from './discovery-ui.js';
|
|
43
44
|
|
|
44
45
|
// StatusControl (#20, design sim): status chip + contextual primary action
|
|
45
46
|
// + kebab menu for the remaining lifecycle transitions. Shared by the feature
|
|
@@ -56,8 +57,10 @@ const emit = defineEmits<{
|
|
|
56
57
|
'set-status': [target: DiscoveryStatus];
|
|
57
58
|
}>();
|
|
58
59
|
|
|
59
|
-
const
|
|
60
|
-
|
|
60
|
+
const { locale } = useSuperAdminI18n();
|
|
61
|
+
|
|
62
|
+
const primary = computed(() => primaryReviewAction(props.status, locale.value));
|
|
63
|
+
const menu = computed(() => reviewMenuActions(props.status, locale.value));
|
|
61
64
|
</script>
|
|
62
65
|
|
|
63
66
|
<style scoped>
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
// `<script setup>` allows no named exports and the cards share the same
|
|
4
4
|
// labels/colors/coverage calculations.
|
|
5
5
|
|
|
6
|
-
import type {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from '@saasicat/types';
|
|
6
|
+
import type { CatalogEntryI18n, DiscoveryStatus } from '@saasicat/types';
|
|
7
|
+
import { DEFAULT_SA_LOCALE, type SaLocale } from '../../client/i18n/locale.js';
|
|
8
|
+
import { commonMessages } from '../../client/i18n/messages/common.js';
|
|
9
|
+
import { discoveryMessages } from '../../client/i18n/messages/discovery.js';
|
|
11
10
|
|
|
12
11
|
/** Translatable fields of a catalog entry. */
|
|
13
12
|
export type I18nField = 'label' | 'description' | 'unit';
|
|
@@ -43,30 +42,21 @@ export function localeFull(locale: string): string {
|
|
|
43
42
|
return LOCALE_NAMES[locale]?.full ?? locale;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
export function i18nFieldLabel(f: I18nField): string {
|
|
47
|
-
|
|
45
|
+
export function i18nFieldLabel(f: I18nField, locale: SaLocale = DEFAULT_SA_LOCALE): string {
|
|
46
|
+
if (f === 'label') return discoveryMessages[locale].trans.fieldLabel;
|
|
47
|
+
if (f === 'description') return commonMessages[locale].description;
|
|
48
|
+
return discoveryMessages[locale].unit;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
// ─── Approval lifecycle (#20): status display + state machine ────────────────
|
|
51
52
|
|
|
52
|
-
export
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
hint: 'Für Pläne, Bundles & Marketing freigegeben.',
|
|
60
|
-
},
|
|
61
|
-
outdated: {
|
|
62
|
-
label: 'Outdated',
|
|
63
|
-
hint: 'Code hat sich seit der Freigabe geändert — bitte erneut prüfen und freigeben.',
|
|
64
|
-
},
|
|
65
|
-
obsolete: {
|
|
66
|
-
label: 'Obsolete',
|
|
67
|
-
hint: 'Abgekündigt — nicht mehr verwenden, in neuen Plänen ausblenden.',
|
|
68
|
-
},
|
|
69
|
-
};
|
|
53
|
+
export function statusLabel(status: DiscoveryStatus, locale: SaLocale = DEFAULT_SA_LOCALE): string {
|
|
54
|
+
return discoveryMessages[locale].statusLabels[status];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function statusHint(status: DiscoveryStatus, locale: SaLocale = DEFAULT_SA_LOCALE): string {
|
|
58
|
+
return discoveryMessages[locale].statusHints[status];
|
|
59
|
+
}
|
|
70
60
|
|
|
71
61
|
export interface ReviewAction {
|
|
72
62
|
label: string;
|
|
@@ -77,53 +67,51 @@ export interface ReviewAction {
|
|
|
77
67
|
}
|
|
78
68
|
|
|
79
69
|
/** Primary action per status (design sim `StatusControl`, #20). */
|
|
80
|
-
export function primaryReviewAction(
|
|
70
|
+
export function primaryReviewAction(
|
|
71
|
+
status: DiscoveryStatus,
|
|
72
|
+
locale: SaLocale = DEFAULT_SA_LOCALE,
|
|
73
|
+
): ReviewAction {
|
|
74
|
+
const actions = discoveryMessages[locale].reviewActions;
|
|
81
75
|
switch (status) {
|
|
82
76
|
case 'pending':
|
|
83
|
-
return { label:
|
|
77
|
+
return { label: actions.approve, to: 'approved', emphasized: true };
|
|
84
78
|
case 'approved':
|
|
85
|
-
return { label:
|
|
79
|
+
return { label: actions.revoke, to: 'pending', emphasized: false };
|
|
86
80
|
case 'outdated':
|
|
87
|
-
return { label:
|
|
81
|
+
return { label: actions.reapprove, to: 'approved', emphasized: true };
|
|
88
82
|
case 'obsolete':
|
|
89
|
-
return { label:
|
|
83
|
+
return { label: actions.reactivate, to: 'pending', emphasized: false };
|
|
90
84
|
}
|
|
91
85
|
}
|
|
92
86
|
|
|
93
87
|
/** Kebab menu actions per status (design sim `StatusControl`, #20). */
|
|
94
|
-
export function reviewMenuActions(
|
|
88
|
+
export function reviewMenuActions(
|
|
89
|
+
status: DiscoveryStatus,
|
|
90
|
+
locale: SaLocale = DEFAULT_SA_LOCALE,
|
|
91
|
+
): ReviewAction[] {
|
|
92
|
+
const actions = discoveryMessages[locale].reviewActions;
|
|
93
|
+
const markObsolete: ReviewAction = {
|
|
94
|
+
label: actions.markObsolete,
|
|
95
|
+
to: 'obsolete',
|
|
96
|
+
emphasized: false,
|
|
97
|
+
danger: true,
|
|
98
|
+
};
|
|
95
99
|
switch (status) {
|
|
96
100
|
case 'pending':
|
|
97
|
-
return [
|
|
98
|
-
{ label: 'Als obsolet markieren', to: 'obsolete', emphasized: false, danger: true },
|
|
99
|
-
];
|
|
101
|
+
return [markObsolete];
|
|
100
102
|
case 'approved':
|
|
101
103
|
return [
|
|
102
|
-
{ label:
|
|
103
|
-
|
|
104
|
+
{ label: actions.markOutdated, to: 'outdated', emphasized: false },
|
|
105
|
+
markObsolete,
|
|
104
106
|
];
|
|
105
107
|
case 'outdated':
|
|
106
|
-
return [
|
|
107
|
-
{ label: 'Freigabe entziehen', to: 'pending', emphasized: false },
|
|
108
|
-
{ label: 'Als obsolet markieren', to: 'obsolete', emphasized: false, danger: true },
|
|
109
|
-
];
|
|
108
|
+
return [{ label: actions.revoke, to: 'pending', emphasized: false }, markObsolete];
|
|
110
109
|
case 'obsolete':
|
|
111
110
|
return [];
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
// ─── Capability
|
|
116
|
-
|
|
117
|
-
const CODE_STATUS_LABELS: Record<CapabilityCodeStatus, string> = {
|
|
118
|
-
active: 'Aktiv',
|
|
119
|
-
experimental: 'Experimental',
|
|
120
|
-
deprecated: 'Deprecated',
|
|
121
|
-
retired: 'Retired',
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export function codeStatusLabel(status: CapabilityCodeStatus): string {
|
|
125
|
-
return CODE_STATUS_LABELS[status] ?? status;
|
|
126
|
-
}
|
|
114
|
+
// ─── Capability kind styling (read-only code facts, #20) ─────────────────────
|
|
127
115
|
|
|
128
116
|
const KIND_COLORS: Record<string, string> = {
|
|
129
117
|
endpoint: '#2563eb',
|