feat(vocab): add dashboard learning summary and related endpoints
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s

- Introduced `getDashboardLearningSummary` method in `VocabService` to provide a compact overview of enrolled courses and current lessons for users.
- Updated `vocabController` to include a new route for the dashboard widget, allowing users to access their learning summary.
- Enhanced `vocabRouter` to route requests for the new dashboard widget endpoint.
- Added localization support for the new dashboard features across multiple languages, improving user engagement and accessibility.
- Updated UI components to integrate the new dashboard widget, ensuring a seamless user experience.
This commit is contained in:
Torsten Schulz (local)
2026-04-02 15:06:50 +02:00
parent 77e6f8d3e8
commit 5fcd55be43
28 changed files with 1095 additions and 39 deletions

View File

@@ -25,9 +25,11 @@
</span>
<span>
{{ $t('falukant.politics.current.benefit') }}:
<template v-if="pos.benefit && pos.benefit.length">
<span v-if="pos.benefit.includes('*')">{{ $t('falukant.politics.current.benefit_all') }}</span>
<span v-else>{{ pos.benefit.join(', ') }}</span>
<template v-if="politicsBenefitItems(pos).length">
<template v-for="(b, i) in politicsBenefitItems(pos)" :key="i">
<span>{{ formatPoliticsBenefitItem(b) }}</span>
<span v-if="i < politicsBenefitItems(pos).length - 1">, </span>
</template>
</template>
<template v-else></template>
</span>
@@ -178,6 +180,49 @@ export default {
this.loadCurrentPositions();
},
methods: {
politicsBenefitItems(pos) {
const raw = pos?.benefit;
if (!Array.isArray(raw) || !raw.length) {
return [];
}
if (raw.every((x) => typeof x === 'string') && raw.includes('*')) {
return [this.$t('falukant.politics.current.benefit_all')];
}
return raw;
},
formatPoliticsRegionLevel(key) {
const k = String(key || '');
const path = `falukant.politics.regionLevels.${k}`;
const t = this.$t(path);
return t !== path ? t : k;
},
formatPoliticsBenefitItem(b) {
if (b == null) {
return '';
}
if (typeof b === 'string') {
return b;
}
if (typeof b === 'object' && b.tr) {
if (b.tr === 'tax_exemption' && b.params?.all) {
return this.$t('falukant.politics.benefits.tax_exemption_all');
}
if (b.tr === 'tax_exemption' && Array.isArray(b.params?.regions)) {
const labels = b.params.regions.map((r) => this.formatPoliticsRegionLevel(r)).join(', ');
return this.$t('falukant.politics.benefits.tax_exemption', { regions: labels });
}
if (b.tr === 'daily_salary') {
return this.$t('falukant.politics.benefits.daily_salary', { amount: b.params?.amount ?? '—' });
}
if (b.tr === 'generic_benefit') {
return this.$t('falukant.politics.benefits.generic', { code: b.params?.code || '' });
}
}
return String(b);
},
onTabChange(tab) {
if (tab === 'current') {
this.loadCurrentPositions();

View File

@@ -192,7 +192,8 @@ export default {
'/api/news': 'home.dashboard.widgetLabels.news',
'/api/calendar/widget/birthdays': 'home.dashboard.widgetLabels.birthdays',
'/api/calendar/widget/upcoming': 'home.dashboard.widgetLabels.upcoming',
'/api/calendar/widget/mini': 'home.dashboard.widgetLabels.calendar'
'/api/calendar/widget/mini': 'home.dashboard.widgetLabels.calendar',
'/api/vocab/dashboard-widget': 'home.dashboard.widgetLabels.vocabCourses'
}[endpoint];
return key ? this.$t(key) : fallbackLabel;
},
@@ -211,7 +212,8 @@ export default {
'News',
'Geburtstage',
'Nächste Termine',
'Kalender'
'Kalender',
'Sprachkurse'
];
return {
...widget,