From 68470bf576fa47c962dbf948f8e6ef00dc903e43 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 26 Aug 2026 10:16:41 +0200 Subject: [PATCH] feat(vocab): update intensive review UI and logic for better user guidance --- backend/services/vocabService.js | 24 +++++++------------ .../src/i18n/locales/ceb/socialnetwork.json | 11 ++++++--- .../src/i18n/locales/de/socialnetwork.json | 8 +++---- .../src/i18n/locales/en/socialnetwork.json | 8 +++---- .../src/i18n/locales/es/socialnetwork.json | 11 ++++++--- .../src/i18n/locales/fr/socialnetwork.json | 11 ++++++--- frontend/src/views/social/VocabCourseView.vue | 12 +--------- 7 files changed, 42 insertions(+), 43 deletions(-) diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index b35f0a5..d02a507 100755 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -2022,15 +2022,12 @@ export default class VocabService { const requestedLimit = this._clampInteger(query?.limit, { min: 1, max: DAILY_SRS_LIMIT, fallback: DAILY_SRS_LIMIT }); const limit = Math.min(DAILY_SRS_LIMIT, requestedLimit); const now = new Date(); - const dueWhere = { - userId: user.id, - courseId: Number(course.id), - nextDueAt: { - [Op.lte]: now - } - }; - const dueRows = await VocabSrsItem.findAll({ - where: dueWhere, + // SRS is a daily review: a term scheduled for any time today belongs to + // today's batch. Older rows remain due as well. Comparing calendar dates + // avoids an accidental time-of-day gate for a daily task. + const todayKey = this._getSrsScheduleDateKey(now); + const scheduledRows = await VocabSrsItem.findAll({ + where: { userId: user.id, courseId: Number(course.id) }, order: [ ['nextDueAt', 'ASC'], ['wrongCount', 'DESC'], @@ -2043,20 +2040,17 @@ export default class VocabService { .map((entry) => String(entry?.itemKey || '').trim()) .filter(Boolean) ); - const validDueRows = dueRows.filter((item) => + const validDueRows = scheduledRows.filter((item) => this._isTrainableSrsPair(item) && (!validKeys.size || validKeys.has(String(item.itemKey || '').trim())) + && this._getSrsScheduleDateKey(item.nextDueAt) <= todayKey ); - const scheduledRows = await VocabSrsItem.findAll({ - where: { userId: user.id, courseId: Number(course.id) }, - order: [['nextDueAt', 'ASC']] - }); const upcomingByDate = new Map(); scheduledRows .filter((item) => ( this._isTrainableSrsPair(item) && (!validKeys.size || validKeys.has(String(item.itemKey || '').trim())) - && new Date(item.nextDueAt).getTime() > now.getTime() + && this._getSrsScheduleDateKey(item.nextDueAt) > todayKey )) .forEach((item) => { const date = this._getSrsScheduleDateKey(item.nextDueAt); diff --git a/frontend/src/i18n/locales/ceb/socialnetwork.json b/frontend/src/i18n/locales/ceb/socialnetwork.json index 93c436a..affe5d4 100755 --- a/frontend/src/i18n/locales/ceb/socialnetwork.json +++ b/frontend/src/i18n/locales/ceb/socialnetwork.json @@ -441,9 +441,14 @@ "courseFlowBlockTitle": "Kasamtangang block", "courseFlowBlockDescription": "Dinhi nahimutang ang sunod regular nga lakang sa kurso.", "courseFlowBlockEmpty": "Nahuman na ang kasamtangang block o walay abli nga leksiyon niini karon.", - "courseFlowIntensiveTitle": "Angay nga intensive phase", - "courseFlowIntensiveDescription": "Mas dikit nga balik-balik sa dihang lig-on na ang nag-una nga block.", - "courseFlowIntensiveEmpty": "Wala pay bag-ong intensive phase nga naablihan karon.", + "courseFlowIntensiveTitle": "Sunod nga adlaw-adlaw nga balik-balik", + "courseFlowIntensiveDescription": "Ang petsa sa balik-balik sa imong mga termino nagsalig sa imong miaging sakto nga tubag.", + "courseFlowIntensiveEmpty": "Matan-aw nimo dinhi ang sunod nga mga petsa.", + "courseFlowIntensiveStatusAction": "Tan-awa ang sunod nga balik-balik", + "courseFlowIntensiveStatusTitle": "Sunod nga balik-balik sa bokabularyo", + "courseFlowIntensiveStatusAllDone": "Wala pay dugang nga balik-balik sa bokabularyo nga nakaplano alang sa nahuman nga mga leksiyon.", + "courseFlowIntensiveStatusProgress": "{count} ka termino", + "courseFlowIntensiveStatusNoDate": "Ang mga petsa nagsalig sa imong sakto nga tubag: 1, 3, 7, 14, 30, 60 ug 120 ka adlaw. Ang lisod nga mga termino gilain ug praktisahon sa lugar sa lisod nga mga termino.", "courseFlowPracticeTitle": "Libre nga pagpalalom", "courseFlowPracticeDescription": "Nahuman nang mga leksiyon para sa hinay-hinay nga dugang praktis gawas sa obligadong agi-anan.", "courseFlowPracticeEmpty": "Sa dihang makahuman ka sa unang mga leksiyon, makita sila dinhi para sa libre nga praktis.", diff --git a/frontend/src/i18n/locales/de/socialnetwork.json b/frontend/src/i18n/locales/de/socialnetwork.json index d6384ea..d7a87bb 100755 --- a/frontend/src/i18n/locales/de/socialnetwork.json +++ b/frontend/src/i18n/locales/de/socialnetwork.json @@ -771,14 +771,14 @@ "courseFlowBlockTitle": "Aktueller Block", "courseFlowBlockDescription": "Hier liegt der nächste reguläre Fortschritt im Kurs.", "courseFlowBlockEmpty": "Der aktuelle Block ist bereits abgeschlossen oder es gibt gerade keine offene Blocklektion.", - "courseFlowIntensiveTitle": "Fällige Intensivphase", - "courseFlowIntensiveDescription": "Zusätzliche Wiederholung für Begriffe, die du in der Tageswiederholung falsch beantwortet hast.", - "courseFlowIntensiveEmpty": "Aktuell sind keine Begriffe als schwer markiert.", + "courseFlowIntensiveTitle": "Nächste Tageswiederholungen", + "courseFlowIntensiveDescription": "Die Wiederholungstermine deiner Begriffe richten sich nach den bisherigen richtigen Antworten.", + "courseFlowIntensiveEmpty": "Die nächsten Termine kannst du hier einsehen.", "courseFlowIntensiveStatusAction": "Nächste Wiederholungen prüfen", "courseFlowIntensiveStatusTitle": "Nächste Vokabelwiederholungen", "courseFlowIntensiveStatusAllDone": "Für abgeschlossene Lektionen sind derzeit keine weiteren Vokabelwiederholungen geplant.", "courseFlowIntensiveStatusProgress": "{count} Begriffe", - "courseFlowIntensiveStatusNoDate": "Die Termine richten sich nach deinen richtigen Antworten: 1, 3, 7, 14, 30, 60 und 120 Tage. Eine falsche Antwort wird als schwer markiert und kommt zeitnah wieder.", + "courseFlowIntensiveStatusNoDate": "Die Termine richten sich nach deinen richtigen Antworten: 1, 3, 7, 14, 30, 60 und 120 Tage. Schwer markierte Begriffe werden getrennt im Bereich „Schwere Begriffe“ geübt.", "courseFlowPracticeTitle": "Freie Vertiefung", "courseFlowPracticeDescription": "Abgeschlossene Lektionen für lockeres Nachtrainieren außerhalb des Pflichtpfads.", "courseFlowPracticeEmpty": "Sobald du erste Lektionen abgeschlossen hast, erscheinen sie hier für freies Nachtrainieren.", diff --git a/frontend/src/i18n/locales/en/socialnetwork.json b/frontend/src/i18n/locales/en/socialnetwork.json index bb0ac50..1b1c9ec 100755 --- a/frontend/src/i18n/locales/en/socialnetwork.json +++ b/frontend/src/i18n/locales/en/socialnetwork.json @@ -771,14 +771,14 @@ "courseFlowBlockTitle": "Current block", "courseFlowBlockDescription": "This is where the next regular progress step in the course lives.", "courseFlowBlockEmpty": "The current block is already done or there is no open block lesson right now.", - "courseFlowIntensiveTitle": "Due intensive review", - "courseFlowIntensiveDescription": "Extra review for terms answered incorrectly in the daily review.", - "courseFlowIntensiveEmpty": "No terms are currently marked as hard.", + "courseFlowIntensiveTitle": "Upcoming daily reviews", + "courseFlowIntensiveDescription": "Each term's review date follows your previous correct answers.", + "courseFlowIntensiveEmpty": "You can view the upcoming dates here.", "courseFlowIntensiveStatusAction": "Check upcoming reviews", "courseFlowIntensiveStatusTitle": "Upcoming vocabulary reviews", "courseFlowIntensiveStatusAllDone": "No further vocabulary reviews are currently scheduled for completed lessons.", "courseFlowIntensiveStatusProgress": "{count} terms", - "courseFlowIntensiveStatusNoDate": "Dates are based on correct answers: 1, 3, 7, 14, 30, 60 and 120 days. An incorrect answer is marked hard and returns soon.", + "courseFlowIntensiveStatusNoDate": "Dates are based on correct answers: 1, 3, 7, 14, 30, 60 and 120 days. Terms marked hard are practised separately in the hard-terms area.", "courseFlowPracticeTitle": "Free practice", "courseFlowPracticeDescription": "Completed lessons for relaxed extra practice outside the required path.", "courseFlowPracticeEmpty": "As soon as you complete your first lessons, they will appear here for free practice.", diff --git a/frontend/src/i18n/locales/es/socialnetwork.json b/frontend/src/i18n/locales/es/socialnetwork.json index 9658268..f6cbbaa 100755 --- a/frontend/src/i18n/locales/es/socialnetwork.json +++ b/frontend/src/i18n/locales/es/socialnetwork.json @@ -750,9 +750,14 @@ "courseFlowBlockTitle": "Bloque actual", "courseFlowBlockDescription": "Aquí está el siguiente progreso regular dentro del curso.", "courseFlowBlockEmpty": "El bloque actual ya está terminado o ahora mismo no hay ninguna lección abierta del bloque.", - "courseFlowIntensiveTitle": "Fase intensiva pendiente", - "courseFlowIntensiveDescription": "Repaso concentrado cuando el bloque anterior ya está bastante asentado.", - "courseFlowIntensiveEmpty": "Ahora mismo no hay ninguna nueva fase intensiva desbloqueada.", + "courseFlowIntensiveTitle": "Próximos repasos diarios", + "courseFlowIntensiveDescription": "Las fechas de repaso de tus términos dependen de tus respuestas correctas anteriores.", + "courseFlowIntensiveEmpty": "Puedes consultar aquí las próximas fechas.", + "courseFlowIntensiveStatusAction": "Consultar próximos repasos", + "courseFlowIntensiveStatusTitle": "Próximos repasos de vocabulario", + "courseFlowIntensiveStatusAllDone": "Actualmente no hay más repasos de vocabulario programados para las lecciones completadas.", + "courseFlowIntensiveStatusProgress": "{count} términos", + "courseFlowIntensiveStatusNoDate": "Las fechas se basan en tus respuestas correctas: 1, 3, 7, 14, 30, 60 y 120 días. Los términos marcados como difíciles se practican por separado en el área de términos difíciles.", "courseFlowPracticeTitle": "Práctica libre", "courseFlowPracticeDescription": "Lecciones completadas para repasar con calma fuera del camino obligatorio.", "courseFlowPracticeEmpty": "En cuanto completes tus primeras lecciones, aparecerán aquí para práctica libre.", diff --git a/frontend/src/i18n/locales/fr/socialnetwork.json b/frontend/src/i18n/locales/fr/socialnetwork.json index 499797c..8867f9a 100755 --- a/frontend/src/i18n/locales/fr/socialnetwork.json +++ b/frontend/src/i18n/locales/fr/socialnetwork.json @@ -750,9 +750,14 @@ "courseFlowBlockTitle": "Bloc actuel", "courseFlowBlockDescription": "C'est là que se situe la prochaine progression régulière du cours.", "courseFlowBlockEmpty": "Le bloc en cours est déjà terminé ou il n'y a actuellement aucune leçon en bloc ouverte.", - "courseFlowIntensiveTitle": "Phase intensive due", - "courseFlowIntensiveDescription": "Répétition condensée dès que le bloc devant lui est largement en place.", - "courseFlowIntensiveEmpty": "Il n’y a actuellement aucune nouvelle phase intensive activée.", + "courseFlowIntensiveTitle": "Prochaines révisions quotidiennes", + "courseFlowIntensiveDescription": "Les dates de révision de vos termes dépendent de vos réponses correctes précédentes.", + "courseFlowIntensiveEmpty": "Vous pouvez consulter les prochaines dates ici.", + "courseFlowIntensiveStatusAction": "Vérifier les prochaines révisions", + "courseFlowIntensiveStatusTitle": "Prochaines révisions de vocabulaire", + "courseFlowIntensiveStatusAllDone": "Aucune autre révision de vocabulaire n’est actuellement prévue pour les leçons terminées.", + "courseFlowIntensiveStatusProgress": "{count} termes", + "courseFlowIntensiveStatusNoDate": "Les dates reposent sur vos réponses correctes : 1, 3, 7, 14, 30, 60 et 120 jours. Les termes marqués difficiles sont travaillés séparément dans la zone des termes difficiles.", "courseFlowPracticeTitle": "Approfondissement gratuit", "courseFlowPracticeDescription": "Cours terminés pour une formation de suivi occasionnelle en dehors du parcours obligatoire.", "courseFlowPracticeEmpty": "Dès que vous aurez terminé les premières leçons, elles apparaîtront ici pour une formation complémentaire gratuite.", diff --git a/frontend/src/views/social/VocabCourseView.vue b/frontend/src/views/social/VocabCourseView.vue index 21906ea..3ecfa2b 100755 --- a/frontend/src/views/social/VocabCourseView.vue +++ b/frontend/src/views/social/VocabCourseView.vue @@ -196,17 +196,7 @@

{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveDescription') }}

-
- -
-

{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveEmpty') }}

+

{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveEmpty') }}