feat(vocab): implement user vocab lesson progress reset functionality
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s

- Added `resetUserVocabLessonProgress` method in `AdminController` to allow admins to reset a user's progress for a specific vocab lesson.
- Introduced corresponding route in `adminRouter` for the new reset functionality.
- Enhanced `VocabService` with methods to purge lesson progress for users, ensuring that only the specified lesson's progress is affected.
- Updated UI components in `UsersView` to facilitate the selection of courses and lessons for resetting progress, including confirmation dialogs and loading states.
- Added localization support for the new reset functionality across multiple languages.
- Implemented reset functionality in `VocabLessonView` for users to reset their own lesson progress.
This commit is contained in:
Torsten Schulz (local)
2026-04-02 08:25:56 +02:00
parent 13534498fa
commit c3b2c60362
22 changed files with 517 additions and 24 deletions

View File

@@ -5,6 +5,14 @@
<div class="lesson-header">
<button @click="back" class="btn-back">{{ $t('general.back') }}</button>
<h2>{{ lesson.title }}</h2>
<button
type="button"
class="btn-reset-lesson"
:disabled="resettingLessonProgress"
@click="confirmResetLessonProgress"
>
{{ resettingLessonProgress ? $t('general.loading') : $t('socialnetwork.vocab.courses.resetLessonProgress') }}
</button>
</div>
<!-- Tabs für Lernen und Übungen -->
@@ -942,7 +950,8 @@ export default {
lessonStatePersistenceReady: false,
lessonStateSaveTimer: null,
lessonStateSaveInFlight: false,
pendingLessonStatePayload: null
pendingLessonStatePayload: null,
resettingLessonProgress: false
};
},
computed: {
@@ -1373,6 +1382,44 @@ export default {
const userId = this.user?.id || 'guest';
return `vocab-lesson-state:${LESSON_STATE_VERSION}:${userId}:${this.courseId}:${this.lessonId}`;
},
clearLocalLessonStateCache() {
const storageKey = this.getLessonStateStorageKey();
if (!storageKey) {
return;
}
try {
window.localStorage.removeItem(storageKey);
} catch (error) {
console.warn('[VocabLessonView] Lokaler Lektions-Cache konnte nicht gelöscht werden:', error);
}
},
confirmResetLessonProgress() {
if (!this.lessonId || this.resettingLessonProgress) {
return;
}
if (!window.confirm(this.$t('socialnetwork.vocab.courses.resetLessonProgressConfirm'))) {
return;
}
this.resetLessonProgressOnServer();
},
async resetLessonProgressOnServer() {
if (!this.lessonId || this.resettingLessonProgress) {
return;
}
this.resettingLessonProgress = true;
try {
await apiClient.delete(`/api/vocab/lessons/${this.lessonId}/progress`);
this.clearLocalLessonStateCache();
this.$root?.$refs?.messageDialog?.open?.('tr:socialnetwork.vocab.courses.resetLessonProgressSuccess');
await this.loadLesson();
} catch (e) {
console.error('[VocabLessonView] Lektion zurücksetzen fehlgeschlagen:', e);
const msg = e?.response?.data?.error || this.$t('socialnetwork.vocab.courses.resetLessonProgressError');
this.$root?.$refs?.messageDialog?.open?.(msg);
} finally {
this.resettingLessonProgress = false;
}
},
buildPersistedLessonState() {
return {
version: LESSON_STATE_VERSION,
@@ -3104,6 +3151,29 @@ export default {
margin-bottom: 20px;
}
.lesson-header h2 {
flex: 1;
min-width: 0;
margin: 0;
}
.btn-reset-lesson {
flex-shrink: 0;
padding: 8px 14px;
border: 1px solid rgba(140, 90, 60, 0.35);
border-radius: 4px;
background: rgba(255, 248, 240, 0.95);
color: #6b4420;
cursor: pointer;
font-size: 0.875rem;
font-weight: 600;
}
.btn-reset-lesson:disabled {
opacity: 0.65;
cursor: not-allowed;
}
.lesson-overview-card {
display: flex;
flex-direction: column;