feat(vocab): implement scheduled review management in VocabService and UI updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
- Added methods in VocabService to handle scheduled review states, including normalization of review dates and management of review stages. - Enhanced lesson state management to support review scheduling, improving the learning process for users. - Updated VocabCourseView and VocabLessonView to display review statuses and due dates, providing clearer feedback on lesson progress and review requirements. - Introduced new UI elements to indicate review status, enhancing user engagement and understanding of lesson timelines.
This commit is contained in:
@@ -54,6 +54,17 @@
|
||||
<strong>{{ effectiveExercises?.length || 0 }} {{ $t('socialnetwork.vocab.courses.exercisesShort') }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="lessonReviewBadgeLabel"
|
||||
class="lesson-review-status"
|
||||
:class="lessonReviewStatusClass"
|
||||
>
|
||||
<div class="lesson-review-status__top">
|
||||
<span class="lesson-review-status__badge">{{ lessonReviewBadgeLabel }}</span>
|
||||
<strong>{{ lessonReviewHeadline }}</strong>
|
||||
</div>
|
||||
<p>{{ lessonReviewHint }}</p>
|
||||
</div>
|
||||
<details
|
||||
v-if="lessonPedagogy.phaseLabel || lessonPedagogy.newUnitTarget || lessonPedagogy.reviewWeight != null"
|
||||
class="lesson-overview-more"
|
||||
@@ -1212,6 +1223,57 @@ export default {
|
||||
isIntensiveReview: false
|
||||
};
|
||||
},
|
||||
lessonProgress() {
|
||||
return this.lesson?.progress || null;
|
||||
},
|
||||
lessonReviewBadgeLabel() {
|
||||
const progress = this.lessonProgress;
|
||||
if (!progress?.completed) {
|
||||
return '';
|
||||
}
|
||||
const stage = Number(progress.reviewStage || 0);
|
||||
if (stage === 0) return 'Tag 1';
|
||||
if (stage === 1) return 'Tag 3';
|
||||
if (stage === 2) return 'Tag 7';
|
||||
if (stage >= 3) return 'Review abgeschlossen';
|
||||
return '';
|
||||
},
|
||||
lessonReviewStatusClass() {
|
||||
const progress = this.lessonProgress;
|
||||
if (!progress?.completed) {
|
||||
return '';
|
||||
}
|
||||
if (progress.reviewCompleted) {
|
||||
return 'lesson-review-status--done';
|
||||
}
|
||||
if (progress.reviewDue) {
|
||||
return 'lesson-review-status--due';
|
||||
}
|
||||
return 'lesson-review-status--scheduled';
|
||||
},
|
||||
lessonReviewHeadline() {
|
||||
const progress = this.lessonProgress;
|
||||
if (!progress?.completed) {
|
||||
return '';
|
||||
}
|
||||
if (progress.reviewCompleted) {
|
||||
return 'Diese Lektion ist in der freien Vertiefung angekommen.';
|
||||
}
|
||||
if (progress.reviewDue) {
|
||||
return 'Diese Review-Welle ist jetzt fällig.';
|
||||
}
|
||||
return 'Diese Lektion ist für die nächste Review-Welle vorgemerkt.';
|
||||
},
|
||||
lessonReviewHint() {
|
||||
const progress = this.lessonProgress;
|
||||
if (!progress?.completed) {
|
||||
return '';
|
||||
}
|
||||
if (progress.reviewCompleted) {
|
||||
return 'Die 1/3/7-Tage-Wiederholung ist abgeschlossen. Du kannst die Lektion jetzt flexibel weitertrainieren.';
|
||||
}
|
||||
return `Nächste Fälligkeit: ${this.formatLessonReviewDue(progress.reviewNextDueAt)}.`;
|
||||
},
|
||||
assistantAvailable() {
|
||||
if (!this.assistantSettings) {
|
||||
return false;
|
||||
@@ -1993,6 +2055,31 @@ export default {
|
||||
}
|
||||
return this.$t('socialnetwork.vocab.courses.durationMinutes', { minutes });
|
||||
},
|
||||
formatLessonReviewDue(reviewNextDueAt) {
|
||||
if (!reviewNextDueAt) {
|
||||
return 'jetzt';
|
||||
}
|
||||
const dueTimestamp = new Date(reviewNextDueAt).getTime();
|
||||
if (!Number.isFinite(dueTimestamp)) {
|
||||
return 'jetzt';
|
||||
}
|
||||
const diffMs = dueTimestamp - Date.now();
|
||||
if (diffMs > 0) {
|
||||
const untilDays = Math.ceil(diffMs / (24 * 60 * 60 * 1000));
|
||||
if (untilDays <= 1) {
|
||||
return 'morgen';
|
||||
}
|
||||
return `in ${untilDays} Tagen`;
|
||||
}
|
||||
const overdueDays = Math.floor((Date.now() - dueTimestamp) / (24 * 60 * 60 * 1000));
|
||||
if (overdueDays <= 0) {
|
||||
return 'heute';
|
||||
}
|
||||
if (overdueDays === 1) {
|
||||
return 'seit 1 Tag';
|
||||
}
|
||||
return `seit ${overdueDays} Tagen`;
|
||||
},
|
||||
getQuestionData(exercise) {
|
||||
if (!exercise.questionData) return null;
|
||||
return typeof exercise.questionData === 'string'
|
||||
@@ -3040,6 +3127,61 @@ export default {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.lesson-review-status {
|
||||
padding: 14px 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(34, 96, 164, 0.18);
|
||||
background: rgba(235, 244, 255, 0.86);
|
||||
color: #21598f;
|
||||
}
|
||||
|
||||
.lesson-review-status__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.lesson-review-status__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(34, 96, 164, 0.14);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.lesson-review-status strong,
|
||||
.lesson-review-status p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.lesson-review-status p {
|
||||
margin-top: 8px;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.lesson-review-status--due {
|
||||
border-color: rgba(185, 99, 24, 0.22);
|
||||
background: rgba(255, 246, 226, 0.92);
|
||||
color: #8d5412;
|
||||
}
|
||||
|
||||
.lesson-review-status--due .lesson-review-status__badge {
|
||||
background: rgba(185, 99, 24, 0.16);
|
||||
}
|
||||
|
||||
.lesson-review-status--done {
|
||||
border-color: rgba(68, 138, 86, 0.22);
|
||||
background: rgba(239, 250, 242, 0.92);
|
||||
color: #2f6b3d;
|
||||
}
|
||||
|
||||
.lesson-review-status--done .lesson-review-status__badge {
|
||||
background: rgba(68, 138, 86, 0.14);
|
||||
}
|
||||
|
||||
.lesson-meta-label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
|
||||
Reference in New Issue
Block a user