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:
@@ -36,6 +36,16 @@
|
||||
"loadCoursesError": "Dili makarga ang lista sa mga kurso.",
|
||||
"loadingLessons": "Nagkarga sa mga leksiyon …"
|
||||
},
|
||||
"vocabLessonMarkComplete": {
|
||||
"divider": "Ayuhon ang pag-uswag (dili paghimo og peke nga resulta sa ehersisyo)",
|
||||
"throughLabel": "Tanang leksiyon hangtod sa numero (lakip)",
|
||||
"hint": "I-mark ang kulang o abli nga mga row nga nahuman, lakip ang target score ug unang review wave. Ang nahuman na dili usbon.",
|
||||
"submit": "I-mark nga nahuman hangtod dinhi",
|
||||
"confirm": "I-mark nga nahuman ang tanang leksiyon nga numero ≤ {n} ni {username} niining kurso?",
|
||||
"success": "{marked} ka leksiyon nga bag-ong gi-mark nga nahuman ({unchanged} klaro nang nahuman).",
|
||||
"successNone": "Walay pagbag-o: tanang leksiyon nga naapektuhan ({unchanged}) klaro nang nahuman.",
|
||||
"error": "Dili ma-mark nga nahuman."
|
||||
},
|
||||
"rights": {
|
||||
"add": "Idugang ang katungod",
|
||||
"select": "Palihog pagpili",
|
||||
|
||||
@@ -45,6 +45,16 @@
|
||||
"loadCoursesError": "Die Kursliste konnte nicht geladen werden.",
|
||||
"loadingLessons": "Lektionen werden geladen …"
|
||||
},
|
||||
"vocabLessonMarkComplete": {
|
||||
"divider": "Fortschritt reparieren (ohne Übungsergebnisse zu fälschen)",
|
||||
"throughLabel": "Alle Lektionen bis Lektionsnummer (einschließlich)",
|
||||
"hint": "Setzt fehlende oder offene Einträge auf „abgeschlossen“, inkl. Ziel-Score und erster Review-Welle. Bereits abgeschlossene Lektionen bleiben unverändert.",
|
||||
"submit": "Bis hier als abgeschlossen markieren",
|
||||
"confirm": "Alle Lektionen mit Nummer ≤ {n} für {username} in diesem Kurs als abgeschlossen markieren?",
|
||||
"success": "{marked} Lektion(en) neu als abgeschlossen gesetzt ({unchanged} waren bereits erledigt).",
|
||||
"successNone": "Keine Änderung: alle betroffenen Lektionen ({unchanged}) waren bereits abgeschlossen.",
|
||||
"error": "Markieren fehlgeschlagen."
|
||||
},
|
||||
"adultVerification": {
|
||||
"title": "[Admin] - Erotik-Freigaben",
|
||||
"intro": "Volljährige Nutzer können den Erotikbereich beantragen. Hier werden Anfragen geprüft und freigegeben oder abgelehnt.",
|
||||
|
||||
@@ -45,6 +45,16 @@
|
||||
"loadCoursesError": "Could not load the course list.",
|
||||
"loadingLessons": "Loading lessons…"
|
||||
},
|
||||
"vocabLessonMarkComplete": {
|
||||
"divider": "Repair progress (does not fabricate exercise answers)",
|
||||
"throughLabel": "All lessons up to and including lesson number",
|
||||
"hint": "Marks missing or open rows as completed, including target score and first review wave. Already completed lessons are left unchanged.",
|
||||
"submit": "Mark through here as completed",
|
||||
"confirm": "Mark every lesson with number ≤ {n} for {username} in this course as completed?",
|
||||
"success": "{marked} lesson(s) newly marked complete ({unchanged} were already done).",
|
||||
"successNone": "No change: all affected lessons ({unchanged}) were already completed.",
|
||||
"error": "Could not mark lessons complete."
|
||||
},
|
||||
"adultVerification": {
|
||||
"title": "[Admin] - Erotic approvals",
|
||||
"intro": "Adult users can request access to the erotic area. Requests can be reviewed, approved or rejected here.",
|
||||
|
||||
@@ -45,6 +45,16 @@
|
||||
"loadCoursesError": "No se pudo cargar la lista de cursos.",
|
||||
"loadingLessons": "Cargando lecciones…"
|
||||
},
|
||||
"vocabLessonMarkComplete": {
|
||||
"divider": "Reparar progreso (no inventa resultados de ejercicios)",
|
||||
"throughLabel": "Todas las lecciones hasta el número (incluido)",
|
||||
"hint": "Marca filas faltantes o abiertas como completadas, con puntuación objetivo y primera ola de repaso. Las ya completadas no se cambian.",
|
||||
"submit": "Marcar hasta aquí como completadas",
|
||||
"confirm": "¿Marcar todas las lecciones con número ≤ {n} para {username} en este curso como completadas?",
|
||||
"success": "{marked} lección(es) marcadas como completadas ({unchanged} ya estaban hechas).",
|
||||
"successNone": "Sin cambios: todas las lecciones afectadas ({unchanged}) ya estaban completadas.",
|
||||
"error": "No se pudo marcar como completadas."
|
||||
},
|
||||
"adultVerification": {
|
||||
"title": "[Admin] - Aprobaciones eróticas",
|
||||
"intro": "Los usuarios adultos pueden solicitar acceso al área erótica. Aquí se revisan, aprueban o rechazan las solicitudes.",
|
||||
|
||||
@@ -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