feat(frontend): eerste queries voor assignments
This commit is contained in:
		
							parent
							
								
									ce5e9cd342
								
							
						
					
					
						commit
						45cb020861
					
				
					 10 changed files with 560 additions and 218 deletions
				
			
		
							
								
								
									
										44
									
								
								frontend/src/assets/assignment.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								frontend/src/assets/assignment.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | ||||||
|  | .container { | ||||||
|  |     display: flex; | ||||||
|  |     justify-content: center; | ||||||
|  |     align-items: center; | ||||||
|  |     padding: 2%; | ||||||
|  |     min-height: 100vh; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .assignment-card { | ||||||
|  |     width: 85%; | ||||||
|  |     padding: 2%; | ||||||
|  |     border-radius: 12px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .description { | ||||||
|  |     margin-top: 2%; | ||||||
|  |     line-height: 1.6; | ||||||
|  |     font-size: 1.1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .top-right-btn { | ||||||
|  |     position: absolute; | ||||||
|  |     right: 2%; | ||||||
|  |     color: red; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .group-section { | ||||||
|  |     margin-top: 2rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .group-section h3 { | ||||||
|  |     margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .group-section ul { | ||||||
|  |     padding-left: 1.2rem; | ||||||
|  |     list-style-type: disc; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .subtitle-section { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     justify-content: space-between; | ||||||
|  | } | ||||||
|  | @ -13,15 +13,10 @@ import type {LearningPath} from "@/data-objects/learning-paths/learning-path.ts" | ||||||
| import type {ClassesResponse} from "@/controllers/classes.ts"; | import type {ClassesResponse} from "@/controllers/classes.ts"; | ||||||
| import type {AssignmentDTO} from "@dwengo-1/common/interfaces/assignment"; | import type {AssignmentDTO} from "@dwengo-1/common/interfaces/assignment"; | ||||||
| import {AssignmentController} from "@/controllers/assignments.ts"; | import {AssignmentController} from "@/controllers/assignments.ts"; | ||||||
| import type {GroupDTO} from "@dwengo-1/common/interfaces/group"; |  | ||||||
| import {GroupController} from "@/controllers/groups.ts"; |  | ||||||
| 
 | 
 | ||||||
| /*** | /*** | ||||||
|  TODO: when clicking the assign button from lp page pass the lp-object in a state: |  TODO: when clicking the assign button from lp page pass the lp-object in a state: | ||||||
|  */ |  */ | ||||||
| const props = defineProps<{ |  | ||||||
|     learningPath?: LearningPath | null;  // Optional learningPath prop |  | ||||||
| }>(); |  | ||||||
| 
 | 
 | ||||||
| const router = useRouter(); | const router = useRouter(); | ||||||
| const {t, locale} = useI18n(); | const {t, locale} = useI18n(); | ||||||
|  | @ -48,26 +43,7 @@ async function submitForm(assignmentTitle: string, | ||||||
| 
 | 
 | ||||||
|     //TODO: replace with query function |     //TODO: replace with query function | ||||||
|     const controller: AssignmentController = new AssignmentController(selectedClass); |     const controller: AssignmentController = new AssignmentController(selectedClass); | ||||||
|     const response = await controller.createAssignment(assignmentDTO); |     await controller.createAssignment(assignmentDTO); | ||||||
|     // Create groups |  | ||||||
|     for (let i = 0; i < groups.length; i++) { |  | ||||||
|         const group: GroupDTO = { |  | ||||||
|             assignment: response.id, |  | ||||||
|             groupNumber: i, |  | ||||||
|             members: groups[i] |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         console.log("Posting group:", group); |  | ||||||
| 
 |  | ||||||
|         const groupController: GroupController = new GroupController(selectedClass, response.id); |  | ||||||
| 
 |  | ||||||
|         try { |  | ||||||
|             await groupController.createGroup(group); |  | ||||||
|             console.log("Group successfully posted:", group); |  | ||||||
|         } catch (err) { |  | ||||||
|             console.error("Group POST failed:", err); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     await router.push('/user/assignment'); |     await router.push('/user/assignment'); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import {ref, defineProps, defineEmits} from 'vue'; | import {ref, defineProps, defineEmits} from 'vue'; | ||||||
| import {useI18n} from 'vue-i18n'; | import {useI18n} from 'vue-i18n'; | ||||||
| import {useClassStudentsQuery} from "@/queries/classes.ts"; | import {useClassStudentsQuery} from "@/queries/classes-temp.ts"; | ||||||
| import UsingQueryResult from "@/components/UsingQueryResult.vue"; | import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
| import type {StudentsResponse} from "@/controllers/students.ts"; | import type {StudentsResponse} from "@/controllers/students.ts"; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,17 +1,55 @@ | ||||||
| import {useMutation, useQueryClient, type UseMutationReturnType} from "@tanstack/vue-query"; | import {computed, type MaybeRefOrGetter, toValue} from "vue"; | ||||||
| import {AssignmentController, type AssignmentResponse} from "@/controllers/assignments.ts"; | import {useQuery, type UseQueryReturnType} from "@tanstack/vue-query"; | ||||||
| import type {AssignmentDTO} from "@dwengo-1/common/interfaces/assignment"; | import {AssignmentController, type AssignmentResponse, type AssignmentsResponse} from "@/controllers/assignments.ts"; | ||||||
| import {toValue} from "vue"; | import type {SubmissionsResponse} from "@/controllers/submissions.ts"; | ||||||
| 
 | 
 | ||||||
| export function useCreateAssignmentMutation(classId: string): UseMutationReturnType<AssignmentResponse, Error, AssignmentDTO, unknown> { | export function useAssignmentsQuery(classId: MaybeRefOrGetter<string | undefined>, full: MaybeRefOrGetter<boolean> = true): UseQueryReturnType<AssignmentsResponse, Error> { | ||||||
|     const queryClient = useQueryClient(); |     const resolvedClassId = toValue(classId) as string; | ||||||
|  |     const resolvedFull = toValue(full); | ||||||
| 
 | 
 | ||||||
|     const assignmentController = new AssignmentController(toValue(classId)); |     const assignmentController = new AssignmentController(resolvedClassId); | ||||||
| 
 |     return useQuery({ | ||||||
|     return useMutation({ |         queryKey: computed(() => [ | ||||||
|         mutationFn: async (data) => assignmentController.createAssignment(data), |             'assignments', | ||||||
|         onSuccess: async () => { |             resolvedClassId, | ||||||
|             await queryClient.invalidateQueries({queryKey: ["assignments"]}); |             resolvedFull, | ||||||
|         }, |         ]), | ||||||
|     }); |         queryFn: async () => assignmentController.getAll(resolvedFull), | ||||||
|  |         enabled: () => Boolean(resolvedClassId) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export function useAssignmentQuery(classId: MaybeRefOrGetter<string | undefined>, num: MaybeRefOrGetter<number>): UseQueryReturnType<AssignmentResponse, Error> { | ||||||
|  |     const resolvedClassId = toValue(classId) as string; | ||||||
|  |     const resolvedNum = toValue(num); | ||||||
|  | 
 | ||||||
|  |     const assignmentController = new AssignmentController(resolvedClassId); | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => [ | ||||||
|  |             'assignment', | ||||||
|  |             resolvedClassId, | ||||||
|  |             resolvedNum, | ||||||
|  |         ]), | ||||||
|  |         queryFn: async () => assignmentController.getByNumber(resolvedNum), | ||||||
|  |         enabled: () => Boolean(resolvedClassId) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useSubmissionsQuery(classId: MaybeRefOrGetter<string | undefined>, assignmentNum: MaybeRefOrGetter<number>, full: MaybeRefOrGetter<boolean>): UseQueryReturnType<SubmissionsResponse, Error> { | ||||||
|  |     const resolvedClassId = toValue(classId) as string; | ||||||
|  |     const resolvedNum = toValue(assignmentNum); | ||||||
|  |     const resolvedFull = toValue(full); | ||||||
|  | 
 | ||||||
|  |     const assignmentController = new AssignmentController(resolvedClassId); | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: computed(() => [ | ||||||
|  |             'submissions', | ||||||
|  |             resolvedClassId, | ||||||
|  |             resolvedNum, | ||||||
|  |             resolvedFull | ||||||
|  |         ]), | ||||||
|  |         queryFn: async () => assignmentController.getSubmissions(resolvedNum, resolvedFull), | ||||||
|  |         enabled: () => Boolean(resolvedClassId) | ||||||
|  |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import {useQuery, type UseQueryReturnType} from "@tanstack/vue-query"; |  | ||||||
| import {computed, type MaybeRefOrGetter, toValue} from "vue"; |  | ||||||
| import type {StudentsResponse} from "@/controllers/students.ts"; |  | ||||||
| import {getClassController} from "@/controllers/controllers.ts"; | import {getClassController} from "@/controllers/controllers.ts"; | ||||||
|  | import {computed, type MaybeRefOrGetter, toValue} from "vue"; | ||||||
|  | import {useQuery, type UseQueryReturnType} from "@tanstack/vue-query"; | ||||||
|  | import type {StudentsResponse} from "@/controllers/students.ts"; | ||||||
| 
 | 
 | ||||||
| const classController = getClassController(); | const classController = getClassController(); | ||||||
| 
 | 
 | ||||||
|  | @ -9,7 +9,7 @@ function classStudentsQueryKey(classId: string, full: boolean): [string, string, | ||||||
|     return ["class-students", classId, full]; |     return ["class-students", classId, full]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | //TODO: delete and use the one in classes.ts
 | ||||||
| export function useClassStudentsQuery( | export function useClassStudentsQuery( | ||||||
|     classId: MaybeRefOrGetter<string | undefined>, |     classId: MaybeRefOrGetter<string | undefined>, | ||||||
|     full: MaybeRefOrGetter<boolean> = true, |     full: MaybeRefOrGetter<boolean> = true, | ||||||
							
								
								
									
										200
									
								
								frontend/src/views/assignments/Assignment.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								frontend/src/views/assignments/Assignment.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,200 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { useRoute } from "vue-router"; | ||||||
|  | import {ref, computed} from "vue"; | ||||||
|  | import {useI18n} from "vue-i18n"; | ||||||
|  | import auth from "@/services/auth/auth-service.ts"; | ||||||
|  | import {AssignmentController} from "@/controllers/assignments.ts"; | ||||||
|  | import {asyncComputed} from "@vueuse/core"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const {t, locale} = useI18n(); | ||||||
|  | const language = computed(() => locale.value); | ||||||
|  | const route = useRoute(); | ||||||
|  | const assignmentId = ref(Number(route.params.id)); | ||||||
|  | const classId = window.history.state?.class_id; | ||||||
|  | const controller = new AssignmentController(classId); | ||||||
|  | 
 | ||||||
|  | const role = auth.authState.activeRole; | ||||||
|  | const isTeacher = computed(() => role === 'teacher'); | ||||||
|  | 
 | ||||||
|  | const assignment = asyncComputed(async () => { | ||||||
|  |     return await controller.getByNumber(assignmentId.value) | ||||||
|  | }, null); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const submitted = ref(true);//TODO: update by fetching submissions and check group | ||||||
|  | 
 | ||||||
|  | const submitAssignment = async () => { | ||||||
|  |     //TODO | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /*** | ||||||
|  |  // Display group members | ||||||
|  |  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) | ||||||
|  |  ); | ||||||
|  |  }); | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | const deleteAssignment = async () => { | ||||||
|  |     await controller.deleteAssignment(assignmentId.value); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <div class="container"> | ||||||
|  |         <v-card v-if="assignment" class="assignment-card"> | ||||||
|  |             <div class="top-buttons"> | ||||||
|  |                 <v-btn | ||||||
|  |                     icon | ||||||
|  |                     variant="text" | ||||||
|  |                     class="back-btn" | ||||||
|  |                     to="/user/assignment" | ||||||
|  |                 > | ||||||
|  |                     <v-icon>mdi-arrow-left</v-icon> | ||||||
|  |                 </v-btn> | ||||||
|  | 
 | ||||||
|  |                 <v-btn | ||||||
|  |                     v-if="isTeacher" | ||||||
|  |                     icon | ||||||
|  |                     variant="text" | ||||||
|  |                     class="top-right-btn" | ||||||
|  |                     @click="deleteAssignment" | ||||||
|  |                 > | ||||||
|  |                     <v-icon>mdi-delete</v-icon> | ||||||
|  |                 </v-btn> | ||||||
|  |                 <v-chip | ||||||
|  |                     v-if="!isTeacher" | ||||||
|  |                     class="ma-2 top-right-btn" | ||||||
|  |                     label | ||||||
|  |                     color="success" | ||||||
|  |                 > | ||||||
|  |                     {{ t("submitted") }} | ||||||
|  |                 </v-chip> | ||||||
|  |             </div> | ||||||
|  |             <v-card-title class="text-h4">{{ assignment.title }}</v-card-title> | ||||||
|  |             <v-card-subtitle class="subtitle-section"> | ||||||
|  |                 <v-btn | ||||||
|  |                     :to="`/learningPath/${language}/${assignment.learningPath}`" | ||||||
|  |                     variant="tonal" | ||||||
|  |                     color="primary" | ||||||
|  |                 > | ||||||
|  |                     {{ t("learning-path") }} | ||||||
|  |                 </v-btn> | ||||||
|  |             </v-card-subtitle> | ||||||
|  | 
 | ||||||
|  |             <v-card-text class="description"> | ||||||
|  |                 {{ assignment.description }} | ||||||
|  |             </v-card-text> | ||||||
|  | 
 | ||||||
|  |             <v-card-text class="group-section"> | ||||||
|  |                 <h3>{{ t("group") }}</h3> | ||||||
|  | 
 | ||||||
|  |                 <!-- Student view | ||||||
|  |                 <div v-if="!isTeacher"> | ||||||
|  |                     <div v-if="myGroup"> | ||||||
|  |                         <ul> | ||||||
|  |                             <li v-for="student in myGroup.members" :key="student.username"> | ||||||
|  |                                 {{ student.firstName + ' ' + student.lastName}} | ||||||
|  |                             </li> | ||||||
|  |                         </ul> | ||||||
|  |                     </div> | ||||||
|  |                 </div>--> | ||||||
|  | 
 | ||||||
|  |                 <!-- Teacher view | ||||||
|  |                 <div v-if="isTeacher"> | ||||||
|  |                     <v-expansion-panels> | ||||||
|  |                         <v-expansion-panel | ||||||
|  |                             v-for="(group, index) in assignment.groups" | ||||||
|  |                             :key="group.id" | ||||||
|  |                         > | ||||||
|  |                             <v-expansion-panel-title> | ||||||
|  |                                 {{ t("group") }} {{ index + 1 }} | ||||||
|  |                             </v-expansion-panel-title> | ||||||
|  |                             <v-expansion-panel-text> | ||||||
|  |                                 <ul> | ||||||
|  |                                     <li v-for="student in group.members" :key="student.username"> | ||||||
|  |                                         {{ student.firstName + ' ' + student.lastName }} | ||||||
|  |                                     </li> | ||||||
|  |                                 </ul> | ||||||
|  |                             </v-expansion-panel-text> | ||||||
|  |                         </v-expansion-panel> | ||||||
|  |                     </v-expansion-panels> | ||||||
|  |                 </div>--> | ||||||
|  |             </v-card-text > | ||||||
|  |             <v-card-actions class="justify-end"> | ||||||
|  |                 <v-btn | ||||||
|  |                     v-if="!isTeacher" | ||||||
|  |                     size="large" | ||||||
|  |                     color="success" | ||||||
|  |                     variant="flat" | ||||||
|  |                     @click="submitAssignment" | ||||||
|  |                 > | ||||||
|  |                     {{ t("submit") }} | ||||||
|  |                 </v-btn> | ||||||
|  |                 <v-btn | ||||||
|  |                     v-if="isTeacher" | ||||||
|  |                     size="large" | ||||||
|  |                     color="success" | ||||||
|  |                     variant="text" | ||||||
|  |                 > | ||||||
|  |                     {{ t("view-submissions") }} | ||||||
|  |                 </v-btn> | ||||||
|  |             </v-card-actions> | ||||||
|  | 
 | ||||||
|  |         </v-card> | ||||||
|  |     </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | .container { | ||||||
|  |     display: flex; | ||||||
|  |     justify-content: center; | ||||||
|  |     align-items: center; | ||||||
|  |     padding: 2%; | ||||||
|  |     min-height: 100vh; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .assignment-card { | ||||||
|  |     width: 85%; | ||||||
|  |     padding: 2%; | ||||||
|  |     border-radius: 12px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .description { | ||||||
|  |     margin-top: 2%; | ||||||
|  |     line-height: 1.6; | ||||||
|  |     font-size: 1.1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .top-right-btn { | ||||||
|  |     position: absolute; | ||||||
|  |     right: 2%; | ||||||
|  |     color: red; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .group-section { | ||||||
|  |     margin-top: 2rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .group-section h3 { | ||||||
|  |     margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .group-section ul { | ||||||
|  |     padding-left: 1.2rem; | ||||||
|  |     list-style-type: disc; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .subtitle-section { | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     justify-content: space-between; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | 
 | ||||||
|  | @ -1,162 +1,20 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import { useRoute } from "vue-router"; |  | ||||||
|     import {ref, computed} from "vue"; |  | ||||||
|     import {useI18n} from "vue-i18n"; |  | ||||||
|     import auth from "@/services/auth/auth-service.ts"; |  | ||||||
|     import {AssignmentController} from "@/controllers/assignments.ts"; |  | ||||||
|     import {asyncComputed} from "@vueuse/core"; |  | ||||||
| 
 | 
 | ||||||
|  | import auth from "@/services/auth/auth-service.ts"; | ||||||
|  | import {computed} from "vue"; | ||||||
|  | import StudentAssignment from "@/views/assignments/StudentAssignment.vue"; | ||||||
|  | import TeacherAssignment from "@/views/assignments/TeacherAssignment.vue"; | ||||||
| 
 | 
 | ||||||
|     const {t, locale} = useI18n(); | const role = auth.authState.activeRole; | ||||||
|     const language = computed(() => locale.value); | const isTeacher = computed(() => role === 'teacher'); | ||||||
|     const route = useRoute(); |  | ||||||
|     const assignmentId = ref(Number(route.params.id)); |  | ||||||
|     const classId = window.history.state?.class_id; |  | ||||||
|     const controller = new AssignmentController(classId); |  | ||||||
| 
 |  | ||||||
|     const role = auth.authState.activeRole; |  | ||||||
|     const isTeacher = computed(() => role === 'teacher'); |  | ||||||
| 
 |  | ||||||
|     const assignment = asyncComputed(async () => { |  | ||||||
|         return await controller.getByNumber(assignmentId.value) |  | ||||||
|     }, null); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     /*** |  | ||||||
|     // Display group members |  | ||||||
|     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) |  | ||||||
|         ); |  | ||||||
|     }); |  | ||||||
|         */ |  | ||||||
| 
 |  | ||||||
|     const deleteAssignment = async () => { |  | ||||||
|         await controller.deleteAssignment(assignmentId.value); |  | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <div class="container"> |     <StudentAssignment v-if="!isTeacher"></StudentAssignment> | ||||||
|         <v-card v-if="assignment" class="assignment-card"> |     <TeacherAssignment v-else></TeacherAssignment> | ||||||
|             <div class="top-buttons"> |  | ||||||
|                 <v-btn |  | ||||||
|                     icon |  | ||||||
|                     variant="text" |  | ||||||
|                     class="back-btn" |  | ||||||
|                     to="/user/assignment" |  | ||||||
|                 > |  | ||||||
|                     <v-icon>mdi-arrow-left</v-icon> |  | ||||||
|                 </v-btn> |  | ||||||
| 
 |  | ||||||
|                 <v-btn |  | ||||||
|                     v-if="isTeacher" |  | ||||||
|                     icon |  | ||||||
|                     variant="text" |  | ||||||
|                     class="delete-btn" |  | ||||||
|                     @click="deleteAssignment" |  | ||||||
|                 > |  | ||||||
|                     <v-icon>mdi-delete</v-icon> |  | ||||||
|                 </v-btn> |  | ||||||
|             </div> |  | ||||||
|             <v-card-title class="text-h4">{{ assignment.title }}</v-card-title> |  | ||||||
|             <v-card-subtitle> |  | ||||||
|                 <v-btn |  | ||||||
|                     :to="`/learningPath/${language}/${assignment.learningPath}`" |  | ||||||
|                     variant="tonal" |  | ||||||
|                     color="primary" |  | ||||||
|                 > |  | ||||||
|                     {{ t("learning-path") }} |  | ||||||
|                 </v-btn> |  | ||||||
|             </v-card-subtitle> |  | ||||||
| 
 |  | ||||||
|             <v-card-text class="description"> |  | ||||||
|                 {{ assignment.description }} |  | ||||||
|             </v-card-text> |  | ||||||
| 
 |  | ||||||
|             <v-card-text class="group-section"> |  | ||||||
|                 <h3>{{ t("group") }}</h3> |  | ||||||
| 
 |  | ||||||
|                 <!-- Student view |  | ||||||
|                 <div v-if="!isTeacher"> |  | ||||||
|                     <div v-if="myGroup"> |  | ||||||
|                         <ul> |  | ||||||
|                             <li v-for="student in myGroup.members" :key="student.username"> |  | ||||||
|                                 {{ student.firstName + ' ' + student.lastName}} |  | ||||||
|                             </li> |  | ||||||
|                         </ul> |  | ||||||
|                     </div> |  | ||||||
|                 </div>--> |  | ||||||
| 
 |  | ||||||
|                 <!-- Teacher view |  | ||||||
|                 <div v-if="isTeacher"> |  | ||||||
|                     <v-expansion-panels> |  | ||||||
|                         <v-expansion-panel |  | ||||||
|                             v-for="(group, index) in assignment.groups" |  | ||||||
|                             :key="group.id" |  | ||||||
|                         > |  | ||||||
|                             <v-expansion-panel-title> |  | ||||||
|                                 {{ t("group") }} {{ index + 1 }} |  | ||||||
|                             </v-expansion-panel-title> |  | ||||||
|                             <v-expansion-panel-text> |  | ||||||
|                                 <ul> |  | ||||||
|                                     <li v-for="student in group.members" :key="student.username"> |  | ||||||
|                                         {{ student.firstName + ' ' + student.lastName }} |  | ||||||
|                                     </li> |  | ||||||
|                                 </ul> |  | ||||||
|                             </v-expansion-panel-text> |  | ||||||
|                         </v-expansion-panel> |  | ||||||
|                     </v-expansion-panels> |  | ||||||
|                 </div>--> |  | ||||||
|             </v-card-text> |  | ||||||
| 
 |  | ||||||
|         </v-card> |  | ||||||
|     </div> |  | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| .container { |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     align-items: center; |  | ||||||
|     padding: 2%; |  | ||||||
|     min-height: 100vh; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .assignment-card { |  | ||||||
|     width: 85%; |  | ||||||
|     padding: 2%; |  | ||||||
|     border-radius: 12px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .description { |  | ||||||
|     margin-top: 2%; |  | ||||||
|     line-height: 1.6; |  | ||||||
|     font-size: 1.1rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .delete-btn { |  | ||||||
|     position: absolute; |  | ||||||
|     right: 1%; |  | ||||||
|     color: red; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .group-section { |  | ||||||
|     margin-top: 2rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .group-section h3 { |  | ||||||
|     margin-bottom: 0.5rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .group-section ul { |  | ||||||
|     padding-left: 1.2rem; |  | ||||||
|     list-style-type: disc; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </style> | </style> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										110
									
								
								frontend/src/views/assignments/StudentAssignment.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								frontend/src/views/assignments/StudentAssignment.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,110 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { useRoute } from "vue-router"; | ||||||
|  | import {ref, computed} from "vue"; | ||||||
|  | import {useI18n} from "vue-i18n"; | ||||||
|  | import {AssignmentController} from "@/controllers/assignments.ts"; | ||||||
|  | import {asyncComputed} from "@vueuse/core"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const {t, locale} = useI18n(); | ||||||
|  | const language = computed(() => locale.value); | ||||||
|  | const route = useRoute(); | ||||||
|  | const assignmentId = ref(Number(route.params.id)); | ||||||
|  | const classId = window.history.state?.class_id; | ||||||
|  | const controller = new AssignmentController(classId); | ||||||
|  | 
 | ||||||
|  | const assignment = asyncComputed(async () => { | ||||||
|  |     return await controller.getByNumber(assignmentId.value) | ||||||
|  | }, null); | ||||||
|  | 
 | ||||||
|  | const submitted = ref(true);//TODO: update by fetching submissions and check group | ||||||
|  | 
 | ||||||
|  | const submitAssignment = async () => { | ||||||
|  |     //TODO | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /*** | ||||||
|  |  // Display group members | ||||||
|  |  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) | ||||||
|  |  ); | ||||||
|  |  }); | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <div class="container"> | ||||||
|  |         <v-card v-if="assignment" class="assignment-card"> | ||||||
|  |             <div class="top-buttons"> | ||||||
|  |                 <v-btn | ||||||
|  |                     icon | ||||||
|  |                     variant="text" | ||||||
|  |                     class="back-btn" | ||||||
|  |                     to="/user/assignment" | ||||||
|  |                 > | ||||||
|  |                     <v-icon>mdi-arrow-left</v-icon> | ||||||
|  |                 </v-btn> | ||||||
|  | 
 | ||||||
|  |                 <v-chip | ||||||
|  |                     class="ma-2 top-right-btn" | ||||||
|  |                     label | ||||||
|  |                     color="success" | ||||||
|  |                 > | ||||||
|  |                     {{ t("submitted") }} | ||||||
|  |                 </v-chip> | ||||||
|  |             </div> | ||||||
|  |             <v-card-title class="text-h4">{{ assignment.title }}</v-card-title> | ||||||
|  |             <v-card-subtitle class="subtitle-section"> | ||||||
|  |                 <v-btn | ||||||
|  |                     :to="`/learningPath/${language}/${assignment.learningPath}`" | ||||||
|  |                     variant="tonal" | ||||||
|  |                     color="primary" | ||||||
|  |                 > | ||||||
|  |                     {{ t("learning-path") }} | ||||||
|  |                 </v-btn> | ||||||
|  |             </v-card-subtitle> | ||||||
|  | 
 | ||||||
|  |             <v-card-text class="description"> | ||||||
|  |                 {{ assignment.description }} | ||||||
|  |             </v-card-text> | ||||||
|  | 
 | ||||||
|  |             <v-card-text class="group-section"> | ||||||
|  |                 <h3>{{ t("group") }}</h3> | ||||||
|  | 
 | ||||||
|  |                 <!-- Student view | ||||||
|  |                 <div v-if="!isTeacher"> | ||||||
|  |                     <div v-if="myGroup"> | ||||||
|  |                         <ul> | ||||||
|  |                             <li v-for="student in myGroup.members" :key="student.username"> | ||||||
|  |                                 {{ student.firstName + ' ' + student.lastName}} | ||||||
|  |                             </li> | ||||||
|  |                         </ul> | ||||||
|  |                     </div> | ||||||
|  |                 </div>--> | ||||||
|  | 
 | ||||||
|  |             </v-card-text > | ||||||
|  |             <v-card-actions class="justify-end"> | ||||||
|  |                 <v-btn | ||||||
|  |                     size="large" | ||||||
|  |                     color="success" | ||||||
|  |                     variant="flat" | ||||||
|  |                     @click="submitAssignment" | ||||||
|  |                 > | ||||||
|  |                     {{ t("submit") }} | ||||||
|  |                 </v-btn> | ||||||
|  |             </v-card-actions> | ||||||
|  | 
 | ||||||
|  |         </v-card> | ||||||
|  |     </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | @import "@/assets/assignment.css"; | ||||||
|  | 
 | ||||||
|  | </style> | ||||||
|  | 
 | ||||||
							
								
								
									
										118
									
								
								frontend/src/views/assignments/TeacherAssignment.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								frontend/src/views/assignments/TeacherAssignment.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,118 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import {useRoute} from "vue-router"; | ||||||
|  | import {ref, computed} from "vue"; | ||||||
|  | import {useI18n} from "vue-i18n"; | ||||||
|  | import {AssignmentController, type AssignmentResponse} from "@/controllers/assignments.ts"; | ||||||
|  | import {useAssignmentQuery} from "@/queries/assignments.ts"; | ||||||
|  | import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const {t, locale} = useI18n(); | ||||||
|  | const language = computed(() => locale.value); | ||||||
|  | const route = useRoute(); | ||||||
|  | const assignmentId = ref(Number(route.params.id)); | ||||||
|  | const classId = window.history.state?.class_id; | ||||||
|  | const controller = new AssignmentController(classId); | ||||||
|  | 
 | ||||||
|  | const assignmentQueryResult = useAssignmentQuery(() => classId, assignmentId); | ||||||
|  | 
 | ||||||
|  | /*** | ||||||
|  |  // Display group members | ||||||
|  |  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) | ||||||
|  |  ); | ||||||
|  |  }); | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | const deleteAssignment = async () => { | ||||||
|  |     await controller.deleteAssignment(assignmentId.value); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <div class="container"> | ||||||
|  |         <using-query-result | ||||||
|  |             :query-result="assignmentQueryResult" | ||||||
|  |             v-slot="{ data }: {data: AssignmentResponse}" | ||||||
|  |         > | ||||||
|  |             <v-card v-if="data" class="assignment-card"> | ||||||
|  |                 <div class="top-buttons"> | ||||||
|  |                     <v-btn | ||||||
|  |                         icon | ||||||
|  |                         variant="text" | ||||||
|  |                         class="back-btn" | ||||||
|  |                         to="/user/assignment" | ||||||
|  |                     > | ||||||
|  |                         <v-icon>mdi-arrow-left</v-icon> | ||||||
|  |                     </v-btn> | ||||||
|  | 
 | ||||||
|  |                     <v-btn | ||||||
|  |                         icon | ||||||
|  |                         variant="text" | ||||||
|  |                         class="top-right-btn" | ||||||
|  |                         @click="deleteAssignment" | ||||||
|  |                     > | ||||||
|  |                         <v-icon>mdi-delete</v-icon> | ||||||
|  |                     </v-btn> | ||||||
|  |                 </div> | ||||||
|  |                 <v-card-title class="text-h4">{{ data.title }}</v-card-title> | ||||||
|  |                 <v-card-subtitle class="subtitle-section"> | ||||||
|  |                     <v-btn | ||||||
|  |                         :to="`/learningPath/${language}/${data.learningPath}`" | ||||||
|  |                         variant="tonal" | ||||||
|  |                         color="primary" | ||||||
|  |                     > | ||||||
|  |                         {{ t("learning-path") }} | ||||||
|  |                     </v-btn> | ||||||
|  |                 </v-card-subtitle> | ||||||
|  | 
 | ||||||
|  |                 <v-card-text class="description"> | ||||||
|  |                     {{ data.description }} | ||||||
|  |                 </v-card-text> | ||||||
|  | 
 | ||||||
|  |                 <v-card-text class="group-section"> | ||||||
|  |                     <h3>{{ t("group") }}</h3> | ||||||
|  | 
 | ||||||
|  |                     <!-- Teacher view | ||||||
|  |                     <div v-if="isTeacher"> | ||||||
|  |                         <v-expansion-panels> | ||||||
|  |                             <v-expansion-panel | ||||||
|  |                                 v-for="(group, index) in assignment.groups" | ||||||
|  |                                 :key="group.id" | ||||||
|  |                             > | ||||||
|  |                                 <v-expansion-panel-title> | ||||||
|  |                                     {{ t("group") }} {{ index + 1 }} | ||||||
|  |                                 </v-expansion-panel-title> | ||||||
|  |                                 <v-expansion-panel-text> | ||||||
|  |                                     <ul> | ||||||
|  |                                         <li v-for="student in group.members" :key="student.username"> | ||||||
|  |                                             {{ student.firstName + ' ' + student.lastName }} | ||||||
|  |                                         </li> | ||||||
|  |                                     </ul> | ||||||
|  |                                 </v-expansion-panel-text> | ||||||
|  |                             </v-expansion-panel> | ||||||
|  |                         </v-expansion-panels> | ||||||
|  |                     </div>--> | ||||||
|  |                 </v-card-text> | ||||||
|  |                 <v-card-actions class="justify-end"> | ||||||
|  |                     <v-btn | ||||||
|  |                         size="large" | ||||||
|  |                         color="success" | ||||||
|  |                         variant="text" | ||||||
|  |                     > | ||||||
|  |                         {{ t("view-submissions") }} | ||||||
|  |                     </v-btn> | ||||||
|  |                 </v-card-actions> | ||||||
|  |             </v-card> | ||||||
|  |         </using-query-result> | ||||||
|  |     </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | @import "@/assets/assignment.css"; | ||||||
|  | </style> | ||||||
|  | 
 | ||||||
|  | @ -27,14 +27,15 @@ if (isTeacher.value) { | ||||||
|     classesQueryResults = useStudentClassesQuery(username, true); |     classesQueryResults = useStudentClassesQuery(username, true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | //TODO: replace with query from classes | ||||||
| const classController = new ClassController(); | const classController = new ClassController(); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 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 []; | ||||||
|     const result = await Promise.all( |     const result = await Promise.all( | ||||||
|         (classes as ClassDTO[]).map(async (cls) => { |         (classes as ClassDTO[]).map(async (cls) => { | ||||||
|  |             //TODO: replace by class queries | ||||||
|             const {assignments} = await classController.getAssignments(cls.id); |             const {assignments} = await classController.getAssignments(cls.id); | ||||||
|             return assignments.map(a => ({ |             return assignments.map(a => ({ | ||||||
|                 id: a.id, |                 id: a.id, | ||||||
|  | @ -51,8 +52,6 @@ const assignments = asyncComputed(async () => { | ||||||
|     return result.flat(); |     return result.flat(); | ||||||
| }, []); | }, []); | ||||||
| 
 | 
 | ||||||
| console.log(assignments); |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| const goToCreateAssignment = async () => { | const goToCreateAssignment = async () => { | ||||||
|     await router.push('/assignment/create'); |     await router.push('/assignment/create'); | ||||||
|  | @ -67,6 +66,7 @@ const goToAssignmentDetails = async (id: number, class_id: string) => { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| const goToDeleteAssignment = async (id: number, class_id: string) => { | const goToDeleteAssignment = async (id: number, class_id: string) => { | ||||||
|  |     //TODO: replace with query | ||||||
|     const controller = new AssignmentController(class_id); |     const controller = new AssignmentController(class_id); | ||||||
|     await controller.deleteAssignment(id); |     await controller.deleteAssignment(id); | ||||||
| }; | }; | ||||||
|  | @ -98,32 +98,30 @@ onMounted(async () => { | ||||||
|                     cols="12" |                     cols="12" | ||||||
|                 > |                 > | ||||||
|                     <v-card class="assignment-card"> |                     <v-card class="assignment-card"> | ||||||
|                         <v-card-text class="card-content"> |                         <div class="top-content"> | ||||||
|                             <div class="top-content"> |                             <div class="assignment-title">{{ assignment.title }}</div> | ||||||
|                                 <div class="assignment-title">{{ assignment.title }}</div> |                             <div class="assignment-class"> | ||||||
|                                 <div class="assignment-class"> |                                 {{ t('class') }}: | ||||||
|                                     {{ t('class') }}: |                                 <span class="class-name"> | ||||||
|                                     <span class="class-name"> |  | ||||||
|                                       {{ assignment.class.displayName }} |                                       {{ assignment.class.displayName }} | ||||||
|                                     </span> |                                     </span> | ||||||
|                                 </div> |  | ||||||
|                             </div> |                             </div> | ||||||
|  |                         </div> | ||||||
| 
 | 
 | ||||||
|                             <div class="spacer"></div> |                         <div class="spacer"></div> | ||||||
| 
 | 
 | ||||||
|                             <div class="button-row"> |                         <div class="button-row"> | ||||||
|                                 <v-btn color="primary" |                             <v-btn color="primary" | ||||||
|                                        size="small" |                                    variant="text" | ||||||
|                                        @click="goToAssignmentDetails(assignment.id, assignment.class.id)"> |                                    @click="goToAssignmentDetails(assignment.id, assignment.class.id)"> | ||||||
|                                     {{ t('view-assignment') }} |                                 {{ t('view-assignment') }} | ||||||
|                                 </v-btn> |                             </v-btn> | ||||||
|                                 <v-btn v-if="isTeacher" color="red" |                             <v-btn v-if="isTeacher" color="red" | ||||||
|                                        size="small" |                                    variant="text" | ||||||
|                                        @click="goToDeleteAssignment(assignment.id, assignment.class.id)"> |                                    @click="goToDeleteAssignment(assignment.id, assignment.class.id)"> | ||||||
|                                     {{ t('delete') }} |                                 {{ t('delete') }} | ||||||
|                                 </v-btn> |                             </v-btn> | ||||||
|                             </div> |                         </div> | ||||||
|                         </v-card-text> |  | ||||||
|                     </v-card> |                     </v-card> | ||||||
| 
 | 
 | ||||||
|                 </v-col> |                 </v-col> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Joyelle Ndagijimana
						Joyelle Ndagijimana