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",
"delete": "löschen",
"view-assignment": "Auftrag anzeigen",
"legendTeacherExclusive": "Information für Lehrkräfte",
"code": "code",
"class": "Klasse",
"invitations": "Einladungen",
"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.",
@ -90,5 +88,10 @@
"sent": "sent",
"failed": "gescheitert",
"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",
"student": "student",
"teacher": "teacher",
"assignments": "assignments",
"classes": "classes",
"assignments": "Assignments",
"classes": "Classes",
"discussions": "discussions",
"logout": "log out",
"error_title": "Error",
@ -88,5 +88,10 @@
"sent": "sent",
"failed": "failed",
"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",
"delete": "supprimer",
"view-assignment": "Voir le travail",
"read-more": "En savoir plus",
"code": "code",
"class": "classe",
"invitations": "invitations",
"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.",
@ -90,5 +88,10 @@
"sent": "envoyé",
"failed": "échoué",
"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",
"student": "leerling",
"teacher": "leerkracht",
"assignments": "opdrachten",
"classes": "klassen",
"assignments": "Opdrachten",
"classes": "Klassen",
"discussions": "discussies",
"logout": "log uit",
"error_title": "Fout",
@ -71,9 +71,7 @@
"class": "klas",
"delete": "verwijderen",
"view-assignment": "Opdracht bekijken",
"read-more": "Lees meer",
"code": "code",
"class": "klas",
"invitations": "uitnodigingen",
"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.",
@ -90,5 +88,10 @@
"sent": "verzonden",
"failed": "mislukt",
"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">
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 TeacherAssignment from "@/views/assignments/TeacherAssignment.vue";
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 isTeacher = computed(() => role === 'teacher');
@ -13,18 +17,57 @@ const route = useRoute();
const classId = ref<string>(route.params.classId as string);
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>
<template>
<TeacherAssignment
:class-id="classId"
:assignment-id="assignmentId"
:use-groups-with-progress="useGroupsWithProgress"
v-if="isTeacher"
>
</TeacherAssignment>
<StudentAssignment
:class-id="classId"
:assignment-id="assignmentId"
:use-groups-with-progress="useGroupsWithProgress"
v-else
>
</StudentAssignment>

View file

@ -1,5 +1,5 @@
<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 {useI18n} from "vue-i18n";
import {useAssignmentQuery} from "@/queries/assignments.ts";
@ -10,14 +10,21 @@ import {useStudentsByUsernamesQuery} from "@/queries/students.ts";
import {useGroupsQuery} from "@/queries/groups.ts";
import {useGetLearningPathQuery} from "@/queries/learning-paths.ts";
import type {Language} from "@/data-objects/language.ts";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
const props = defineProps<{
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 language = computed(() => locale.value);
const language = ref<Language>(locale.value as Language);
const learningPath = ref();
// Get the user's username/id
const username = asyncComputed(async () => {
const user = await auth.loadUser();
@ -25,13 +32,16 @@ const username = asyncComputed(async () => {
});
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 lpQueryResult = useGetLearningPathQuery(
computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""),
computed(() => language.value as Language)
computed(() => language.value)
);
const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true);
const group = computed(() =>
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
const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as string[]);
@ -71,6 +93,7 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as
</v-chip>
</div>
<v-card-title class="text-h4">{{ data.assignment.title }}</v-card-title>
<v-card-subtitle class="subtitle-section">
<using-query-result
:query-result="lpQueryResult"
@ -89,6 +112,25 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as
<v-card-text class="description">
{{ data.assignment.description }}
</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">
<h3>{{ t("group") }}</h3>
@ -101,7 +143,6 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as
</div>
</v-card-text>
</v-card>
</using-query-result>
</div>
@ -110,5 +151,14 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as
<style scoped>
@import "@/assets/assignment.css";
.progress-label {
font-weight: bold;
margin-right: 5px;
}
.progress-bar {
width: 40%;
}
</style>

View file

@ -1,19 +1,23 @@
<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 {useAssignmentQuery, useDeleteAssignmentMutation} from "@/queries/assignments.ts";
import UsingQueryResult from "@/components/UsingQueryResult.vue";
import {useGroupsQuery} from "@/queries/groups.ts";
import {useGetLearningPathQuery} from "@/queries/learning-paths.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 type {AssignmentResponse} from "@/controllers/assignments.ts";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
const props = defineProps<{
classId: string
assignmentId: number
assignmentId: number,
useGroupsWithProgress: (
groups: Ref<GroupDTO[]>,
hruid: Ref<string>,
language: Ref<Language>
) => { groupProgressMap: Record<string, number> };
}>();
const {t, locale} = useI18n();
@ -21,43 +25,6 @@ const language = computed(() => locale.value);
const groups = 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);
learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath;
// Get learning path object
@ -70,8 +37,8 @@ const lpQueryResult = useGetLearningPathQuery(
const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true);
groups.value = groupsQueryResult.data.value?.groups;
/* Crashes right now cause api data has inexistent hruid TODO: uncomment later
Const {groupProgressMap} = useGroupsWithProgress(
/* Crashes right now cause api data has inexistent hruid TODO: uncomment later and use it in progress bar
Const {groupProgressMap} = props.useGroupsWithProgress(
groups,
learningPath,
language
@ -200,7 +167,7 @@ async function deleteAssignment(num: number, clsId: string): Promise<void> {
variant="text"
class="text-capitalize"
>
{{ item.submitted ? t('see-submission') : t('not-submitted') }}
{{ item.submitted ? t('see-submission') : t('no-submission') }}
</v-btn>
</template>