feat: bezig met ui/ux assignment, bug in testdata
This commit is contained in:
		
							parent
							
								
									7e4e179121
								
							
						
					
					
						commit
						509db70c73
					
				
					 6 changed files with 227 additions and 79 deletions
				
			
		|  | @ -13,6 +13,7 @@ import { requireFields } from './error-helper.js'; | ||||||
| import { BadRequestException } from '../exceptions/bad-request-exception.js'; | import { BadRequestException } from '../exceptions/bad-request-exception.js'; | ||||||
| import { Assignment } from '../entities/assignments/assignment.entity.js'; | import { Assignment } from '../entities/assignments/assignment.entity.js'; | ||||||
| import { EntityDTO } from '@mikro-orm/core'; | import { EntityDTO } from '@mikro-orm/core'; | ||||||
|  | import { FALLBACK_LANG } from '../config.js'; | ||||||
| 
 | 
 | ||||||
| function getAssignmentParams(req: Request): { classid: string; assignmentNumber: number; full: boolean } { | function getAssignmentParams(req: Request): { classid: string; assignmentNumber: number; full: boolean } { | ||||||
|     const classid = req.params.classid; |     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> { | export async function createAssignmentHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const classid = req.params.classid; |     const classid = req.params.classid; | ||||||
|     const description = req.body.description; |     const description = req.body.description || ""; | ||||||
|     const language = req.body.language; |     const language = req.body.language || FALLBACK_LANG; | ||||||
|     const learningPath = req.body.learningPath; |     const learningPath = req.body.learningPath || ""; | ||||||
|     const title = req.body.title; |     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); |     const assignment = await createAssignment(classid, assignmentData); | ||||||
| 
 | 
 | ||||||
|     res.json({ assignment }); |     res.json({ assignment }); | ||||||
|  |  | ||||||
							
								
								
									
										49
									
								
								frontend/src/components/DwengoTable.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								frontend/src/components/DwengoTable.vue
									
										
									
									
									
										Normal 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> | ||||||
							
								
								
									
										0
									
								
								frontend/src/components/assignments/AssignmentCard.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								frontend/src/components/assignments/AssignmentCard.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -25,7 +25,7 @@ export class AssignmentController extends BaseController { | ||||||
|         return this.get<AssignmentResponse>(`/${num}`); |         return this.get<AssignmentResponse>(`/${num}`); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async createAssignment(data: AssignmentDTO): Promise<AssignmentResponse> { |     async createAssignment(data: Partial<AssignmentDTO>): Promise<AssignmentResponse> { | ||||||
|         return this.post<AssignmentResponse>(`/`, data); |         return this.post<AssignmentResponse>(`/`, data); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -117,7 +117,7 @@ export function useAssignmentQuery( | ||||||
| export function useCreateAssignmentMutation(): UseMutationReturnType< | export function useCreateAssignmentMutation(): UseMutationReturnType< | ||||||
|     AssignmentResponse, |     AssignmentResponse, | ||||||
|     Error, |     Error, | ||||||
|     { cid: string; data: AssignmentDTO }, |     { cid: string; data: Partial<AssignmentDTO> }, | ||||||
|     unknown |     unknown | ||||||
| > { | > { | ||||||
|     const queryClient = useQueryClient(); |     const queryClient = useQueryClient(); | ||||||
|  |  | ||||||
|  | @ -6,10 +6,10 @@ import authState from "@/services/auth/auth-service.ts"; | ||||||
| import auth from "@/services/auth/auth-service.ts"; | import auth from "@/services/auth/auth-service.ts"; | ||||||
| import { useTeacherAssignmentsQuery, useTeacherClassesQuery } from "@/queries/teachers.ts"; | import { useTeacherAssignmentsQuery, useTeacherClassesQuery } from "@/queries/teachers.ts"; | ||||||
| import { useStudentAssignmentsQuery, useStudentClassesQuery } from "@/queries/students.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 type { ClassDTO } from "@dwengo-1/common/interfaces/class"; | ||||||
| import { asyncComputed } from "@vueuse/core"; | 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 { AssignmentsResponse } from "@/controllers/assignments"; | ||||||
| import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; | import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; | ||||||
| import UsingQueryResult from "@/components/UsingQueryResult.vue"; | import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|  | @ -38,8 +38,14 @@ onMounted(async () => { | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const isTeacher = computed(() => role.value === "teacher"); | const isTeacher = computed(() => role.value === "teacher"); | ||||||
| 
 |  | ||||||
| const assignmentsQuery = isTeacher ? useTeacherAssignmentsQuery(username, true) : useStudentAssignmentsQuery(username, true); | 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> { | async function goToCreateAssignment(): Promise<void> { | ||||||
|     await router.push("/assignment/create"); |     await router.push("/assignment/create"); | ||||||
|  | @ -65,6 +71,27 @@ onMounted(async () => { | ||||||
|     const user = await auth.loadUser(); |     const user = await auth.loadUser(); | ||||||
|     username.value = user?.profile?.preferred_username ?? ""; |     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> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -80,79 +107,81 @@ onMounted(async () => { | ||||||
|         <div v-else> |         <div v-else> | ||||||
|             <using-query-result :query-result="assignmentsQuery" |             <using-query-result :query-result="assignmentsQuery" | ||||||
|                 v-slot="assignmentsResponse: { data: AssignmentsResponse }"> |                 v-slot="assignmentsResponse: { data: AssignmentsResponse }"> | ||||||
|                 <v-btn v-if="isTeacher" color="primary" class="mb-4 center-btn" @click="goToCreateAssignment"> |                 <v-container fluid class="ma-4"> | ||||||
|                     {{ t("new-assignment") }} |                     <v-row no-gutters class="custom-breakpoint"> | ||||||
|                 </v-btn> |                         <v-col cols="12" sm="6" md="6" class="responsive-col"> | ||||||
|                 <v-container> |                             <v-table class="table"> | ||||||
|                     <v-table class="table"> |                                 <thead> | ||||||
|                         <thead> |                                     <tr> | ||||||
|                             <tr> |                                         <th class="header">{{ t("assignment") }}</th> | ||||||
|                                 <th class="header">{{ t("assignments") }}</th> |                                         <th class="header"> | ||||||
|                                 <th class="header"> |                                             {{ t("progress") }} | ||||||
|                                     {{ t("class") }} |                                         </th> | ||||||
|                                 </th> |                                         <th class="header">{{ t("deadline") }}</th> | ||||||
|                                 <th class="header">{{ t("groups") }}</th> |                                     </tr> | ||||||
|                             </tr> |                                 </thead> | ||||||
|                         </thead> |                                 <tbody> | ||||||
|                         <tbody> |                                     <tr v-for="a in assignmentsResponse.data.assignments as AssignmentDTO[]" | ||||||
|                             <tr v-for="a in assignmentsResponse.data.assignments as AssignmentDTO[]" |                                         :key="a.id + a.within"> | ||||||
|                                 :key="a.id + a.within"> |                                         <td> | ||||||
|                                 <td> |                                             <v-btn :to="`/assignment/${a.within}/${a.id}`" variant="text"> | ||||||
|                                     <v-btn :to="`/class/${a.within}`" variant="text"> |                                                 {{ a.title }} | ||||||
|                                         {{ a.title }} |                                                 <v-icon end> mdi-menu-right </v-icon> | ||||||
|                                         <v-icon end> mdi-menu-right </v-icon> |                                             </v-btn> | ||||||
|                                     </v-btn> |                                         </td> | ||||||
|                                 </td> |                                         <td> | ||||||
|                                 <td> |                                             <v-progress-linear :model-value="0" color="blue-grey" height="25"> | ||||||
|                                     <span>{{ a.within }}</span> |                                                 <template v-slot:default="{ value }"> | ||||||
|                                     <!-- <span v-if="!isMdAndDown">{{ c.id }}</span> |                                                     <strong>{{ Math.ceil(value) }}%</strong> | ||||||
|                                     <span v-else style="cursor: pointer" @click="openCodeDialog(c.id)"><v-icon |                                                 </template> | ||||||
|                                             icon="mdi-eye"></v-icon></span> --> |                                             </v-progress-linear> | ||||||
|                                 </td> |                                         </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> |                                 <v-sheet class="pa-4 sheet" max-width="600px"> | ||||||
|                             </tr> |                                     <p>{{ t("createClassInstructions") }}</p> | ||||||
|                         </tbody> |                                     <v-form @submit.prevent> | ||||||
|                     </v-table> |                                         <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> |                 </v-container> | ||||||
|             </using-query-result> |             </using-query-result> | ||||||
|         </div> |         </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> |     </main> | ||||||
| 
 | 
 | ||||||
| </template> | </template> | ||||||
|  | @ -207,4 +236,68 @@ onMounted(async () => { | ||||||
|     font-weight: 500; |     font-weight: 500; | ||||||
|     color: #333; |     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> | </style> | ||||||
|  |  | ||||||
		Reference in a new issue