feat(admin): add user vocab course management functionality
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
All checks were successful
Deploy to production / deploy (push) Successful in 2m59s
- Implemented `getUserVocabCourses` and `getVocabCourseForAdmin` methods in `AdminController` to allow admins to retrieve enrolled vocab courses for users and specific course details, respectively. - Updated `adminRouter` to include new routes for accessing user vocab courses and course details. - Enhanced `AdminService` with methods to list user-enrolled vocab courses and retrieve course information with lessons, ensuring proper access control. - Improved `VocabService` to support the new functionalities, including attaching language names to course data. - Updated UI components in `UsersView` to reflect changes, including error handling and loading states for course retrieval, along with localization updates for new features.
This commit is contained in:
@@ -50,12 +50,15 @@
|
||||
<span>{{ $t('admin.vocabLessonReset.selectCourse') }}</span>
|
||||
<select v-model="selectedVocabCourseId" @change="onVocabCourseChange">
|
||||
<option value="">{{ $t('admin.vocabLessonReset.selectCourse') }}</option>
|
||||
<option v-for="c in vocabCourses" :key="c.id" :value="String(c.id)">{{ c.title }}</option>
|
||||
<option v-for="c in vocabCoursesForSelect" :key="c.id" :value="String(c.id)">{{ c.selectLabel }}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<p v-else-if="vocabCoursesAttempted && !loadingVocabCourses && vocabLoadError" class="vocab-reset__hint">
|
||||
{{ $t('admin.vocabLessonReset.loadCoursesError') }}
|
||||
</p>
|
||||
<p v-else-if="vocabCoursesAttempted && !loadingVocabCourses" class="vocab-reset__hint">
|
||||
{{ $t('admin.vocabLessonReset.noCourses') }}
|
||||
{{ $t('admin.vocabLessonReset.noEnrolledCourses') }}
|
||||
</p>
|
||||
|
||||
<label v-if="vocabCourseLessons.length" class="edit__field">
|
||||
@@ -101,13 +104,34 @@ export default {
|
||||
selectedVocabLessonId: '',
|
||||
loadingVocabCourses: false,
|
||||
loadingVocabCourseDetail: false,
|
||||
vocabResetSubmitting: false
|
||||
vocabResetSubmitting: false,
|
||||
vocabLoadError: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
/** Gleicher Titel bei mehreren Kurs-IDs → Kurs-ID anhängen (z. B. geklonte Kurse). */
|
||||
vocabCoursesForSelect() {
|
||||
const list = this.vocabCourses;
|
||||
const counts = {};
|
||||
list.forEach((c) => {
|
||||
const t = (c.title || '').trim() || '—';
|
||||
counts[t] = (counts[t] || 0) + 1;
|
||||
});
|
||||
return list.map((c) => {
|
||||
const t = (c.title || '').trim() || '—';
|
||||
const dup = counts[t] > 1;
|
||||
return {
|
||||
...c,
|
||||
selectLabel: dup ? `${c.title} (#${c.id})` : (c.title || `#${c.id}`)
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearVocabResetUi() {
|
||||
this.vocabCourses = [];
|
||||
this.vocabCoursesAttempted = false;
|
||||
this.vocabLoadError = false;
|
||||
this.selectedVocabCourseId = '';
|
||||
this.vocabCourseLessons = [];
|
||||
this.selectedVocabLessonId = '';
|
||||
@@ -137,8 +161,9 @@ export default {
|
||||
}
|
||||
this.loadingVocabCourses = true;
|
||||
this.vocabCoursesAttempted = true;
|
||||
this.vocabLoadError = false;
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/vocab/courses');
|
||||
const { data } = await apiClient.get(`/api/admin/users/${this.selected.id}/vocab-courses`);
|
||||
this.vocabCourses = Array.isArray(data) ? data : [];
|
||||
this.selectedVocabCourseId = '';
|
||||
this.vocabCourseLessons = [];
|
||||
@@ -146,6 +171,7 @@ export default {
|
||||
} catch (e) {
|
||||
console.error('[UsersView] vocab courses:', e);
|
||||
this.vocabCourses = [];
|
||||
this.vocabLoadError = true;
|
||||
this.$root?.$refs?.messageDialog?.open?.(this.$t('admin.vocabLessonReset.error'));
|
||||
} finally {
|
||||
this.loadingVocabCourses = false;
|
||||
@@ -159,7 +185,7 @@ export default {
|
||||
}
|
||||
this.loadingVocabCourseDetail = true;
|
||||
try {
|
||||
const { data } = await apiClient.get(`/api/vocab/courses/${this.selectedVocabCourseId}`);
|
||||
const { data } = await apiClient.get(`/api/admin/vocab/courses/${this.selectedVocabCourseId}`);
|
||||
const lessons = data?.lessons || [];
|
||||
this.vocabCourseLessons = [...lessons].sort((a, b) => {
|
||||
if (a.weekNumber !== b.weekNumber) {
|
||||
|
||||
Reference in New Issue
Block a user