fix: 403 error bij het opvragen van een opdracht
This commit is contained in:
		
							parent
							
								
									96076844a5
								
							
						
					
					
						commit
						149e4e80fc
					
				
					 2 changed files with 277 additions and 269 deletions
				
			
		|  | @ -1,90 +1,103 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import { ref, computed, watchEffect } from "vue"; | import {ref, computed, watchEffect} from "vue"; | ||||||
|     import auth from "@/services/auth/auth-service.ts"; | import auth from "@/services/auth/auth-service.ts"; | ||||||
|     import { useI18n } from "vue-i18n"; | import {useI18n} from "vue-i18n"; | ||||||
|     import { useAssignmentQuery } from "@/queries/assignments.ts"; | import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | import type {AssignmentsResponse} from "@/controllers/assignments.ts"; | ||||||
|     import type { AssignmentResponse } from "@/controllers/assignments.ts"; | import {asyncComputed} from "@vueuse/core"; | ||||||
|     import { asyncComputed } from "@vueuse/core"; | import { | ||||||
|     import { useStudentGroupsQuery, useStudentsByUsernamesQuery } from "@/queries/students.ts"; |     useStudentAssignmentsQuery, | ||||||
|     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; |     useStudentGroupsQuery, | ||||||
|     import type { Language } from "@/data-objects/language.ts"; |     useStudentsByUsernamesQuery | ||||||
|     import { calculateProgress } from "@/utils/assignment-utils.ts"; | } from "@/queries/students.ts"; | ||||||
|     import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | import {useGetLearningPathQuery} from "@/queries/learning-paths.ts"; | ||||||
|  | import type {Language} from "@/data-objects/language.ts"; | ||||||
|  | import {calculateProgress} from "@/utils/assignment-utils.ts"; | ||||||
|  | import type {LearningPath} from "@/data-objects/learning-paths/learning-path.ts"; | ||||||
| 
 | 
 | ||||||
|     const props = defineProps<{ | const props = defineProps<{ | ||||||
|         classId: string; |     classId: string; | ||||||
|         assignmentId: number; |     assignmentId: number; | ||||||
|     }>(); | }>(); | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); | const {t} = useI18n(); | ||||||
|     const lang = ref(); | const lang = ref(); | ||||||
|     const learningPath = ref(); | const learningPath = ref(); | ||||||
|     // Get the user's username/id | // Get the user's username/id | ||||||
|     const username = asyncComputed(async () => { | const username = asyncComputed(async () => { | ||||||
|         const user = await auth.loadUser(); |     const user = await auth.loadUser(); | ||||||
|         return user?.profile?.preferred_username ?? undefined; |     return user?.profile?.preferred_username ?? undefined; | ||||||
|     }); | }); | ||||||
| 
 | 
 | ||||||
|     const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId); | const assignmentsQueryResult = useStudentAssignmentsQuery(username, true); | ||||||
|     learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; |  | ||||||
| 
 | 
 | ||||||
|     const groupsQueryResult = useStudentGroupsQuery(username, true); | const assignment = computed(() => { | ||||||
|     const group = computed(() => { |     const assignments = assignmentsQueryResult.data.value?.assignments; | ||||||
|         const groups = groupsQueryResult.data.value?.groups; |     if (!assignments) return undefined; | ||||||
| 
 | 
 | ||||||
|         if (!groups) return undefined; |     return assignments.find( | ||||||
| 
 |         (a) => a.id === props.assignmentId && a.within === props.classId | ||||||
|         // Sort by original groupNumber |  | ||||||
|         const sortedGroups = [...groups].sort((a, b) => a.groupNumber - b.groupNumber); |  | ||||||
| 
 |  | ||||||
|         return sortedGroups |  | ||||||
|             .map((group, index) => ({ |  | ||||||
|                 ...group, |  | ||||||
|                 groupNo: index + 1, // Renumbered index |  | ||||||
|             })) |  | ||||||
|             .find((group) => group.members?.some((m) => m.username === username.value)); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     watchEffect(() => { |  | ||||||
|         learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; |  | ||||||
|         lang.value = assignmentQueryResult.data.value?.assignment?.language as Language; |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const learningPathParams = computed(() => { |  | ||||||
|         if (!group.value || !learningPath.value || !lang.value) return undefined; |  | ||||||
| 
 |  | ||||||
|         return { |  | ||||||
|             forGroup: group.value.groupNumber, |  | ||||||
|             assignmentNo: props.assignmentId, |  | ||||||
|             classId: props.classId, |  | ||||||
|         }; |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const lpQueryResult = useGetLearningPathQuery( |  | ||||||
|         () => learningPath.value, |  | ||||||
|         () => lang.value, |  | ||||||
|         () => learningPathParams.value, |  | ||||||
|     ); |     ); | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
|     const progressColor = computed(() => { |  | ||||||
|         const progress = calculateProgress(lpQueryResult.data.value as LearningPath); |  | ||||||
|         if (progress >= 100) return "success"; |  | ||||||
|         if (progress >= 50) return "warning"; |  | ||||||
|         return "error"; |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     const studentQueries = useStudentsByUsernamesQuery(() => (group.value?.members as string[]) ?? undefined); | learningPath.value = assignment.value?.learningPath; | ||||||
|  | 
 | ||||||
|  | const groupsQueryResult = useStudentGroupsQuery(username, true); | ||||||
|  | const group = computed(() => { | ||||||
|  |     const groups = groupsQueryResult.data.value?.groups; | ||||||
|  | 
 | ||||||
|  |     if (!groups) return undefined; | ||||||
|  | 
 | ||||||
|  |     // Sort by original groupNumber | ||||||
|  |     const sortedGroups = [...groups].sort((a, b) => a.groupNumber - b.groupNumber); | ||||||
|  | 
 | ||||||
|  |     return sortedGroups | ||||||
|  |         .map((group, index) => ({ | ||||||
|  |             ...group, | ||||||
|  |             groupNo: index + 1, // Renumbered index | ||||||
|  |         })) | ||||||
|  |         .find((group) => group.members?.some((m) => m.username === username.value)); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | watchEffect(() => { | ||||||
|  |     learningPath.value = assignment.value?.learningPath; | ||||||
|  |     lang.value = assignment.value?.language as Language; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const learningPathParams = computed(() => { | ||||||
|  |     if (!group.value || !learningPath.value || !lang.value) return undefined; | ||||||
|  | 
 | ||||||
|  |     return { | ||||||
|  |         forGroup: group.value.groupNumber, | ||||||
|  |         assignmentNo: props.assignmentId, | ||||||
|  |         classId: props.classId, | ||||||
|  |     }; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const lpQueryResult = useGetLearningPathQuery( | ||||||
|  |     () => learningPath.value, | ||||||
|  |     () => lang.value, | ||||||
|  |     () => learningPathParams.value, | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | const progressColor = computed(() => { | ||||||
|  |     const progress = calculateProgress(lpQueryResult.data.value as LearningPath); | ||||||
|  |     if (progress >= 100) return "success"; | ||||||
|  |     if (progress >= 50) return "warning"; | ||||||
|  |     return "error"; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const studentQueries = useStudentsByUsernamesQuery(() => (group.value?.members as string[]) ?? undefined); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <div class="container"> |     <div class="container"> | ||||||
|         <using-query-result |         <using-query-result | ||||||
|             :query-result="assignmentQueryResult" |             :query-result="assignmentsQueryResult" | ||||||
|             v-slot="assignmentResponse: { data: AssignmentResponse }" |  | ||||||
|         > |         > | ||||||
|             <v-card |             <v-card | ||||||
|                 v-if="assignmentResponse" |                 v-if="assignment" | ||||||
|                 class="assignment-card" |                 class="assignment-card" | ||||||
|             > |             > | ||||||
|                 <div class="top-buttons"> |                 <div class="top-buttons"> | ||||||
|  | @ -98,7 +111,7 @@ | ||||||
|                     </v-btn> |                     </v-btn> | ||||||
|                 </div> |                 </div> | ||||||
|                 <v-card-title class="text-h4 assignmentTopTitle" |                 <v-card-title class="text-h4 assignmentTopTitle" | ||||||
|                     >{{ assignmentResponse.data.assignment.title }} |                 >{{ assignment.title }} | ||||||
|                 </v-card-title> |                 </v-card-title> | ||||||
| 
 | 
 | ||||||
|                 <v-card-subtitle class="subtitle-section"> |                 <v-card-subtitle class="subtitle-section"> | ||||||
|  | @ -110,7 +123,7 @@ | ||||||
|                             v-if="lpData" |                             v-if="lpData" | ||||||
|                             :to=" |                             :to=" | ||||||
|                                 group |                                 group | ||||||
|                                     ? `/learningPath/${lpData.hruid}/${assignmentResponse.data.assignment?.language}/${lpData.startNode.learningobjectHruid}?forGroup=${0}&assignmentNo=${assignmentId}&classId=${classId}` |                                     ? `/learningPath/${lpData.hruid}/${assignment.language}/${lpData.startNode.learningobjectHruid}?forGroup=${group.groupNo}&assignmentNo=${assignment.id}&classId=${assignment.within}` | ||||||
|                                     : undefined |                                     : undefined | ||||||
|                             " |                             " | ||||||
|                             :disabled="!group" |                             :disabled="!group" | ||||||
|  | @ -123,7 +136,7 @@ | ||||||
|                 </v-card-subtitle> |                 </v-card-subtitle> | ||||||
| 
 | 
 | ||||||
|                 <v-card-text class="description"> |                 <v-card-text class="description"> | ||||||
|                     {{ assignmentResponse.data.assignment.description }} |                     {{ assignment.description }} | ||||||
|                 </v-card-text> |                 </v-card-text> | ||||||
|                 <v-card-text> |                 <v-card-text> | ||||||
|                     <v-card-text> |                     <v-card-text> | ||||||
|  | @ -177,9 +190,9 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
|     @import "@/assets/assignment.css"; | @import "@/assets/assignment.css"; | ||||||
| 
 | 
 | ||||||
|     .progress-bar { | .progress-bar { | ||||||
|         width: 40%; |     width: 40%; | ||||||
|     } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,143 +1,139 @@ | ||||||
| <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 authState from "@/services/auth/auth-service.ts"; | 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 { useDeleteAssignmentMutation } from "@/queries/assignments.ts"; | import {useDeleteAssignmentMutation} from "@/queries/assignments.ts"; | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import { asyncComputed } from "@vueuse/core"; |  | ||||||
| 
 | 
 | ||||||
|     const { t, locale } = useI18n(); | const {t, locale} = useI18n(); | ||||||
|     const router = useRouter(); | const router = useRouter(); | ||||||
| 
 | 
 | ||||||
|     const role = ref(auth.authState.activeRole); | const role = ref(auth.authState.activeRole); | ||||||
|     const username = ref<string | undefined>(undefined); | const isTeacher = computed(() => role.value === "teacher"); | ||||||
|     const isLoading = ref(false); | const username = ref<string | undefined>(undefined); | ||||||
|     const isError = ref(false); | const isLoading = ref(false); | ||||||
|     const errorMessage = ref<string>(""); | const isError = ref(false); | ||||||
|  | const errorMessage = ref<string>(""); | ||||||
| 
 | 
 | ||||||
|     // Load current user before rendering the page | // Load current user before rendering the page | ||||||
|     onMounted(async () => { | onMounted(async () => { | ||||||
|         isLoading.value = true; |     isLoading.value = true; | ||||||
|         try { |     try { | ||||||
|             const userObject = await authState.loadUser(); |         const userObject = await authState.loadUser(); | ||||||
|             username.value = userObject!.profile.preferred_username; |         username.value = userObject!.profile.preferred_username; | ||||||
|         } catch (error) { |     } catch (error) { | ||||||
|             isError.value = true; |         isError.value = true; | ||||||
|             errorMessage.value = error instanceof Error ? error.message : String(error); |         errorMessage.value = error instanceof Error ? error.message : String(error); | ||||||
|         } finally { |     } finally { | ||||||
|             isLoading.value = false; |         isLoading.value = false; | ||||||
|         } |     } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const classesQueryResult = isTeacher.value | ||||||
|  |     ? useTeacherClassesQuery(username, true) | ||||||
|  |     : useStudentClassesQuery(username, true); | ||||||
|  | 
 | ||||||
|  | const assignmentsQueryResult = isTeacher.value | ||||||
|  |     ? useTeacherAssignmentsQuery(username, true) | ||||||
|  |     : useStudentAssignmentsQuery(username, true); | ||||||
|  | 
 | ||||||
|  | const allAssignments = computed(() => { | ||||||
|  |     const assignments = assignmentsQueryResult.data.value?.assignments; | ||||||
|  |     if (!assignments) return []; | ||||||
|  | 
 | ||||||
|  |     const classes = classesQueryResult.data.value?.classes; | ||||||
|  |     if (!classes) return []; | ||||||
|  | 
 | ||||||
|  |     const result = assignments.map((a) => ({ | ||||||
|  |         id: a.id, | ||||||
|  |         class: classes.find((cls) => cls?.id === a.within) ?? undefined, | ||||||
|  |         title: a.title, | ||||||
|  |         description: a.description, | ||||||
|  |         learningPath: a.learningPath, | ||||||
|  |         language: a.language, | ||||||
|  |         deadline: a.deadline, | ||||||
|  |         groups: a.groups, | ||||||
|  |     })); | ||||||
|  | 
 | ||||||
|  |     // Order the assignments by deadline | ||||||
|  |     return result.flat().sort((a, b) => { | ||||||
|  |         const now = Date.now(); | ||||||
|  |         const aTime = new Date(a.deadline).getTime(); | ||||||
|  |         const bTime = new Date(b.deadline).getTime(); | ||||||
|  | 
 | ||||||
|  |         const aIsPast = aTime < now; | ||||||
|  |         const bIsPast = bTime < now; | ||||||
|  | 
 | ||||||
|  |         if (aIsPast && !bIsPast) return 1; | ||||||
|  |         if (!aIsPast && bIsPast) return -1; | ||||||
|  | 
 | ||||||
|  |         return aTime - bTime; | ||||||
|     }); |     }); | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
|     const isTeacher = computed(() => role.value === "teacher"); |  | ||||||
|     const classesQueryResult = isTeacher.value |  | ||||||
|         ? useTeacherClassesQuery(username, true) |  | ||||||
|         : useStudentClassesQuery(username, true); |  | ||||||
| 
 | 
 | ||||||
|     const assignmentsQueryResult = isTeacher.value | async function goToCreateAssignment(): Promise<void> { | ||||||
|         ? useTeacherAssignmentsQuery(username, true) |     await router.push("/assignment/create"); | ||||||
|         : useStudentAssignmentsQuery(username, true); | } | ||||||
| 
 | 
 | ||||||
|     const allAssignments = asyncComputed( | async function goToAssignmentDetails(id: number, clsId: string): Promise<void> { | ||||||
|         async () => { |     await router.push(`/assignment/${clsId}/${id}`); | ||||||
|             const assignments = assignmentsQueryResult.data.value?.assignments; | } | ||||||
|             if (!assignments) return []; |  | ||||||
| 
 | 
 | ||||||
|             const classes = classesQueryResult.data.value?.classes; | const {mutate, data, isSuccess} = useDeleteAssignmentMutation(); | ||||||
|             if (!classes) return []; |  | ||||||
| 
 | 
 | ||||||
|             const result = assignments.map((a) => ({ | watch([isSuccess, data], async ([success, oldData]) => { | ||||||
|                 id: a.id, |     if (success && oldData?.assignment) { | ||||||
|                 class: classes.find((cls) => cls?.id === a.within) ?? undefined, |         window.location.reload(); | ||||||
|                 title: a.title, |  | ||||||
|                 description: a.description, |  | ||||||
|                 learningPath: a.learningPath, |  | ||||||
|                 language: a.language, |  | ||||||
|                 deadline: a.deadline, |  | ||||||
|                 groups: a.groups, |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             // Order the assignments by deadline |  | ||||||
|             return result.flat().sort((a, b) => { |  | ||||||
|                 const now = Date.now(); |  | ||||||
|                 const aTime = new Date(a.deadline).getTime(); |  | ||||||
|                 const bTime = new Date(b.deadline).getTime(); |  | ||||||
| 
 |  | ||||||
|                 const aIsPast = aTime < now; |  | ||||||
|                 const bIsPast = bTime < now; |  | ||||||
| 
 |  | ||||||
|                 if (aIsPast && !bIsPast) return 1; |  | ||||||
|                 if (!aIsPast && bIsPast) return -1; |  | ||||||
| 
 |  | ||||||
|                 return aTime - bTime; |  | ||||||
|             }); |  | ||||||
|         }, |  | ||||||
|         [], |  | ||||||
|         { evaluating: true }, |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     async function goToCreateAssignment(): Promise<void> { |  | ||||||
|         await router.push("/assignment/create"); |  | ||||||
|     } |     } | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
|     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(); | function formatDate(date?: string | Date): string { | ||||||
|  |     if (!date) return "–"; | ||||||
|  |     const d = new Date(date); | ||||||
| 
 | 
 | ||||||
|     watch([isSuccess, data], async ([success, oldData]) => { |     // Choose locale based on selected language | ||||||
|         if (success && oldData?.assignment) { |     const currentLocale = locale.value; | ||||||
|             window.location.reload(); | 
 | ||||||
|         } |     return d.toLocaleDateString(currentLocale, { | ||||||
|  |         weekday: "short", | ||||||
|  |         day: "2-digit", | ||||||
|  |         month: "long", | ||||||
|  |         year: "numeric", | ||||||
|  |         hour: "numeric", | ||||||
|  |         minute: "2-digit", | ||||||
|     }); |     }); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     async function goToDeleteAssignment(num: number, clsId: string): Promise<void> { | function getDeadlineClass(deadline?: string | Date): string { | ||||||
|         mutate({ cid: clsId, an: num }); |     if (!deadline) return ""; | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     function formatDate(date?: string | Date): string { |     const date = new Date(deadline); | ||||||
|         if (!date) return "–"; |     const now = new Date(); | ||||||
|         const d = new Date(date); |     const in24Hours = new Date(now.getTime() + 24 * 60 * 60 * 1000); | ||||||
| 
 | 
 | ||||||
|         // Choose locale based on selected language |     if (date.getTime() < now.getTime()) return "deadline-passed"; | ||||||
|         const currentLocale = locale.value; |     if (date.getTime() <= in24Hours.getTime()) return "deadline-in24hours"; | ||||||
|  |     return "deadline-upcoming"; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|         return d.toLocaleDateString(currentLocale, { | onMounted(async () => { | ||||||
|             weekday: "short", |     const user = await auth.loadUser(); | ||||||
|             day: "2-digit", |     username.value = user?.profile?.preferred_username ?? ""; | ||||||
|             month: "long", | }); | ||||||
|             year: "numeric", |  | ||||||
|             hour: "numeric", |  | ||||||
|             minute: "2-digit", |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     function getDeadlineClass(deadline?: string | Date): string { | onMounted(async () => { | ||||||
|         if (!deadline) return ""; |     const user = await auth.loadUser(); | ||||||
| 
 |     username.value = user?.profile?.preferred_username ?? ""; | ||||||
|         const date = new Date(deadline); | }); | ||||||
|         const now = new Date(); |  | ||||||
|         const in24Hours = new Date(now.getTime() + 24 * 60 * 60 * 1000); |  | ||||||
| 
 |  | ||||||
|         if (date.getTime() < now.getTime()) return "deadline-passed"; |  | ||||||
|         if (date.getTime() <= in24Hours.getTime()) return "deadline-in24hours"; |  | ||||||
|         return "deadline-upcoming"; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     onMounted(async () => { |  | ||||||
|         const user = await auth.loadUser(); |  | ||||||
|         username.value = user?.profile?.preferred_username ?? ""; |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     onMounted(async () => { |  | ||||||
|         const user = await auth.loadUser(); |  | ||||||
|         username.value = user?.profile?.preferred_username ?? ""; |  | ||||||
|     }); |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -214,88 +210,87 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
|     .assignments-container { | .assignments-container { | ||||||
|         width: 100%; |     width: 100%; | ||||||
|         margin: 0 auto; |     margin: 0 auto; | ||||||
|         box-sizing: border-box; |     box-sizing: border-box; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .center-btn { | .center-btn { | ||||||
|         display: block; |     display: block; | ||||||
|         margin: 0 auto 2rem auto; |     margin: 0 auto 2rem auto; | ||||||
|         font-weight: 600; |     font-weight: 600; | ||||||
|         background-color: #10ad61; |     background-color: #10ad61; | ||||||
|         color: white; |     color: white; | ||||||
|         transition: background-color 0.2s; |     transition: background-color 0.2s; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .center-btn:hover { | .center-btn:hover { | ||||||
|         background-color: #0e6942; |     background-color: #0e6942; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .assignment-card { | .assignment-card { | ||||||
|         padding: 1.25rem; |     padding: 1.25rem; | ||||||
|         border-radius: 16px; |     border-radius: 16px; | ||||||
|         box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); |     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); | ||||||
|         background-color: white; |     background-color: white; | ||||||
|         transition: |     transition: transform 0.2s, | ||||||
|             transform 0.2s, |     box-shadow 0.2s; | ||||||
|             box-shadow 0.2s; | } | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     .assignment-card:hover { | .assignment-card:hover { | ||||||
|         box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); |     box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .top-content { | .top-content { | ||||||
|         margin-bottom: 1rem; |     margin-bottom: 1rem; | ||||||
|         word-break: break-word; |     word-break: break-word; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .assignment-title { | .assignment-title { | ||||||
|         font-weight: 700; |     font-weight: 700; | ||||||
|         font-size: 1.4rem; |     font-size: 1.4rem; | ||||||
|         color: #0e6942; |     color: #0e6942; | ||||||
|         margin-bottom: 0.3rem; |     margin-bottom: 0.3rem; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .assignment-class, | .assignment-class, | ||||||
|     .assignment-deadline { | .assignment-deadline { | ||||||
|         font-size: 0.95rem; |     font-size: 0.95rem; | ||||||
|         color: #444; |     color: #444; | ||||||
|         margin-bottom: 0.2rem; |     margin-bottom: 0.2rem; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .class-name { | .class-name { | ||||||
|         font-weight: 600; |     font-weight: 600; | ||||||
|         color: #097180; |     color: #097180; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .assignment-deadline.deadline-passed { | .assignment-deadline.deadline-passed { | ||||||
|         color: #d32f2f; |     color: #d32f2f; | ||||||
|         font-weight: bold; |     font-weight: bold; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .assignment-deadline.deadline-in24hours { | .assignment-deadline.deadline-in24hours { | ||||||
|         color: #f57c00; |     color: #f57c00; | ||||||
|         font-weight: bold; |     font-weight: bold; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .spacer { | .spacer { | ||||||
|         flex: 1; |     flex: 1; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .button-row { | .button-row { | ||||||
|         display: flex; |     display: flex; | ||||||
|         justify-content: flex-end; |     justify-content: flex-end; | ||||||
|         gap: 0.75rem; |     gap: 0.75rem; | ||||||
|         flex-wrap: wrap; |     flex-wrap: wrap; | ||||||
|     } | } | ||||||
| 
 | 
 | ||||||
|     .no-assignments { | .no-assignments { | ||||||
|         text-align: center; |     text-align: center; | ||||||
|         font-size: 1.2rem; |     font-size: 1.2rem; | ||||||
|         color: #777; |     color: #777; | ||||||
|         padding: 3rem 0; |     padding: 3rem 0; | ||||||
|     } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Joyelle Ndagijimana
						Joyelle Ndagijimana