feat(vocab): add dashboard learning summary and related endpoints
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s
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:
@@ -2636,6 +2636,111 @@ export default class VocabService {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Kompakte Übersicht für das Start-Dashboard: eingeschriebene Kurse und „aktuelle“ Lektion
|
||||
* (gleiche Logik wie VocabCourseView.currentLesson: erste unvollständige, sonst letzte).
|
||||
*/
|
||||
async getDashboardLearningSummary(hashedUserId) {
|
||||
const user = await this._getUserByHashedId(hashedUserId);
|
||||
|
||||
const enrollments = await VocabCourseEnrollment.findAll({
|
||||
where: { userId: user.id },
|
||||
include: [
|
||||
{
|
||||
model: VocabCourse,
|
||||
as: 'course',
|
||||
required: true,
|
||||
attributes: ['id', 'title']
|
||||
}
|
||||
],
|
||||
order: [['enrolledAt', 'DESC']]
|
||||
});
|
||||
|
||||
const courseById = new Map();
|
||||
for (const e of enrollments) {
|
||||
const c = e.course?.get({ plain: true });
|
||||
if (!c?.id || courseById.has(c.id)) {
|
||||
continue;
|
||||
}
|
||||
courseById.set(c.id, { id: c.id, title: c.title || '' });
|
||||
}
|
||||
|
||||
const coursesMeta = [...courseById.values()];
|
||||
if (coursesMeta.length === 0) {
|
||||
return { courses: [] };
|
||||
}
|
||||
|
||||
const courseIds = coursesMeta.map((c) => c.id);
|
||||
|
||||
const lessons = await VocabCourseLesson.findAll({
|
||||
where: { courseId: { [Op.in]: courseIds } },
|
||||
attributes: ['id', 'courseId', 'lessonNumber', 'title'],
|
||||
order: [
|
||||
['courseId', 'ASC'],
|
||||
['lessonNumber', 'ASC']
|
||||
]
|
||||
});
|
||||
|
||||
const progressRows = await VocabCourseProgress.findAll({
|
||||
where: { userId: user.id, courseId: { [Op.in]: courseIds } },
|
||||
attributes: ['lessonId', 'completed']
|
||||
});
|
||||
|
||||
const completedByLessonId = new Map();
|
||||
for (const row of progressRows) {
|
||||
const plain = row.get({ plain: true });
|
||||
completedByLessonId.set(plain.lessonId, Boolean(plain.completed));
|
||||
}
|
||||
|
||||
const lessonsByCourse = new Map();
|
||||
for (const row of lessons) {
|
||||
const plain = row.get({ plain: true });
|
||||
const list = lessonsByCourse.get(plain.courseId) || [];
|
||||
list.push(plain);
|
||||
lessonsByCourse.set(plain.courseId, list);
|
||||
}
|
||||
|
||||
const courses = [];
|
||||
for (const meta of coursesMeta) {
|
||||
const sorted = lessonsByCourse.get(meta.id) || [];
|
||||
if (sorted.length === 0) {
|
||||
courses.push({
|
||||
courseId: meta.id,
|
||||
title: meta.title,
|
||||
currentLesson: null,
|
||||
allLessonsCompleted: false
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let current = null;
|
||||
for (const lesson of sorted) {
|
||||
if (!completedByLessonId.get(lesson.id)) {
|
||||
current = lesson;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!current) {
|
||||
current = sorted[sorted.length - 1];
|
||||
}
|
||||
|
||||
const allLessonsCompleted = sorted.every((lesson) => completedByLessonId.get(lesson.id) === true);
|
||||
|
||||
courses.push({
|
||||
courseId: meta.id,
|
||||
title: meta.title,
|
||||
currentLesson: {
|
||||
id: current.id,
|
||||
lessonNumber: current.lessonNumber,
|
||||
title: current.title || ''
|
||||
},
|
||||
allLessonsCompleted
|
||||
});
|
||||
}
|
||||
|
||||
return { courses };
|
||||
}
|
||||
|
||||
/**
|
||||
* Kurse, in die der Nutzer (per Hash) eingeschrieben ist — jede courseId nur einmal,
|
||||
* bei mehrfachen Einschreibungen zählt die jeweils neueste Zeile.
|
||||
|
||||
Reference in New Issue
Block a user