feat: nieuwe lijstview voor assignment

This commit is contained in:
Adriaan Jacquet 2025-05-02 14:01:00 +02:00
parent c03669eda7
commit 7e4e179121

View file

@ -1,188 +1,210 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, watch } from "vue"; import { ref, computed, onMounted, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import auth from "@/services/auth/auth-service.ts"; import authState from "@/services/auth/auth-service.ts";
import { useTeacherClassesQuery } from "@/queries/teachers.ts"; import auth from "@/services/auth/auth-service.ts";
import { useStudentClassesQuery } from "@/queries/students.ts"; import { useTeacherAssignmentsQuery, useTeacherClassesQuery } from "@/queries/teachers.ts";
import { ClassController } from "@/controllers/classes.ts"; import { useStudentAssignmentsQuery, useStudentClassesQuery } from "@/queries/students.ts";
import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; import { ClassController } from "@/controllers/classes.ts";
import { asyncComputed } from "@vueuse/core"; import type { ClassDTO } from "@dwengo-1/common/interfaces/class";
import { useDeleteAssignmentMutation } from "@/queries/assignments.ts"; import { asyncComputed } from "@vueuse/core";
import { useDeleteAssignmentMutation } from "@/queries/assignments.ts";
import type { AssignmentsResponse } from "@/controllers/assignments";
import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment";
import UsingQueryResult from "@/components/UsingQueryResult.vue";
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();
const role = ref(auth.authState.activeRole); const role = ref(auth.authState.activeRole);
const username = ref<string>(""); const username = ref<string | undefined>(undefined);
const isLoading = ref(false);
const isError = ref(false);
const errorMessage = ref<string>("");
const isTeacher = computed(() => role.value === "teacher"); // Load current user before rendering the page
onMounted(async () => {
// Fetch and store all the teacher's classes isLoading.value = true;
let classesQueryResults = undefined; try {
const userObject = await authState.loadUser();
if (isTeacher.value) { username.value = userObject!.profile.preferred_username;
classesQueryResults = useTeacherClassesQuery(username, true); } catch (error) {
} else { isError.value = true;
classesQueryResults = useStudentClassesQuery(username, true); errorMessage.value = error instanceof Error ? error.message : String(error);
} finally {
isLoading.value = false;
} }
});
//TODO: remove later const isTeacher = computed(() => role.value === "teacher");
const classController = new ClassController();
//TODO: replace by query that fetches all user's assignment const assignmentsQuery = isTeacher ? useTeacherAssignmentsQuery(username, true) : useStudentAssignmentsQuery(username, true);
const assignments = asyncComputed(async () => {
const classes = classesQueryResults?.data?.value?.classes;
if (!classes) return [];
const result = await Promise.all(
(classes as ClassDTO[]).map(async (cls) => {
const { assignments } = await classController.getAssignments(cls.id);
return assignments.map((a) => ({
id: a.id,
class: cls,
title: a.title,
description: a.description,
learningPath: a.learningPath,
language: a.language,
groups: a.groups,
}));
}),
);
return result.flat(); async function goToCreateAssignment(): Promise<void> {
}, []); await router.push("/assignment/create");
}
async function goToCreateAssignment(): Promise<void> { async function goToAssignmentDetails(id: number, clsId: string): Promise<void> {
await router.push("/assignment/create"); await router.push(`/assignment/${clsId}/${id}`);
}
const { mutate, data, isSuccess } = useDeleteAssignmentMutation();
watch([isSuccess, data], async ([success, oldData]) => {
if (success && oldData?.assignment) {
window.location.reload();
} }
});
async function goToAssignmentDetails(id: number, clsId: string): Promise<void> { async function goToDeleteAssignment(num: number, clsId: string): Promise<void> {
await router.push(`/assignment/${clsId}/${id}`); mutate({ cid: clsId, an: num });
} }
const { mutate, data, isSuccess } = useDeleteAssignmentMutation(); onMounted(async () => {
const user = await auth.loadUser();
watch([isSuccess, data], async ([success, oldData]) => { username.value = user?.profile?.preferred_username ?? "";
if (success && oldData?.assignment) { });
window.location.reload();
}
});
async function goToDeleteAssignment(num: number, clsId: string): Promise<void> {
mutate({ cid: clsId, an: num });
}
onMounted(async () => {
const user = await auth.loadUser();
username.value = user?.profile?.preferred_username ?? "";
});
</script> </script>
<template> <template>
<div class="assignments-container"> <main>
<h1>{{ t("assignments") }}</h1> <h1>{{ t("assignments") }}</h1>
<div class="loading-div" v-if="isLoading">
<v-progress-circular indeterminate></v-progress-circular>
</div>
<div v-if="isError">
<v-empty-state icon="mdi-alert-circle-outline" :text="errorMessage"
:title="t('error_title')"></v-empty-state>
</div>
<div v-else>
<using-query-result :query-result="assignmentsQuery"
v-slot="assignmentsResponse: { data: AssignmentsResponse }">
<v-btn v-if="isTeacher" color="primary" class="mb-4 center-btn" @click="goToCreateAssignment">
{{ t("new-assignment") }}
</v-btn>
<v-container>
<v-table class="table">
<thead>
<tr>
<th class="header">{{ t("assignments") }}</th>
<th class="header">
{{ t("class") }}
</th>
<th class="header">{{ t("groups") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="a in assignmentsResponse.data.assignments as AssignmentDTO[]"
:key="a.id + a.within">
<td>
<v-btn :to="`/class/${a.within}`" variant="text">
{{ a.title }}
<v-icon end> mdi-menu-right </v-icon>
</v-btn>
</td>
<td>
<span>{{ a.within }}</span>
<!-- <span v-if="!isMdAndDown">{{ c.id }}</span>
<span v-else style="cursor: pointer" @click="openCodeDialog(c.id)"><v-icon
icon="mdi-eye"></v-icon></span> -->
</td>
<v-btn <td>{{ a.groups.length }}</td>
v-if="isTeacher" </tr>
color="primary" </tbody>
class="mb-4 center-btn" </v-table>
@click="goToCreateAssignment" </v-container>
> </using-query-result>
{{ t("new-assignment") }} </div>
</v-btn> <div class="assignments-container">
<using-query-result :query-result="assignmentsQuery"
v-slot="assignmentsResponse: { data: AssignmentsResponse }">
<v-container>
<v-row>
<v-col v-for="assignment in assignmentsResponse.data.assignments as AssignmentDTO[]"
:key="assignment.id + assignment.within" cols="12">
<v-card class="assignment-card">
<div class="top-content">
<div class="assignment-title">{{ assignment.title }}</div>
<div class="assignment-class">
{{ t("class") }}:
<span class="class-name">
{{ assignment.within }}
</span>
</div>
</div>
<v-container> <div class="spacer"></div>
<v-row>
<v-col
v-for="assignment in assignments"
:key="assignment.id"
cols="12"
>
<v-card class="assignment-card">
<div class="top-content">
<div class="assignment-title">{{ assignment.title }}</div>
<div class="assignment-class">
{{ t("class") }}:
<span class="class-name">
{{ assignment.class.displayName }}
</span>
</div>
</div>
<div class="spacer"></div> <div class="button-row">
<v-btn color="primary" variant="text"
@click="goToAssignmentDetails(assignment.id, assignment.within)">
{{ t("view-assignment") }}
</v-btn>
<v-btn v-if="isTeacher" color="red" variant="text"
@click="goToDeleteAssignment(assignment.id, assignment.within)">
{{ t("delete") }}
</v-btn>
</div>
</v-card>
</v-col>
</v-row>
</v-container>
</using-query-result>
</div>
</main>
<div class="button-row">
<v-btn
color="primary"
variant="text"
@click="goToAssignmentDetails(assignment.id, assignment.class.id)"
>
{{ t("view-assignment") }}
</v-btn>
<v-btn
v-if="isTeacher"
color="red"
variant="text"
@click="goToDeleteAssignment(assignment.id, assignment.class.id)"
>
{{ t("delete") }}
</v-btn>
</div>
</v-card>
</v-col>
</v-row>
</v-container>
</div>
</template> </template>
<style scoped> <style scoped>
.assignments-container { .assignments-container {
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
padding: 2% 4%; padding: 2% 4%;
box-sizing: border-box; box-sizing: border-box;
} }
.center-btn { .center-btn {
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.assignment-card { .assignment-card {
padding: 1rem; padding: 1rem;
} }
.top-content { .top-content {
margin-bottom: 1rem; margin-bottom: 1rem;
word-break: break-word; word-break: break-word;
} }
.spacer { .spacer {
flex: 1; flex: 1;
} }
.button-row { .button-row {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 0.5rem; gap: 0.5rem;
flex-wrap: wrap; flex-wrap: wrap;
} }
.assignment-title { .assignment-title {
font-weight: bold; font-weight: bold;
font-size: 1.5rem; font-size: 1.5rem;
margin-bottom: 0.1rem; margin-bottom: 0.1rem;
word-break: break-word; word-break: break-word;
} }
.assignment-class { .assignment-class {
color: #666; color: #666;
font-size: 0.95rem; font-size: 0.95rem;
} }
.class-name { .class-name {
font-weight: 500; font-weight: 500;
color: #333; color: #333;
} }
</style> </style>