feat(falukant): implement score threshold logic and enhance UI feedback for certificate progression
All checks were successful
Deploy to production / deploy (push) Successful in 3m3s

- Added a new function to calculate score thresholds based on certificate levels, improving the logic for determining promotion eligibility.
- Updated the FalukantService to include new properties for score and requirement checks, enhancing the decision-making process for certificate readiness.
- Enhanced the OverviewView component to display detailed hints and states regarding certificate progression, providing users with clearer feedback on their status.
- Localized new strings in multiple languages to support the updated UI elements and hints, improving user experience across different languages.
This commit is contained in:
Torsten Schulz (local)
2026-04-01 15:47:11 +02:00
parent d39cea2c01
commit 10fc78e81d
6 changed files with 143 additions and 8 deletions

View File

@@ -112,12 +112,13 @@
<div class="certificate-panel__score">
<span>{{ $t('falukant.overview.certificate.score') }}</span>
<strong>{{ certificateProgress.score }}</strong>
<span class="certificate-panel__state" :class="{ 'is-ready': certificateProgress.readyForNextCertificate }">
{{ certificateProgress.readyForNextCertificate
? $t('falukant.overview.certificate.ready')
: $t('falukant.overview.certificate.notReady') }}
<span class="certificate-panel__state" :class="certificateProgressStateClass">
{{ $t(`falukant.overview.certificate.state.${certificateProgressStateKey}`) }}
</span>
</div>
<p class="certificate-panel__hint">
{{ certificateProgressHint }}
</p>
<div class="certificate-panel__grid">
<article class="certificate-panel__block">
@@ -157,6 +158,13 @@
<article class="certificate-panel__block">
<h4>{{ $t('falukant.overview.certificate.requirements') }}</h4>
<div class="certificate-requirements">
<div
class="certificate-requirement"
:class="{ 'is-met': certificateProgress.scoreRequirementMet }"
>
<span>{{ $t('falukant.overview.certificate.scoreGate') }}</span>
<strong>{{ formatCertificateRequirement(certificateProgress.score, certificateProgress.nextScoreThreshold) }}</strong>
</div>
<div
v-for="requirement in certificateProgress.nextRequirements"
:key="requirement.type"
@@ -438,6 +446,52 @@ export default {
products: entry.products.map((productKey) => this.$t(`falukant.product.${productKey}`)),
}));
},
certificateProgressStateKey() {
const progress = this.certificateProgress;
if (!progress) return 'notReady';
if (progress.readyForNextCertificate) return 'ready';
if (progress.minimumRequirementsMet && !progress.scoreRequirementMet) return 'minimumsMetScoreBlocked';
if (!progress.minimumRequirementsMet && progress.scoreRequirementMet) return 'scoreMetMinimumsMissing';
return 'notReady';
},
certificateProgressStateClass() {
switch (this.certificateProgressStateKey) {
case 'ready':
return 'is-ready';
case 'minimumsMetScoreBlocked':
return 'is-warning';
case 'scoreMetMinimumsMissing':
return 'is-info';
default:
return '';
}
},
certificateProgressHint() {
const progress = this.certificateProgress;
if (!progress) return '';
if (progress.readyForNextCertificate) {
return this.$t('falukant.overview.certificate.hint.ready', {
next: progress.nextCertificate,
threshold: this.formatCertificateValue(progress.nextScoreThreshold, 1),
});
}
if (progress.minimumRequirementsMet && !progress.scoreRequirementMet) {
return this.$t('falukant.overview.certificate.hint.minimumsMetScoreBlocked', {
next: progress.nextCertificate,
target: progress.targetCertificate,
threshold: this.formatCertificateValue(progress.nextScoreThreshold, 1),
});
}
if (!progress.minimumRequirementsMet && progress.scoreRequirementMet) {
return this.$t('falukant.overview.certificate.hint.scoreMetMinimumsMissing', {
next: progress.nextCertificate,
});
}
return this.$t('falukant.overview.certificate.hint.notReady', {
next: progress.nextCertificate,
threshold: this.formatCertificateValue(progress.nextScoreThreshold, 1),
});
},
routineActions() {
return [
{
@@ -820,7 +874,7 @@ export default {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
margin-bottom: 8px;
}
.certificate-panel__state {
@@ -838,6 +892,21 @@ export default {
color: #2f6b3d;
}
.certificate-panel__state.is-warning {
background: rgba(185, 99, 24, 0.16);
color: #8d5412;
}
.certificate-panel__state.is-info {
background: rgba(34, 96, 164, 0.14);
color: #21598f;
}
.certificate-panel__hint {
margin: 0 0 16px;
color: var(--color-text-secondary);
}
.certificate-panel__grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));