extended admin tool for finished lessons
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
This commit is contained in:
@@ -82,6 +82,31 @@
|
||||
{{ vocabResetSubmitting ? $t('general.loading') : $t('admin.vocabLessonReset.reset') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<template v-if="vocabCourseLessons.length">
|
||||
<p class="vocab-reset__divider">{{ $t('admin.vocabLessonMarkComplete.divider') }}</p>
|
||||
<label class="edit__field">
|
||||
<span>{{ $t('admin.vocabLessonMarkComplete.throughLabel') }}</span>
|
||||
<input
|
||||
v-model.number="vocabMarkThroughNumber"
|
||||
type="number"
|
||||
min="1"
|
||||
:max="vocabMaxLessonNumber"
|
||||
:disabled="loadingVocabCourseDetail || vocabMarkSubmitting"
|
||||
class="vocab-reset__number"
|
||||
/>
|
||||
</label>
|
||||
<p class="vocab-reset__hint">{{ $t('admin.vocabLessonMarkComplete.hint') }}</p>
|
||||
<div class="vocab-reset__row">
|
||||
<button
|
||||
type="button"
|
||||
:disabled="!canMarkVocabThrough || vocabMarkSubmitting || loadingVocabCourseDetail"
|
||||
@click="adminMarkVocabLessonsThrough"
|
||||
>
|
||||
{{ vocabMarkSubmitting ? $t('general.loading') : $t('admin.vocabLessonMarkComplete.submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -105,6 +130,8 @@ export default {
|
||||
loadingVocabCourses: false,
|
||||
loadingVocabCourseDetail: false,
|
||||
vocabResetSubmitting: false,
|
||||
vocabMarkThroughNumber: null,
|
||||
vocabMarkSubmitting: false,
|
||||
vocabLoadError: false
|
||||
};
|
||||
},
|
||||
@@ -125,6 +152,15 @@ export default {
|
||||
selectLabel: dup ? `${c.title} (#${c.id})` : (c.title || `#${c.id}`)
|
||||
};
|
||||
});
|
||||
},
|
||||
vocabMaxLessonNumber() {
|
||||
const list = this.vocabCourseLessons;
|
||||
if (!list.length) return 1;
|
||||
return Math.max(...list.map((l) => Number(l.lessonNumber) || 0));
|
||||
},
|
||||
canMarkVocabThrough() {
|
||||
const n = Number(this.vocabMarkThroughNumber);
|
||||
return Number.isFinite(n) && n >= 1 && n <= this.vocabMaxLessonNumber;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -135,6 +171,7 @@ export default {
|
||||
this.selectedVocabCourseId = '';
|
||||
this.vocabCourseLessons = [];
|
||||
this.selectedVocabLessonId = '';
|
||||
this.vocabMarkThroughNumber = null;
|
||||
},
|
||||
async select(u) {
|
||||
const res = await apiClient.get(`/api/admin/users/${u.id}`);
|
||||
@@ -168,6 +205,7 @@ export default {
|
||||
this.selectedVocabCourseId = '';
|
||||
this.vocabCourseLessons = [];
|
||||
this.selectedVocabLessonId = '';
|
||||
this.vocabMarkThroughNumber = null;
|
||||
} catch (e) {
|
||||
console.error('[UsersView] vocab courses:', e);
|
||||
this.vocabCourses = [];
|
||||
@@ -179,6 +217,7 @@ export default {
|
||||
},
|
||||
async onVocabCourseChange() {
|
||||
this.selectedVocabLessonId = '';
|
||||
this.vocabMarkThroughNumber = null;
|
||||
this.vocabCourseLessons = [];
|
||||
if (!this.selectedVocabCourseId) {
|
||||
return;
|
||||
@@ -232,6 +271,45 @@ export default {
|
||||
} finally {
|
||||
this.vocabResetSubmitting = false;
|
||||
}
|
||||
},
|
||||
adminMarkVocabLessonsThrough() {
|
||||
if (!this.selected || !this.selectedVocabCourseId || !this.canMarkVocabThrough || this.vocabMarkSubmitting) {
|
||||
return;
|
||||
}
|
||||
const n = Number(this.vocabMarkThroughNumber);
|
||||
const msg = this.$t('admin.vocabLessonMarkComplete.confirm', {
|
||||
n,
|
||||
username: this.selected.username
|
||||
});
|
||||
if (!window.confirm(msg)) {
|
||||
return;
|
||||
}
|
||||
this.runAdminVocabMarkThrough();
|
||||
},
|
||||
async runAdminVocabMarkThrough() {
|
||||
this.vocabMarkSubmitting = true;
|
||||
try {
|
||||
const { data } = await apiClient.post(
|
||||
`/api/admin/users/${this.selected.id}/vocab-lesson-progress/mark-complete-through`,
|
||||
{
|
||||
courseId: Number(this.selectedVocabCourseId),
|
||||
throughLessonNumber: Number(this.vocabMarkThroughNumber)
|
||||
}
|
||||
);
|
||||
const details = Array.isArray(data?.details) ? data.details : [];
|
||||
const marked = details.filter((d) => d.status === 'marked_complete').length;
|
||||
const unchanged = details.filter((d) => d.status === 'unchanged').length;
|
||||
const msgKey =
|
||||
marked === 0 && unchanged > 0
|
||||
? 'admin.vocabLessonMarkComplete.successNone'
|
||||
: 'admin.vocabLessonMarkComplete.success';
|
||||
this.$root?.$refs?.messageDialog?.open?.(this.$t(msgKey, { marked, unchanged }));
|
||||
} catch (e) {
|
||||
console.error('[UsersView] admin vocab mark complete:', e);
|
||||
this.$root?.$refs?.messageDialog?.open?.(this.$t('admin.vocabLessonMarkComplete.error'));
|
||||
} finally {
|
||||
this.vocabMarkSubmitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -303,6 +381,18 @@ export default {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.vocab-reset__divider {
|
||||
margin: 8px 0 0;
|
||||
padding-top: 14px;
|
||||
border-top: 1px dashed var(--color-border);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.vocab-reset__number {
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.vocab-reset__row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user