feat(frontend): progress van assignment wordt ook getoond voor studenten

This commit is contained in:
Joyelle Ndagijimana 2025-04-19 14:48:13 +02:00
parent 5b17bad368
commit 1d29adaa31
7 changed files with 138 additions and 64 deletions

View file

@ -71,9 +71,7 @@
"class": "klasse", "class": "klasse",
"delete": "löschen", "delete": "löschen",
"view-assignment": "Auftrag anzeigen", "view-assignment": "Auftrag anzeigen",
"legendTeacherExclusive": "Information für Lehrkräfte",
"code": "code", "code": "code",
"class": "Klasse",
"invitations": "Einladungen", "invitations": "Einladungen",
"createClass": "Klasse erstellen", "createClass": "Klasse erstellen",
"createClassInstructions": "Geben Sie einen Namen für Ihre Klasse ein und klicken Sie auf „Erstellen“. Es erscheint ein Fenster mit einem Code, den Sie kopieren können. Geben Sie diesen Code an Ihre Schüler weiter und sie können Ihrer Klasse beitreten.", "createClassInstructions": "Geben Sie einen Namen für Ihre Klasse ein und klicken Sie auf „Erstellen“. Es erscheint ein Fenster mit einem Code, den Sie kopieren können. Geben Sie diesen Code an Ihre Schüler weiter und sie können Ihrer Klasse beitreten.",
@ -90,5 +88,10 @@
"sent": "sent", "sent": "sent",
"failed": "gescheitert", "failed": "gescheitert",
"wrong": "etwas ist schief gelaufen", "wrong": "etwas ist schief gelaufen",
"created": "erstellt" "created": "erstellt",
"group": "Gruppe",
"description": "Beschreibung",
"no-submission": "keine vorlage",
"submission": "einreichung",
"progress": "Fortschritte"
} }

View file

@ -2,8 +2,8 @@
"welcome": "Welcome", "welcome": "Welcome",
"student": "student", "student": "student",
"teacher": "teacher", "teacher": "teacher",
"assignments": "assignments", "assignments": "Assignments",
"classes": "classes", "classes": "Classes",
"discussions": "discussions", "discussions": "discussions",
"logout": "log out", "logout": "log out",
"error_title": "Error", "error_title": "Error",
@ -88,5 +88,10 @@
"sent": "sent", "sent": "sent",
"failed": "failed", "failed": "failed",
"wrong": "something went wrong", "wrong": "something went wrong",
"created": "created" "created": "created",
"group": "Group",
"description": "Description",
"no-submission": "no submission",
"submission": "submission",
"progress": "Progress"
} }

View file

@ -71,9 +71,7 @@
"class": "classe", "class": "classe",
"delete": "supprimer", "delete": "supprimer",
"view-assignment": "Voir le travail", "view-assignment": "Voir le travail",
"read-more": "En savoir plus",
"code": "code", "code": "code",
"class": "classe",
"invitations": "invitations", "invitations": "invitations",
"createClass": "créer une classe", "createClass": "créer une classe",
"createClassInstructions": "Entrez un nom pour votre classe et cliquez sur créer. Une fenêtre apparaît avec un code que vous pouvez copier. Donnez ce code à vos élèves et ils pourront rejoindre votre classe.", "createClassInstructions": "Entrez un nom pour votre classe et cliquez sur créer. Une fenêtre apparaît avec un code que vous pouvez copier. Donnez ce code à vos élèves et ils pourront rejoindre votre classe.",
@ -90,5 +88,10 @@
"sent": "envoyé", "sent": "envoyé",
"failed": "échoué", "failed": "échoué",
"wrong": "quelque chose n'a pas fonctionné", "wrong": "quelque chose n'a pas fonctionné",
"created": "créé" "created": "créé",
"group": "Groupe",
"description": "Description",
"no-submission": "aucune soumission",
"submission": "soumission",
"progress": "Progrès"
} }

View file

@ -2,8 +2,8 @@
"welcome": "Welkom", "welcome": "Welkom",
"student": "leerling", "student": "leerling",
"teacher": "leerkracht", "teacher": "leerkracht",
"assignments": "opdrachten", "assignments": "Opdrachten",
"classes": "klassen", "classes": "Klassen",
"discussions": "discussies", "discussions": "discussies",
"logout": "log uit", "logout": "log uit",
"error_title": "Fout", "error_title": "Fout",
@ -71,9 +71,7 @@
"class": "klas", "class": "klas",
"delete": "verwijderen", "delete": "verwijderen",
"view-assignment": "Opdracht bekijken", "view-assignment": "Opdracht bekijken",
"read-more": "Lees meer",
"code": "code", "code": "code",
"class": "klas",
"invitations": "uitnodigingen", "invitations": "uitnodigingen",
"createClass": "klas aanmaken", "createClass": "klas aanmaken",
"createClassInstructions": "Voer een naam in voor je klas en klik op create. Er verschijnt een venster met een code die je kunt kopiëren. Geef deze code aan je leerlingen en ze kunnen deelnemen aan je klas.", "createClassInstructions": "Voer een naam in voor je klas en klik op create. Er verschijnt een venster met een code die je kunt kopiëren. Geef deze code aan je leerlingen en ze kunnen deelnemen aan je klas.",
@ -90,5 +88,10 @@
"sent": "verzonden", "sent": "verzonden",
"failed": "mislukt", "failed": "mislukt",
"wrong": "er ging iets verkeerd", "wrong": "er ging iets verkeerd",
"created": "gecreëerd" "created": "gecreëerd",
"group": "Groep",
"description": "Beschrijving",
"no-submission": "geen indiening",
"submission": "indiening",
"progress": "Vooruitgang"
} }

View file

@ -1,10 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import auth from "@/services/auth/auth-service.ts"; import auth from "@/services/auth/auth-service.ts";
import {computed, ref} from "vue"; import {computed, reactive, type Ref, ref, watchEffect} from "vue";
import StudentAssignment from "@/views/assignments/StudentAssignment.vue"; import StudentAssignment from "@/views/assignments/StudentAssignment.vue";
import TeacherAssignment from "@/views/assignments/TeacherAssignment.vue"; import TeacherAssignment from "@/views/assignments/TeacherAssignment.vue";
import {useRoute} from "vue-router"; import {useRoute} from "vue-router";
import type {Language} from "@/data-objects/language.ts";
import {useGetLearningPathQuery} from "@/queries/learning-paths.ts";
import type {LearningPath} from "@/data-objects/learning-paths/learning-path.ts";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
const role = auth.authState.activeRole; const role = auth.authState.activeRole;
const isTeacher = computed(() => role === 'teacher'); const isTeacher = computed(() => role === 'teacher');
@ -13,18 +17,57 @@ const route = useRoute();
const classId = ref<string>(route.params.classId as string); const classId = ref<string>(route.params.classId as string);
const assignmentId = ref(Number(route.params.id)); const assignmentId = ref(Number(route.params.id));
function useGroupsWithProgress(
groups: Ref<GroupDTO[]>,
hruid: Ref<string>,
language: Ref<string>
): { groupProgressMap: Record<string, number> } {
const groupProgressMap: Record<string, number> = reactive({});
watchEffect(() => {
// Clear existing entries to avoid stale data
for (const key in groupProgressMap) {
delete groupProgressMap[key];
}
const lang = language.value as Language;
groups.value.forEach((group) => {
const groupKey = group.groupNumber.toString();
const query = useGetLearningPathQuery(hruid.value, lang, {
forGroup: groupKey,
});
const data = query.data.value;
groupProgressMap[groupKey] = data ? calculateProgress(data) : 0;
});
});
return {
groupProgressMap,
};
}
function calculateProgress(lp: LearningPath): number {
return ((lp.amountOfNodes - lp.amountOfNodesLeft) / lp.amountOfNodes) * 100;
}
</script> </script>
<template> <template>
<TeacherAssignment <TeacherAssignment
:class-id="classId" :class-id="classId"
:assignment-id="assignmentId" :assignment-id="assignmentId"
:use-groups-with-progress="useGroupsWithProgress"
v-if="isTeacher" v-if="isTeacher"
> >
</TeacherAssignment> </TeacherAssignment>
<StudentAssignment <StudentAssignment
:class-id="classId" :class-id="classId"
:assignment-id="assignmentId" :assignment-id="assignmentId"
:use-groups-with-progress="useGroupsWithProgress"
v-else v-else
> >
</StudentAssignment> </StudentAssignment>

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref, computed, defineProps} from "vue"; import {ref, computed, defineProps, type Ref} from "vue";
import auth from "@/services/auth/auth-service.ts"; import auth from "@/services/auth/auth-service.ts";
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
import {useAssignmentQuery} from "@/queries/assignments.ts"; import {useAssignmentQuery} from "@/queries/assignments.ts";
@ -10,14 +10,21 @@ import {useStudentsByUsernamesQuery} from "@/queries/students.ts";
import {useGroupsQuery} from "@/queries/groups.ts"; import {useGroupsQuery} from "@/queries/groups.ts";
import {useGetLearningPathQuery} from "@/queries/learning-paths.ts"; import {useGetLearningPathQuery} from "@/queries/learning-paths.ts";
import type {Language} from "@/data-objects/language.ts"; import type {Language} from "@/data-objects/language.ts";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
const props = defineProps<{ const props = defineProps<{
classId: string classId: string
assignmentId: number assignmentId: number,
useGroupsWithProgress: (
groups: Ref<GroupDTO[]>,
hruid: Ref<string>,
language: Ref<Language>
) => { groupProgressMap: Record<string, number> };
}>(); }>();
const {t, locale} = useI18n(); const {t, locale} = useI18n();
const language = computed(() => locale.value); const language = ref<Language>(locale.value as Language);
const learningPath = ref();
// Get the user's username/id // Get the user's username/id
const username = asyncComputed(async () => { const username = asyncComputed(async () => {
const user = await auth.loadUser(); const user = await auth.loadUser();
@ -25,13 +32,16 @@ const username = asyncComputed(async () => {
}); });
const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId); const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId);
learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath;
const submitted = ref(false);//TODO: update by fetching submissions and check if group submitted const submitted = ref(false);//TODO: update by fetching submissions and check if group submitted
const lpQueryResult = useGetLearningPathQuery( const lpQueryResult = useGetLearningPathQuery(
computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""), computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""),
computed(() => language.value as Language) computed(() => language.value)
); );
const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true);
const group = computed(() => const group = computed(() =>
groupsQueryResult?.data.value?.groups.find(group => groupsQueryResult?.data.value?.groups.find(group =>
@ -39,6 +49,18 @@ const group = computed(() =>
) )
); );
const groupArray = computed(() => (group.value ? [group.value] : []));
const progressValue = ref(0);
/* Crashes right now cause api data has inexistent hruid TODO: uncomment later and use it in progress bar
Const {groupProgressMap} = props.useGroupsWithProgress(
groupArray,
learningPath,
language
);
*/
// Assuming group.value.members is a list of usernames TODO: case when it's StudentDTO's // Assuming group.value.members is a list of usernames TODO: case when it's StudentDTO's
const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as string[]); const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as string[]);
@ -71,6 +93,7 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as
</v-chip> </v-chip>
</div> </div>
<v-card-title class="text-h4">{{ data.assignment.title }}</v-card-title> <v-card-title class="text-h4">{{ data.assignment.title }}</v-card-title>
<v-card-subtitle class="subtitle-section"> <v-card-subtitle class="subtitle-section">
<using-query-result <using-query-result
:query-result="lpQueryResult" :query-result="lpQueryResult"
@ -89,6 +112,25 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as
<v-card-text class="description"> <v-card-text class="description">
{{ data.assignment.description }} {{ data.assignment.description }}
</v-card-text> </v-card-text>
<v-card-text>
<v-row align="center" no-gutters>
<v-col cols="auto">
<span class="progress-label">{{ t("progress") + ": " }}</span>
</v-col>
<v-col>
<v-progress-linear
:model-value="progressValue"
color="primary"
height="20"
class="progress-bar"
>
<template v-slot:default="{ value }">
<strong>{{ Math.ceil(value) }}%</strong>
</template>
</v-progress-linear>
</v-col>
</v-row>
</v-card-text>
<v-card-text class="group-section"> <v-card-text class="group-section">
<h3>{{ t("group") }}</h3> <h3>{{ t("group") }}</h3>
@ -101,7 +143,6 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
</using-query-result> </using-query-result>
</div> </div>
@ -110,5 +151,14 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as
<style scoped> <style scoped>
@import "@/assets/assignment.css"; @import "@/assets/assignment.css";
.progress-label {
font-weight: bold;
margin-right: 5px;
}
.progress-bar {
width: 40%;
}
</style> </style>

View file

@ -1,19 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, defineProps, reactive, type Ref, ref, watchEffect} from "vue"; import {computed, defineProps, type Ref, ref} from "vue";
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
import {useAssignmentQuery, useDeleteAssignmentMutation} from "@/queries/assignments.ts"; import {useAssignmentQuery, useDeleteAssignmentMutation} from "@/queries/assignments.ts";
import UsingQueryResult from "@/components/UsingQueryResult.vue"; import UsingQueryResult from "@/components/UsingQueryResult.vue";
import {useGroupsQuery} from "@/queries/groups.ts"; import {useGroupsQuery} from "@/queries/groups.ts";
import {useGetLearningPathQuery} from "@/queries/learning-paths.ts"; import {useGetLearningPathQuery} from "@/queries/learning-paths.ts";
import type {Language} from "@/data-objects/language.ts"; import type {Language} from "@/data-objects/language.ts";
import type {LearningPath} from "@/data-objects/learning-paths/learning-path.ts";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
import router from "@/router"; import router from "@/router";
import type {AssignmentResponse} from "@/controllers/assignments.ts"; import type {AssignmentResponse} from "@/controllers/assignments.ts";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
const props = defineProps<{ const props = defineProps<{
classId: string classId: string
assignmentId: number assignmentId: number,
useGroupsWithProgress: (
groups: Ref<GroupDTO[]>,
hruid: Ref<string>,
language: Ref<Language>
) => { groupProgressMap: Record<string, number> };
}>(); }>();
const {t, locale} = useI18n(); const {t, locale} = useI18n();
@ -21,43 +25,6 @@ const language = computed(() => locale.value);
const groups = ref(); const groups = ref();
const learningPath = ref(); const learningPath = ref();
function useGroupsWithProgress(
groups: Ref<GroupDTO[]>,
hruid: Ref<string>,
language: Ref<string>
): { groupProgressMap: Record<string, number> } {
const groupProgressMap: Record<string, number> = reactive({});
watchEffect(() => {
// Clear existing entries to avoid stale data
for (const key in groupProgressMap) {
delete groupProgressMap[key];
}
const lang = language.value as Language;
groups.value.forEach((group) => {
const groupKey = group.groupNumber.toString();
const query = useGetLearningPathQuery(hruid.value, lang, {
forGroup: groupKey,
});
const data = query.data.value;
groupProgressMap[groupKey] = data ? calculateProgress(data) : 0;
});
});
return {
groupProgressMap,
};
}
function calculateProgress(lp: LearningPath): number {
return ((lp.amountOfNodes - lp.amountOfNodesLeft) / lp.amountOfNodes) * 100;
}
const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId); const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId);
learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath;
// Get learning path object // Get learning path object
@ -70,8 +37,8 @@ const lpQueryResult = useGetLearningPathQuery(
const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true);
groups.value = groupsQueryResult.data.value?.groups; groups.value = groupsQueryResult.data.value?.groups;
/* Crashes right now cause api data has inexistent hruid TODO: uncomment later /* Crashes right now cause api data has inexistent hruid TODO: uncomment later and use it in progress bar
Const {groupProgressMap} = useGroupsWithProgress( Const {groupProgressMap} = props.useGroupsWithProgress(
groups, groups,
learningPath, learningPath,
language language
@ -200,7 +167,7 @@ async function deleteAssignment(num: number, clsId: string): Promise<void> {
variant="text" variant="text"
class="text-capitalize" class="text-capitalize"
> >
{{ item.submitted ? t('see-submission') : t('not-submitted') }} {{ item.submitted ? t('see-submission') : t('no-submission') }}
</v-btn> </v-btn>
</template> </template>