feat: bezig met ui/ux assignment, bug in testdata

This commit is contained in:
Adriaan Jacquet 2025-05-02 20:23:14 +02:00
parent 7e4e179121
commit 509db70c73
6 changed files with 227 additions and 79 deletions

View file

@ -13,6 +13,7 @@ import { requireFields } from './error-helper.js';
import { BadRequestException } from '../exceptions/bad-request-exception.js';
import { Assignment } from '../entities/assignments/assignment.entity.js';
import { EntityDTO } from '@mikro-orm/core';
import { FALLBACK_LANG } from '../config.js';
function getAssignmentParams(req: Request): { classid: string; assignmentNumber: number; full: boolean } {
const classid = req.params.classid;
@ -38,14 +39,19 @@ export async function getAllAssignmentsHandler(req: Request, res: Response): Pro
export async function createAssignmentHandler(req: Request, res: Response): Promise<void> {
const classid = req.params.classid;
const description = req.body.description;
const language = req.body.language;
const learningPath = req.body.learningPath;
const description = req.body.description || "";
const language = req.body.language || FALLBACK_LANG;
const learningPath = req.body.learningPath || "";
const title = req.body.title;
requireFields({ description, language, learningPath, title });
requireFields({ title });
const assignmentData = req.body as AssignmentDTO;
const assignmentData = {
description: description,
language: language,
learningPath: learningPath,
title: title,
} as AssignmentDTO;
const assignment = await createAssignment(classid, assignmentData);
res.json({ assignment });

View file

@ -0,0 +1,49 @@
<template>
<v-table class="table">
<thead>
<tr v-for="name in columns" :key="column">
<th class="header">{{ name }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="([item1, item2, item3], index) in listItems"
:key="index"
>
<td></td>
<td>
<v-btn
:to="`/class/${c.id}`"
variant="text"
>
{{ c.displayName }}
<v-icon end> mdi-menu-right </v-icon>
</v-btn>
</td>
<td>
<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>
<td>{{ c.students.length }}</td>
</tr>
</tbody>
</v-table>
</template>
<script>
export default {
name: 'columnList',
props: {
items: {
type: Array,
required: true
}
}
}
</script>

View file

@ -25,7 +25,7 @@ export class AssignmentController extends BaseController {
return this.get<AssignmentResponse>(`/${num}`);
}
async createAssignment(data: AssignmentDTO): Promise<AssignmentResponse> {
async createAssignment(data: Partial<AssignmentDTO>): Promise<AssignmentResponse> {
return this.post<AssignmentResponse>(`/`, data);
}

View file

@ -117,7 +117,7 @@ export function useAssignmentQuery(
export function useCreateAssignmentMutation(): UseMutationReturnType<
AssignmentResponse,
Error,
{ cid: string; data: AssignmentDTO },
{ cid: string; data: Partial<AssignmentDTO> },
unknown
> {
const queryClient = useQueryClient();

View file

@ -6,10 +6,10 @@ import authState from "@/services/auth/auth-service.ts";
import auth from "@/services/auth/auth-service.ts";
import { useTeacherAssignmentsQuery, useTeacherClassesQuery } from "@/queries/teachers.ts";
import { useStudentAssignmentsQuery, useStudentClassesQuery } from "@/queries/students.ts";
import { ClassController } from "@/controllers/classes.ts";
import { ClassController, type ClassesResponse } from "@/controllers/classes.ts";
import type { ClassDTO } from "@dwengo-1/common/interfaces/class";
import { asyncComputed } from "@vueuse/core";
import { useDeleteAssignmentMutation } from "@/queries/assignments.ts";
import { useCreateAssignmentMutation, 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";
@ -38,8 +38,14 @@ onMounted(async () => {
});
const isTeacher = computed(() => role.value === "teacher");
const assignmentsQuery = isTeacher ? useTeacherAssignmentsQuery(username, true) : useStudentAssignmentsQuery(username, true);
const { mutate: assignmentMutation, isSuccess: assignmentIsSuccess } = useCreateAssignmentMutation();
const classesQuery = isTeacher ? useTeacherClassesQuery(username, true) : useStudentClassesQuery(username, true);
const selectedClass = ref<ClassDTO | undefined>(undefined);
const isClassSelected = ref(false);
const assignmentTitle = ref<string>("");
async function goToCreateAssignment(): Promise<void> {
await router.push("/assignment/create");
@ -65,6 +71,27 @@ onMounted(async () => {
const user = await auth.loadUser();
username.value = user?.profile?.preferred_username ?? "";
});
async function createAssignment(): Promise<void> {
const cid = selectedClass.value!.id;
const assignmentData: Partial<AssignmentDTO> = {
within: cid,
title: assignmentTitle.value!,
};
assignmentMutation({ cid: cid, data: assignmentData}, {
onSuccess: async (classResponse) => {
// showSnackbar(t("classCreated"), "success");
// const createdClass: ClassDTO = classResponse.class;
// code.value = createdClass.id;
await assignmentsQuery.refetch();
},
onError: (err) => {
console.log(err);
// showSnackbar(t("creationFailed") + ": " + err.message, "error");
},
});
}
</script>
<template>
@ -80,79 +107,81 @@ onMounted(async () => {
<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-container fluid class="ma-4">
<v-row no-gutters class="custom-breakpoint">
<v-col cols="12" sm="6" md="6" class="responsive-col">
<v-table class="table">
<thead>
<tr>
<th class="header">{{ t("assignment") }}</th>
<th class="header">
{{ t("progress") }}
</th>
<th class="header">{{ t("deadline") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="a in assignmentsResponse.data.assignments as AssignmentDTO[]"
:key="a.id + a.within">
<td>
<v-btn :to="`/assignment/${a.within}/${a.id}`" variant="text">
{{ a.title }}
<v-icon end> mdi-menu-right </v-icon>
</v-btn>
</td>
<td>
<v-progress-linear :model-value="0" color="blue-grey" height="25">
<template v-slot:default="{ value }">
<strong>{{ Math.ceil(value) }}%</strong>
</template>
</v-progress-linear>
</td>
<td>Nov 9, 2025, 06:00 PM EST+1</td>
</tr>
</tbody>
</v-table>
</v-col>
<v-col cols="12" sm="6" md="6" class="responsive-col">
<div>
<h2>{{ t("createAssignment") }}</h2>
<td>{{ a.groups.length }}</td>
</tr>
</tbody>
</v-table>
<v-sheet class="pa-4 sheet" max-width="600px">
<p>{{ t("createClassInstructions") }}</p>
<v-form @submit.prevent>
<v-text-field class="mt-4" :label="`${t('title')}`" v-model="assignmentTitle"
:placeholder="`${t('EnterAssignmentTitle')}`"
variant="outlined"></v-text-field>
<using-query-result :query-result="classesQuery"
v-slot="{ data }: { data: ClassesResponse }">
<v-card-text class="mt-4">
<v-combobox class="mt-4" v-model="selectedClass"
:items="data.classes"
:label="t('choose-class')"
variant="outlined"
clearable
hide-details
density="compact"
append-inner-icon="mdi-magnify"
item-title="displayName"
item-value="id"
required
:disabled="isClassSelected"
:filter="(item: ClassDTO, query: string) => item.displayName.toLowerCase().includes(query.toLowerCase())"
>
</v-combobox>
</v-card-text>
</using-query-result>
<v-btn class="mt-4" color="#f6faf2" type="submit" @click="createAssignment" block>
{{ t("create")}}
</v-btn>
</v-form>
</v-sheet>
</div>
</v-col>
</v-row>
</v-container>
</using-query-result>
</div>
<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>
<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>
</template>
@ -207,4 +236,68 @@ onMounted(async () => {
font-weight: 500;
color: #333;
}
.header {
font-weight: bold !important;
background-color: #0e6942;
color: white;
padding: 10px;
}
h1 {
color: #0e6942;
text-transform: uppercase;
font-weight: bolder;
padding-top: 2%;
font-size: 50px;
}
h2 {
color: #0e6942;
font-size: 30px;
}
.join {
display: flex;
flex-direction: column;
gap: 20px;
margin-top: 50px;
}
.link {
color: #0b75bb;
text-decoration: underline;
}
main {
margin-left: 30px;
}
td,
th {
border-bottom: 1px solid #0e6942;
border-top: 1px solid #0e6942;
}
.table {
width: 90%;
padding-top: 10px;
border-collapse: collapse;
}
table thead th:first-child {
border-top-left-radius: 10px;
}
.table thead th:last-child {
border-top-right-radius: 10px;
}
.table tbody tr:nth-child(odd) {
background-color: white;
}
.table tbody tr:nth-child(even) {
background-color: #f6faf2;
}
</style>