From b5dcc6ea8fd99884fe29eec0267434152ccf5c5e Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 4 Jun 2026 14:09:27 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20verbessere=20SRS-Logik=20zur=20Bestimmu?= =?UTF-8?q?ng=20des=20n=C3=A4chsten=20F=C3=A4lligkeitsdatums=20f=C3=BCr=20?= =?UTF-8?q?neu=20erstellte=20Elemente=20und=20erweitere=20die=20API-Anfrag?= =?UTF-8?q?en=20f=C3=BCr=20kursweite=20f=C3=A4llige=20Items?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/services/vocabService.js | 13 ++++++- .../socialnetwork/VocabPracticeDialog.vue | 38 +++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index 1d11509..17afc83 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -191,6 +191,17 @@ export default class VocabService { if (existingByKey.has(entry.itemKey)) { continue; } + + // Determine safe default nextDueAt for newly created items: + // - default: next calendar day at 08:00 + // - ensure at least 24 hours after creation + const createdAt = new Date(now); + const nextCalendar08 = new Date(createdAt); + nextCalendar08.setHours(8, 0, 0, 0); + nextCalendar08.setDate(nextCalendar08.getDate() + 1); + const min24h = new Date(createdAt.getTime() + 24 * 60 * 60 * 1000); + const safeNextDue = nextCalendar08.getTime() < min24h.getTime() ? min24h : nextCalendar08; + const created = await VocabSrsItem.create({ userId, courseId: Number(courseId), @@ -199,7 +210,7 @@ export default class VocabService { learning: entry.learning, reference: entry.reference, direction: entry.direction, - nextDueAt: now + nextDueAt: safeNextDue }); createdItems.push(created); existingByKey.set(entry.itemKey, created); diff --git a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue index 7722ca7..0f62500 100644 --- a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue +++ b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue @@ -768,10 +768,12 @@ export default { let res = null; let courseDueRes = null; - // Wenn SRS-Modus auf Kurs-Ebene, lade kursweite fällige Items (Server liefert totalDueCount) - if (this.srsMode && this.openParams.courseId && !this.openParams.lessonId) { + // Wenn SRS-Modus und courseId vorhanden, frage kursweite fällige Items an (Server liefert totalDueCount). + // Wir fragen das immer an, auch wenn gerade eine lessonId gesetzt ist, und nutzen es als Fallback + // falls kein lesson-spezifischer Pool gefunden wird. + if (this.srsMode && this.openParams.courseId) { try { - courseDueRes = await apiClient.get(`/api/vocab/courses/${this.openParams.courseId}/srs/due`, { params: { limit: 100 } }); + courseDueRes = await apiClient.get(`/api/vocab/courses/${this.openParams.courseId}/srs/due`, { params: { limit: 500 } }); } catch (err) { courseDueRes = null; } @@ -786,6 +788,36 @@ export default { lessonId: it.lessonId || null }))); this.srsServerTotalDue = Number.isFinite(Number(courseDueRes.data?.totalDueCount)) ? Number(courseDueRes.data.totalDueCount) : null; + } + + // Wenn wir eine lessonId haben, versuche zuerst lesson-spezifische Vocabs zu laden. + // Falls diese leer sind, und kursweite SRS-Items vorliegen, nutze diese als Fallback. + if (this.openParams.lessonId) { + try { + if (this.allVocabs && this.openParams.courseId) { + res = await apiClient.get(`/api/vocab/courses/${this.openParams.courseId}/completed-lesson-vocabs`, { + params: { untilLessonId: this.openParams.lessonId } + }); + this.pool = this.normalizePool(res.data?.vocabs || []); + } else { + res = await apiClient.get(`/api/vocab/lessons/${this.openParams.lessonId}/vocab-pool`); + this.pool = this.normalizePool(res.data?.vocabs || []); + } + } catch (err) { + res = null; + this.pool = []; + } + + // Fallback auf kursweite SRS-Items, falls keine lesson-spezifischen Items vorhanden sind + if ((!this.pool || this.pool.length === 0) && courseDueRes && Array.isArray(courseDueRes.data?.items) && courseDueRes.data.items.length > 0) { + this.pool = this.normalizePool((courseDueRes.data.items || []).map((it) => ({ + id: it.itemKey, + learning: it.learning, + reference: it.reference, + lessonId: it.lessonId || null + }))); + this.srsServerTotalDue = Number.isFinite(Number(courseDueRes.data?.totalDueCount)) ? Number(courseDueRes.data.totalDueCount) : null; + } } else { // Fallback: lade Lehr- bzw. Kapitel-/Kurs-Vokabeln if (this.openParams.lessonId) {