feat(frontend): groups in assignments ophalen

This commit is contained in:
Joyelle Ndagijimana 2025-04-11 17:30:04 +02:00
parent 45cb020861
commit d6009ff878
8 changed files with 182 additions and 126 deletions

View file

@ -7,7 +7,7 @@
} }
.assignment-card { .assignment-card {
width: 85%; width: 80%;
padding: 2%; padding: 2%;
border-radius: 12px; border-radius: 12px;
} }

View file

@ -23,28 +23,12 @@ const {t, locale} = useI18n();
const role = ref(auth.authState.activeRole); const role = ref(auth.authState.activeRole);
const username = ref<string>(""); const username = ref<string>("");
async function submitForm(assignmentTitle: string, async function submitForm(assignmentDTO: AssignmentDTO): Promise<void> {
selectedLearningPath: string,
selectedClass: string,
groups: string[][],
deadline: string,
description: string,
currentLanguage: string): Promise<void> {
const assignmentDTO: AssignmentDTO = {
id: 0,
class: selectedClass,
title: assignmentTitle,
description: description,
learningPath: selectedLearningPath,
language: currentLanguage,
groups: groups,
//deadline: deadline,
};
//TODO: replace with query function //TODO: replace with query function
const controller: AssignmentController = new AssignmentController(selectedClass); const controller: AssignmentController = new AssignmentController(assignmentDTO.class);
await controller.createAssignment(assignmentDTO); await controller.createAssignment(assignmentDTO);
// Navigate back to all assignments
await router.push('/user/assignment'); await router.push('/user/assignment');
} }
@ -79,22 +63,30 @@ const description = ref('');
const groups = ref<string[][]>([]); const groups = ref<string[][]>([]);
// New group is added to the list // New group is added to the list
const addGroupToList = (students: string[]) => { function addGroupToList(students: string[]): void {
if (students.length) { if (students.length) {
groups.value = [...groups.value, students]; groups.value = [...groups.value, students];
} }
}; }
watch(selectedClass, () => { watch(selectedClass, () => {
groups.value = []; groups.value = [];
}); });
const submitFormHandler = async () => { async function submitFormHandler(): Promise<void> {
const {valid} = await form.value.validate(); const {valid} = await form.value.validate();
// Don't submit the form if all rules don't apply // Don't submit the form if all rules don't apply
if (!valid) return; if (!valid) return;
await submitForm(assignmentTitle.value, selectedLearningPath.value?.hruid, selectedClass.value.id, groups.value, deadline.value, description.value, locale.value); const assignmentDTO: AssignmentDTO = {
}; id: 0,
class: selectedClass.value?.id || "",
title: assignmentTitle.value,
description: description.value,
learningPath: selectedLearningPath.value?.hruid || "",
language: language.value
}
await submitForm(assignmentDTO);
}
</script> </script>

View file

@ -7,7 +7,7 @@ import type {StudentsResponse} from "@/controllers/students.ts";
const props = defineProps<{ const props = defineProps<{
classId: string | undefined classId: string | undefined
groups: string[][], // All groups groups: string[][],
}>(); }>();
const emit = defineEmits(['groupCreated']); const emit = defineEmits(['groupCreated']);
const {t} = useI18n(); const {t} = useI18n();
@ -16,7 +16,6 @@ const selectedStudents = ref([]);
const studentQueryResult = useClassStudentsQuery(() => props.classId, true); const studentQueryResult = useClassStudentsQuery(() => props.classId, true);
function filterStudents(data: StudentsResponse): { title: string, value: string }[] { function filterStudents(data: StudentsResponse): { title: string, value: string }[] {
const students = data.students; const students = data.students;
const studentsInGroups = props.groups.flat(); const studentsInGroups = props.groups.flat();

View file

@ -1,8 +1,9 @@
import { computed, toValue } from "vue"; import {computed, type Ref, toValue} from "vue";
import type {MaybeRefOrGetter} from "vue"; import type {MaybeRefOrGetter} from "vue";
import { import {
type QueryObserverResult,
useMutation, useMutation,
type UseMutationReturnType, type UseMutationReturnType, useQueries,
useQuery, useQuery,
useQueryClient, useQueryClient,
type UseQueryReturnType, type UseQueryReturnType,
@ -27,27 +28,35 @@ const studentController = new StudentController();
function studentsQueryKey(full: boolean): [string, boolean] { function studentsQueryKey(full: boolean): [string, boolean] {
return ["students", full]; return ["students", full];
} }
function studentQueryKey(username: string): [string, string] { function studentQueryKey(username: string): [string, string] {
return ["student", username]; return ["student", username];
} }
function studentClassesQueryKey(username: string, full: boolean): [string, string, boolean] { function studentClassesQueryKey(username: string, full: boolean): [string, string, boolean] {
return ["student-classes", username, full]; return ["student-classes", username, full];
} }
function studentAssignmentsQueryKey(username: string, full: boolean): [string, string, boolean] { function studentAssignmentsQueryKey(username: string, full: boolean): [string, string, boolean] {
return ["student-assignments", username, full]; return ["student-assignments", username, full];
} }
function studentGroupsQueryKeys(username: string, full: boolean): [string, string, boolean] { function studentGroupsQueryKeys(username: string, full: boolean): [string, string, boolean] {
return ["student-groups", username, full]; return ["student-groups", username, full];
} }
function studentSubmissionsQueryKey(username: string): [string, string] { function studentSubmissionsQueryKey(username: string): [string, string] {
return ["student-submissions", username]; return ["student-submissions", username];
} }
function studentQuestionsQueryKey(username: string, full: boolean): [string, string, boolean] { function studentQuestionsQueryKey(username: string, full: boolean): [string, string, boolean] {
return ["student-questions", username, full]; return ["student-questions", username, full];
} }
export function studentJoinRequestsQueryKey(username: string): [string, string] { export function studentJoinRequestsQueryKey(username: string): [string, string] {
return ["student-join-requests", username]; return ["student-join-requests", username];
} }
export function studentJoinRequestQueryKey(username: string, classId: string): [string, string, string] { export function studentJoinRequestQueryKey(username: string, classId: string): [string, string, string] {
return ["student-join-request", username, classId]; return ["student-join-request", username, classId];
} }
@ -69,6 +78,21 @@ export function useStudentQuery(
}); });
} }
export function useStudentsByUsernamesQuery(
usernames: MaybeRefOrGetter<string[] | undefined>
): Ref<QueryObserverResult<StudentResponse>[]> {
const resolvedUsernames = toValue(usernames) ?? [];
return useQueries({
queries: resolvedUsernames?.map((username) => ({
queryKey: computed(() => studentQueryKey(toValue(username))),
queryFn: async () => studentController.getByUsername(toValue(username)),
enabled: Boolean(toValue(username)),
})),
});
}
export function useStudentClassesQuery( export function useStudentClassesQuery(
username: MaybeRefOrGetter<string | undefined>, username: MaybeRefOrGetter<string | undefined>,
full: MaybeRefOrGetter<boolean> = true, full: MaybeRefOrGetter<boolean> = true,

View file

@ -1,18 +1,48 @@
<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} from "vue"; import {computed, ref} 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 {asyncComputed} from "@vueuse/core";
import {GroupController} from "@/controllers/groups.ts";
import {useRoute} from "vue-router";
const role = auth.authState.activeRole; const role = auth.authState.activeRole;
const isTeacher = computed(() => role === 'teacher'); const isTeacher = computed(() => role === 'teacher');
// Get the user's username/id
const username = asyncComputed(async () => {
const user = await auth.loadUser();
return user?.profile?.preferred_username ?? undefined
});
const route = useRoute();
const assignmentId = ref(Number(route.params.id));
const classId = window.history.state?.class_id;
const groupController = new GroupController(classId, assignmentId.value);
const groupDTOs = asyncComputed(async () => await groupController.getAll(true));
console.log(groupDTOs.value);
</script> </script>
<template> <template>
<StudentAssignment v-if="!isTeacher"></StudentAssignment> <TeacherAssignment
<TeacherAssignment v-else></TeacherAssignment> :class-id="classId"
:assignment-id="assignmentId"
:groups="groupDTOs"
v-if="isTeacher"
>
</TeacherAssignment>
<StudentAssignment
:class-id="classId"
:assignment-id="assignmentId"
:groups="groupDTOs"
v-else
>
</StudentAssignment>
</template> </template>
<style scoped> <style scoped>

View file

@ -1,21 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRoute } from "vue-router"; import {ref, computed, defineProps} from "vue";
import {ref, computed} from "vue"; import auth from "@/services/auth/auth-service.ts";
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
import {AssignmentController} from "@/controllers/assignments.ts"; import {useAssignmentQuery} from "@/queries/assignments.ts";
import UsingQueryResult from "@/components/UsingQueryResult.vue";
import type {AssignmentResponse} from "@/controllers/assignments.ts";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
import {asyncComputed} from "@vueuse/core"; import {asyncComputed} from "@vueuse/core";
import {useStudentsByUsernamesQuery} from "@/queries/students.ts";
const props = defineProps<{
classId: string
assignmentId: number
groups: GroupDTO[] | undefined
}>();
const {t, locale} = useI18n(); const {t, locale} = useI18n();
const language = computed(() => locale.value); const language = computed(() => locale.value);
const route = useRoute(); // Get the user's username/id
const assignmentId = ref(Number(route.params.id)); const username = asyncComputed(async () => {
const classId = window.history.state?.class_id; const user = await auth.loadUser();
const controller = new AssignmentController(classId); return user?.profile?.preferred_username ?? undefined
});
const assignment = asyncComputed(async () => { const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId);
return await controller.getByNumber(assignmentId.value)
}, null);
const submitted = ref(true);//TODO: update by fetching submissions and check group const submitted = ref(true);//TODO: update by fetching submissions and check group
@ -23,23 +31,24 @@ const submitAssignment = async () => {
//TODO //TODO
}; };
const group = computed(() => {
/*** return props?.groups?.find(group =>
// Display group members group.members.some(m => m.username === username.value)
const myGroup = computed(() => {
if (!assignment.value || !assignment.value.groups) return null;
console.log(assignment.value.groups)
return assignment.value.groups.find(group =>
group.members.some(m => m.username === myUsername)
); );
}); });
*/
// Assuming group.value.members is a list of usernames
const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as string[]);
</script> </script>
<template> <template>
<div class="container"> <div class="container">
<v-card v-if="assignment" class="assignment-card"> <using-query-result
:query-result="assignmentQueryResult"
v-slot="{ data }: {data: AssignmentResponse}"
>
<v-card v-if="data" class="assignment-card">
<div class="top-buttons"> <div class="top-buttons">
<v-btn <v-btn
icon icon
@ -51,6 +60,7 @@ const submitAssignment = async () => {
</v-btn> </v-btn>
<v-chip <v-chip
v-if="submitted"
class="ma-2 top-right-btn" class="ma-2 top-right-btn"
label label
color="success" color="success"
@ -58,10 +68,10 @@ const submitAssignment = async () => {
{{ t("submitted") }} {{ t("submitted") }}
</v-chip> </v-chip>
</div> </div>
<v-card-title class="text-h4">{{ assignment.title }}</v-card-title> <v-card-title class="text-h4">{{ data.title }}</v-card-title>
<v-card-subtitle class="subtitle-section"> <v-card-subtitle class="subtitle-section">
<v-btn <v-btn
:to="`/learningPath/${language}/${assignment.learningPath}`" :to="`/learningPath/${language}/${data.learningPath}`"
variant="tonal" variant="tonal"
color="primary" color="primary"
> >
@ -70,22 +80,19 @@ const submitAssignment = async () => {
</v-card-subtitle> </v-card-subtitle>
<v-card-text class="description"> <v-card-text class="description">
{{ assignment.description }} {{ data.description }}
</v-card-text> </v-card-text>
<v-card-text class="group-section"> <v-card-text class="group-section">
<h3>{{ t("group") }}</h3> <h3>{{ t("group") }}</h3>
<pre>{{ props.groups }}</pre>
<!-- Student view <div v-if="studentQueries">
<div v-if="!isTeacher">
<div v-if="myGroup">
<ul> <ul>
<li v-for="student in myGroup.members" :key="student.username"> <li v-for="student in studentQueries" :key="student.data?.student.id">
{{ student.firstName + ' ' + student.lastName}} {{ student.data?.student.firstName + ' ' + student.data?.student.lastName }}
</li> </li>
</ul> </ul>
</div> </div>
</div>-->
</v-card-text> </v-card-text>
<v-card-actions class="justify-end"> <v-card-actions class="justify-end">
@ -100,6 +107,7 @@ const submitAssignment = async () => {
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</using-query-result>
</div> </div>
</template> </template>

View file

@ -1,20 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import {useRoute} from "vue-router"; import {computed, defineProps} from "vue";
import {ref, computed} from "vue";
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
import {AssignmentController, type AssignmentResponse} from "@/controllers/assignments.ts"; import {AssignmentController, type AssignmentResponse} from "@/controllers/assignments.ts";
import {useAssignmentQuery} from "@/queries/assignments.ts"; import {useAssignmentQuery} from "@/queries/assignments.ts";
import UsingQueryResult from "@/components/UsingQueryResult.vue"; import UsingQueryResult from "@/components/UsingQueryResult.vue";
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
const props = defineProps<{
classId: string
assignmentId: number
groups: GroupDTO[] | undefined
}>();
const {t, locale} = useI18n(); const {t, locale} = useI18n();
const language = computed(() => locale.value); const language = computed(() => locale.value);
const route = useRoute(); const controller = new AssignmentController(props.classId);
const assignmentId = ref(Number(route.params.id));
const classId = window.history.state?.class_id;
const controller = new AssignmentController(classId);
const assignmentQueryResult = useAssignmentQuery(() => classId, assignmentId); const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId);
/*** /***
// Display group members // Display group members
@ -28,7 +30,7 @@ const assignmentQueryResult = useAssignmentQuery(() => classId, assignmentId);
*/ */
const deleteAssignment = async () => { const deleteAssignment = async () => {
await controller.deleteAssignment(assignmentId.value); await controller.deleteAssignment(props.assignmentId.value);
}; };
</script> </script>

View file

@ -30,6 +30,7 @@ if (isTeacher.value) {
//TODO: replace with query from classes //TODO: replace with query from classes
const classController = new ClassController(); const classController = new ClassController();
//TODO: replace by query that fetches all user's assignment
const assignments = asyncComputed(async () => { const assignments = asyncComputed(async () => {
const classes = classesQueryResults?.data?.value?.classes; const classes = classesQueryResults?.data?.value?.classes;
if (!classes) return []; if (!classes) return [];