feat: extra vertalingen
This commit is contained in:
		
							parent
							
								
									149e4e80fc
								
							
						
					
					
						commit
						c4f178aa52
					
				
					 6 changed files with 320 additions and 280 deletions
				
			
		|  | @ -165,5 +165,21 @@ | |||
|     "pathContainsNonExistingLearningObjects": "Mindestens eines der in diesem Pfad referenzierten Lernobjekte existiert nicht.", | ||||
|     "targetAgesMandatory": "Zielalter müssen angegeben werden.", | ||||
|     "hintRemoveIfUnconditionalTransition": "(entfernen, wenn dies ein bedingungsloser Übergang sein soll)", | ||||
|     "hintKeywordsSeparatedBySpaces": "Schlüsselwörter durch Leerzeichen getrennt" | ||||
|     "hintKeywordsSeparatedBySpaces": "Schlüsselwörter durch Leerzeichen getrennt", | ||||
|     "title-required": "Titel darf nicht leer sein.", | ||||
|     "class-required": "Du musst eine Klasse auswählen.", | ||||
|     "deadline-invalid": "Ungültiges Datum oder Uhrzeit.", | ||||
|     "deadline-past": "Die Frist muss in der Zukunft liegen.", | ||||
|     "lp-required": "Du musst einen Lernpfad auswählen.", | ||||
|     "lp-invalid": "Der ausgewählte Lernpfad existiert nicht.", | ||||
|     "currently-no-groups": "Es gibt keine Gruppen für diese Aufgabe.", | ||||
|     "random-grouping": "Gruppen zufällig erstellen", | ||||
|     "drag-and-drop": "Gruppen manuell erstellen", | ||||
|     "generate-groups": "erzeugen", | ||||
|     "auto-generate-groups": "Gruppen gleicher Größe erstellen", | ||||
|     "preview": "Vorschau", | ||||
|     "current-groups": "Aktuelle Gruppen", | ||||
|     "group-size-label": "Gruppengröße", | ||||
|     "save": "Speichern", | ||||
|     "unassigned": "Nicht zugewiesen" | ||||
| } | ||||
|  |  | |||
|  | @ -168,7 +168,7 @@ | |||
|     "hintRemoveIfUnconditionalTransition": "(remove this if this should be an unconditional transition)", | ||||
|     "hintKeywordsSeparatedBySpaces": "Keywords separated by spaces", | ||||
|     "title-required": "Title cannot be empty.", | ||||
|     "class-required": "You must select at least one class.", | ||||
|     "class-required": "You must select a class.", | ||||
|     "deadline-invalid": "Invalid date or time.", | ||||
|     "deadline-past": "The deadline must be in the future.", | ||||
|     "lp-required": "You must select a learning path.", | ||||
|  |  | |||
|  | @ -88,7 +88,7 @@ | |||
|     "deny": "refuser", | ||||
|     "sent": "envoyé", | ||||
|     "failed": "échoué", | ||||
|     "wrong": "quelque chose n'a pas fonctionné", | ||||
|     "wrong": "Il y a une erreur", | ||||
|     "created": "créé", | ||||
|     "callbackLoading": "Vous serez connecté...", | ||||
|     "loginUnexpectedError": "La connexion a échoué", | ||||
|  | @ -98,7 +98,7 @@ | |||
|     "groupSubmissions": "Soumissions de ce groupe", | ||||
|     "taskCompleted": "Tâche terminée.", | ||||
|     "submittedBy": "Soumis par", | ||||
|     "timestamp": "Horodatage", | ||||
|     "timestamp": "Date et heure", | ||||
|     "loadSubmission": "Charger", | ||||
|     "noSubmissionsYet": "Pas encore de soumissions.", | ||||
|     "viewAsGroup": "Voir la progression du groupe...", | ||||
|  | @ -166,5 +166,21 @@ | |||
|     "pathContainsNonExistingLearningObjects": "Au moins un des objets d’apprentissage référencés dans ce chemin n’existe pas.", | ||||
|     "targetAgesMandatory": "Les âges cibles doivent être spécifiés.", | ||||
|     "hintRemoveIfUnconditionalTransition": "(supprimer ceci s’il s’agit d’une transition inconditionnelle)", | ||||
|     "hintKeywordsSeparatedBySpaces": "Mots-clés séparés par des espaces" | ||||
|     "hintKeywordsSeparatedBySpaces": "Mots-clés séparés par des espaces", | ||||
|     "title-required": "Le titre ne peut pas être vide.", | ||||
|     "class-required": "Vous devez sélectionner une classe.", | ||||
|     "deadline-invalid": "Date ou heure invalide.", | ||||
|     "deadline-past": "La date limite doit être dans le futur.", | ||||
|     "lp-required": "Vous devez sélectionner un parcours d'apprentissage.", | ||||
|     "lp-invalid": "Le parcours d'apprentissage sélectionné n'existe pas.", | ||||
|     "currently-no-groups": "Il n’y a pas de groupes pour cette tâche.", | ||||
|     "random-grouping": "Créer des groupes aléatoirement", | ||||
|     "drag-and-drop": "Créer des groupes manuellement", | ||||
|     "generate-groups": "générer", | ||||
|     "auto-generate-groups": "Créer des groupes de taille égale", | ||||
|     "preview": "Aperçu", | ||||
|     "current-groups": "Groupes actuels", | ||||
|     "group-size-label": "Taille des groupes", | ||||
|     "save": "Enregistrer", | ||||
|     "unassigned": "Non assigné" | ||||
| } | ||||
|  |  | |||
|  | @ -165,5 +165,21 @@ | |||
|     "pathContainsNonExistingLearningObjects": "Ten minste één van de leerobjecten in dit pad bestaat niet.", | ||||
|     "targetAgesMandatory": "Doelleeftijden moeten worden opgegeven.", | ||||
|     "hintRemoveIfUnconditionalTransition": "(verwijder dit voor onvoorwaardelijke overgangen)", | ||||
|     "hintKeywordsSeparatedBySpaces": "Trefwoorden gescheiden door spaties" | ||||
|     "hintKeywordsSeparatedBySpaces": "Trefwoorden gescheiden door spaties", | ||||
|     "title-required": "Titel mag niet leeg zijn.", | ||||
|     "class-required": "Je moet een klas selecteren.", | ||||
|     "deadline-invalid": "Ongeldige datum of tijd.", | ||||
|     "deadline-past": "De deadline moet in de toekomst liggen.", | ||||
|     "lp-required": "Je moet een leerpad selecteren.", | ||||
|     "lp-invalid": "Het geselecteerde leerpad bestaat niet.", | ||||
|     "currently-no-groups": "Er zijn geen groepen voor deze opdracht.", | ||||
|     "random-grouping": "Groepeer willekeurig", | ||||
|     "drag-and-drop": "Stel groepen handmatig samen", | ||||
|     "generate-groups": "genereren", | ||||
|     "auto-generate-groups": "Maak groepen van gelijke grootte", | ||||
|     "preview": "Voorbeeld", | ||||
|     "current-groups": "Huidige groepen", | ||||
|     "group-size-label": "Grootte van groepen", | ||||
|     "save": "Opslaan", | ||||
|     "unassigned": "Niet toegewezen" | ||||
| } | ||||
|  |  | |||
|  | @ -1,101 +1,95 @@ | |||
| <script setup lang="ts"> | ||||
| import {ref, computed, watchEffect} from "vue"; | ||||
| import auth from "@/services/auth/auth-service.ts"; | ||||
| import {useI18n} from "vue-i18n"; | ||||
| import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||
| import type {AssignmentsResponse} from "@/controllers/assignments.ts"; | ||||
| import {asyncComputed} from "@vueuse/core"; | ||||
| import { | ||||
|     useStudentAssignmentsQuery, | ||||
|     useStudentGroupsQuery, | ||||
|     useStudentsByUsernamesQuery | ||||
| } from "@/queries/students.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"; | ||||
|     import { ref, computed, watchEffect } from "vue"; | ||||
|     import auth from "@/services/auth/auth-service.ts"; | ||||
|     import { useI18n } from "vue-i18n"; | ||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||
|     import { asyncComputed } from "@vueuse/core"; | ||||
|     import { | ||||
|         useStudentAssignmentsQuery, | ||||
|         useStudentGroupsQuery, | ||||
|         useStudentsByUsernamesQuery, | ||||
|     } from "@/queries/students.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<{ | ||||
|     classId: string; | ||||
|     assignmentId: number; | ||||
| }>(); | ||||
|     const props = defineProps<{ | ||||
|         classId: string; | ||||
|         assignmentId: number; | ||||
|     }>(); | ||||
| 
 | ||||
| const {t} = useI18n(); | ||||
| const lang = ref(); | ||||
| const learningPath = ref(); | ||||
| // Get the user's username/id | ||||
| const username = asyncComputed(async () => { | ||||
|     const user = await auth.loadUser(); | ||||
|     return user?.profile?.preferred_username ?? undefined; | ||||
| }); | ||||
|     const { t } = useI18n(); | ||||
|     const lang = ref(); | ||||
|     const learningPath = ref(); | ||||
|     // Get the user's username/id | ||||
|     const username = asyncComputed(async () => { | ||||
|         const user = await auth.loadUser(); | ||||
|         return user?.profile?.preferred_username ?? undefined; | ||||
|     }); | ||||
| 
 | ||||
| const assignmentsQueryResult = useStudentAssignmentsQuery(username, true); | ||||
|     const assignmentsQueryResult = useStudentAssignmentsQuery(username, true); | ||||
| 
 | ||||
| const assignment = computed(() => { | ||||
|     const assignments = assignmentsQueryResult.data.value?.assignments; | ||||
|     if (!assignments) return undefined; | ||||
|     const assignment = computed(() => { | ||||
|         const assignments = assignmentsQueryResult.data.value?.assignments; | ||||
|         if (!assignments) return undefined; | ||||
| 
 | ||||
|     return assignments.find( | ||||
|         (a) => a.id === props.assignmentId && a.within === props.classId | ||||
|     ); | ||||
| }); | ||||
|         return assignments.find((a) => a.id === props.assignmentId && a.within === props.classId); | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
| 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; | ||||
|     const groupsQueryResult = useStudentGroupsQuery(username, true); | ||||
|     const group = computed(() => { | ||||
|         const groups = groupsQueryResult.data.value?.groups; | ||||
| 
 | ||||
|     return { | ||||
|         forGroup: group.value.groupNumber, | ||||
|         assignmentNo: props.assignmentId, | ||||
|         classId: props.classId, | ||||
|     }; | ||||
| }); | ||||
|         if (!groups) return undefined; | ||||
| 
 | ||||
| const lpQueryResult = useGetLearningPathQuery( | ||||
|     () => learningPath.value, | ||||
|     () => lang.value, | ||||
|     () => learningPathParams.value, | ||||
| ); | ||||
|         // Sort by original groupNumber | ||||
|         const sortedGroups = [...groups].sort((a, b) => a.groupNumber - b.groupNumber); | ||||
| 
 | ||||
| const progressColor = computed(() => { | ||||
|     const progress = calculateProgress(lpQueryResult.data.value as LearningPath); | ||||
|     if (progress >= 100) return "success"; | ||||
|     if (progress >= 50) return "warning"; | ||||
|     return "error"; | ||||
| }); | ||||
|         return sortedGroups | ||||
|             .map((group, index) => ({ | ||||
|                 ...group, | ||||
|                 groupNo: index + 1, // Renumbered index | ||||
|             })) | ||||
|             .find((group) => group.members?.some((m) => m.username === username.value)); | ||||
|     }); | ||||
| 
 | ||||
| const studentQueries = useStudentsByUsernamesQuery(() => (group.value?.members as string[]) ?? undefined); | ||||
|     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> | ||||
| 
 | ||||
| <template> | ||||
|     <div class="container"> | ||||
|         <using-query-result | ||||
|             :query-result="assignmentsQueryResult" | ||||
|         > | ||||
|         <using-query-result :query-result="assignmentsQueryResult"> | ||||
|             <v-card | ||||
|                 v-if="assignment" | ||||
|                 class="assignment-card" | ||||
|  | @ -110,9 +104,7 @@ const studentQueries = useStudentsByUsernamesQuery(() => (group.value?.members a | |||
|                         <v-icon>mdi-arrow-left</v-icon> | ||||
|                     </v-btn> | ||||
|                 </div> | ||||
|                 <v-card-title class="text-h4 assignmentTopTitle" | ||||
|                 >{{ assignment.title }} | ||||
|                 </v-card-title> | ||||
|                 <v-card-title class="text-h4 assignmentTopTitle">{{ assignment.title }} </v-card-title> | ||||
| 
 | ||||
|                 <v-card-subtitle class="subtitle-section"> | ||||
|                     <using-query-result | ||||
|  | @ -190,9 +182,9 @@ const studentQueries = useStudentsByUsernamesQuery(() => (group.value?.members a | |||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| @import "@/assets/assignment.css"; | ||||
|     @import "@/assets/assignment.css"; | ||||
| 
 | ||||
| .progress-bar { | ||||
|     width: 40%; | ||||
| } | ||||
|     .progress-bar { | ||||
|         width: 40%; | ||||
|     } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,139 +1,138 @@ | |||
| <script setup lang="ts"> | ||||
| import {ref, computed, onMounted, watch} from "vue"; | ||||
| import {useI18n} from "vue-i18n"; | ||||
| import {useRouter} from "vue-router"; | ||||
| import authState from "@/services/auth/auth-service.ts"; | ||||
| import auth from "@/services/auth/auth-service.ts"; | ||||
| import {useTeacherAssignmentsQuery, useTeacherClassesQuery} from "@/queries/teachers.ts"; | ||||
| import {useStudentAssignmentsQuery, useStudentClassesQuery} from "@/queries/students.ts"; | ||||
| import {useDeleteAssignmentMutation} from "@/queries/assignments.ts"; | ||||
| import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||
|     import { ref, computed, onMounted, watch } from "vue"; | ||||
|     import { useI18n } from "vue-i18n"; | ||||
|     import { useRouter } from "vue-router"; | ||||
|     import authState from "@/services/auth/auth-service.ts"; | ||||
|     import auth from "@/services/auth/auth-service.ts"; | ||||
|     import { useTeacherAssignmentsQuery, useTeacherClassesQuery } from "@/queries/teachers.ts"; | ||||
|     import { useStudentAssignmentsQuery, useStudentClassesQuery } from "@/queries/students.ts"; | ||||
|     import { useDeleteAssignmentMutation } from "@/queries/assignments.ts"; | ||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||
| 
 | ||||
| const {t, locale} = useI18n(); | ||||
| const router = useRouter(); | ||||
|     const { t, locale } = useI18n(); | ||||
|     const router = useRouter(); | ||||
| 
 | ||||
| const role = ref(auth.authState.activeRole); | ||||
| const isTeacher = computed(() => role.value === "teacher"); | ||||
| const username = ref<string | undefined>(undefined); | ||||
| const isLoading = ref(false); | ||||
| const isError = ref(false); | ||||
| const errorMessage = ref<string>(""); | ||||
|     const role = ref(auth.authState.activeRole); | ||||
|     const isTeacher = computed(() => role.value === "teacher"); | ||||
|     const username = ref<string | undefined>(undefined); | ||||
|     const isLoading = ref(false); | ||||
|     const isError = ref(false); | ||||
|     const errorMessage = ref<string>(""); | ||||
| 
 | ||||
| // Load current user before rendering the page | ||||
| onMounted(async () => { | ||||
|     isLoading.value = true; | ||||
|     try { | ||||
|         const userObject = await authState.loadUser(); | ||||
|         username.value = userObject!.profile.preferred_username; | ||||
|     } catch (error) { | ||||
|         isError.value = true; | ||||
|         errorMessage.value = error instanceof Error ? error.message : String(error); | ||||
|     } finally { | ||||
|         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; | ||||
|     // Load current user before rendering the page | ||||
|     onMounted(async () => { | ||||
|         isLoading.value = true; | ||||
|         try { | ||||
|             const userObject = await authState.loadUser(); | ||||
|             username.value = userObject!.profile.preferred_username; | ||||
|         } catch (error) { | ||||
|             isError.value = true; | ||||
|             errorMessage.value = error instanceof Error ? error.message : String(error); | ||||
|         } finally { | ||||
|             isLoading.value = false; | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
|     const classesQueryResult = isTeacher.value | ||||
|         ? useTeacherClassesQuery(username, true) | ||||
|         : useStudentClassesQuery(username, true); | ||||
| 
 | ||||
| async function goToCreateAssignment(): Promise<void> { | ||||
|     await router.push("/assignment/create"); | ||||
| } | ||||
|     const assignmentsQueryResult = isTeacher.value | ||||
|         ? useTeacherAssignmentsQuery(username, true) | ||||
|         : useStudentAssignmentsQuery(username, true); | ||||
| 
 | ||||
| async function goToAssignmentDetails(id: number, clsId: string): Promise<void> { | ||||
|     await router.push(`/assignment/${clsId}/${id}`); | ||||
| } | ||||
|     const allAssignments = computed(() => { | ||||
|         const assignments = assignmentsQueryResult.data.value?.assignments; | ||||
|         if (!assignments) return []; | ||||
| 
 | ||||
| const {mutate, data, isSuccess} = useDeleteAssignmentMutation(); | ||||
|         const classes = classesQueryResult.data.value?.classes; | ||||
|         if (!classes) return []; | ||||
| 
 | ||||
| watch([isSuccess, data], async ([success, oldData]) => { | ||||
|     if (success && oldData?.assignment) { | ||||
|         window.location.reload(); | ||||
|     } | ||||
| }); | ||||
|         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, | ||||
|         })); | ||||
| 
 | ||||
| async function goToDeleteAssignment(num: number, clsId: string): Promise<void> { | ||||
|     mutate({cid: clsId, an: num}); | ||||
| } | ||||
|         // 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(); | ||||
| 
 | ||||
| function formatDate(date?: string | Date): string { | ||||
|     if (!date) return "–"; | ||||
|     const d = new Date(date); | ||||
|             const aIsPast = aTime < now; | ||||
|             const bIsPast = bTime < now; | ||||
| 
 | ||||
|     // Choose locale based on selected language | ||||
|     const currentLocale = locale.value; | ||||
|             if (aIsPast && !bIsPast) return 1; | ||||
|             if (!aIsPast && bIsPast) return -1; | ||||
| 
 | ||||
|     return d.toLocaleDateString(currentLocale, { | ||||
|         weekday: "short", | ||||
|         day: "2-digit", | ||||
|         month: "long", | ||||
|         year: "numeric", | ||||
|         hour: "numeric", | ||||
|         minute: "2-digit", | ||||
|             return aTime - bTime; | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function getDeadlineClass(deadline?: string | Date): string { | ||||
|     if (!deadline) return ""; | ||||
|     async function goToCreateAssignment(): Promise<void> { | ||||
|         await router.push("/assignment/create"); | ||||
|     } | ||||
| 
 | ||||
|     const date = new Date(deadline); | ||||
|     const now = new Date(); | ||||
|     const in24Hours = new Date(now.getTime() + 24 * 60 * 60 * 1000); | ||||
|     async function goToAssignmentDetails(id: number, clsId: string): Promise<void> { | ||||
|         await router.push(`/assignment/${clsId}/${id}`); | ||||
|     } | ||||
| 
 | ||||
|     if (date.getTime() < now.getTime()) return "deadline-passed"; | ||||
|     if (date.getTime() <= in24Hours.getTime()) return "deadline-in24hours"; | ||||
|     return "deadline-upcoming"; | ||||
| } | ||||
|     const { mutate, data, isSuccess } = useDeleteAssignmentMutation(); | ||||
| 
 | ||||
| onMounted(async () => { | ||||
|     const user = await auth.loadUser(); | ||||
|     username.value = user?.profile?.preferred_username ?? ""; | ||||
| }); | ||||
|     watch([isSuccess, data], async ([success, oldData]) => { | ||||
|         if (success && oldData?.assignment) { | ||||
|             window.location.reload(); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
| onMounted(async () => { | ||||
|     const user = await auth.loadUser(); | ||||
|     username.value = user?.profile?.preferred_username ?? ""; | ||||
| }); | ||||
|     async function goToDeleteAssignment(num: number, clsId: string): Promise<void> { | ||||
|         mutate({ cid: clsId, an: num }); | ||||
|     } | ||||
| 
 | ||||
|     function formatDate(date?: string | Date): string { | ||||
|         if (!date) return "–"; | ||||
|         const d = new Date(date); | ||||
| 
 | ||||
|         // Choose locale based on selected language | ||||
|         const currentLocale = locale.value; | ||||
| 
 | ||||
|         return d.toLocaleDateString(currentLocale, { | ||||
|             weekday: "short", | ||||
|             day: "2-digit", | ||||
|             month: "long", | ||||
|             year: "numeric", | ||||
|             hour: "numeric", | ||||
|             minute: "2-digit", | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     function getDeadlineClass(deadline?: string | Date): string { | ||||
|         if (!deadline) return ""; | ||||
| 
 | ||||
|         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> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -210,87 +209,88 @@ onMounted(async () => { | |||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| .assignments-container { | ||||
|     width: 100%; | ||||
|     margin: 0 auto; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
|     .assignments-container { | ||||
|         width: 100%; | ||||
|         margin: 0 auto; | ||||
|         box-sizing: border-box; | ||||
|     } | ||||
| 
 | ||||
| .center-btn { | ||||
|     display: block; | ||||
|     margin: 0 auto 2rem auto; | ||||
|     font-weight: 600; | ||||
|     background-color: #10ad61; | ||||
|     color: white; | ||||
|     transition: background-color 0.2s; | ||||
| } | ||||
|     .center-btn { | ||||
|         display: block; | ||||
|         margin: 0 auto 2rem auto; | ||||
|         font-weight: 600; | ||||
|         background-color: #10ad61; | ||||
|         color: white; | ||||
|         transition: background-color 0.2s; | ||||
|     } | ||||
| 
 | ||||
| .center-btn:hover { | ||||
|     background-color: #0e6942; | ||||
| } | ||||
|     .center-btn:hover { | ||||
|         background-color: #0e6942; | ||||
|     } | ||||
| 
 | ||||
| .assignment-card { | ||||
|     padding: 1.25rem; | ||||
|     border-radius: 16px; | ||||
|     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); | ||||
|     background-color: white; | ||||
|     transition: transform 0.2s, | ||||
|     box-shadow 0.2s; | ||||
| } | ||||
|     .assignment-card { | ||||
|         padding: 1.25rem; | ||||
|         border-radius: 16px; | ||||
|         box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); | ||||
|         background-color: white; | ||||
|         transition: | ||||
|             transform 0.2s, | ||||
|             box-shadow 0.2s; | ||||
|     } | ||||
| 
 | ||||
| .assignment-card:hover { | ||||
|     box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); | ||||
| } | ||||
|     .assignment-card:hover { | ||||
|         box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); | ||||
|     } | ||||
| 
 | ||||
| .top-content { | ||||
|     margin-bottom: 1rem; | ||||
|     word-break: break-word; | ||||
| } | ||||
|     .top-content { | ||||
|         margin-bottom: 1rem; | ||||
|         word-break: break-word; | ||||
|     } | ||||
| 
 | ||||
| .assignment-title { | ||||
|     font-weight: 700; | ||||
|     font-size: 1.4rem; | ||||
|     color: #0e6942; | ||||
|     margin-bottom: 0.3rem; | ||||
| } | ||||
|     .assignment-title { | ||||
|         font-weight: 700; | ||||
|         font-size: 1.4rem; | ||||
|         color: #0e6942; | ||||
|         margin-bottom: 0.3rem; | ||||
|     } | ||||
| 
 | ||||
| .assignment-class, | ||||
| .assignment-deadline { | ||||
|     font-size: 0.95rem; | ||||
|     color: #444; | ||||
|     margin-bottom: 0.2rem; | ||||
| } | ||||
|     .assignment-class, | ||||
|     .assignment-deadline { | ||||
|         font-size: 0.95rem; | ||||
|         color: #444; | ||||
|         margin-bottom: 0.2rem; | ||||
|     } | ||||
| 
 | ||||
| .class-name { | ||||
|     font-weight: 600; | ||||
|     color: #097180; | ||||
| } | ||||
|     .class-name { | ||||
|         font-weight: 600; | ||||
|         color: #097180; | ||||
|     } | ||||
| 
 | ||||
| .assignment-deadline.deadline-passed { | ||||
|     color: #d32f2f; | ||||
|     font-weight: bold; | ||||
| } | ||||
|     .assignment-deadline.deadline-passed { | ||||
|         color: #d32f2f; | ||||
|         font-weight: bold; | ||||
|     } | ||||
| 
 | ||||
| .assignment-deadline.deadline-in24hours { | ||||
|     color: #f57c00; | ||||
|     font-weight: bold; | ||||
| } | ||||
|     .assignment-deadline.deadline-in24hours { | ||||
|         color: #f57c00; | ||||
|         font-weight: bold; | ||||
|     } | ||||
| 
 | ||||
| .spacer { | ||||
|     flex: 1; | ||||
| } | ||||
|     .spacer { | ||||
|         flex: 1; | ||||
|     } | ||||
| 
 | ||||
| .button-row { | ||||
|     display: flex; | ||||
|     justify-content: flex-end; | ||||
|     gap: 0.75rem; | ||||
|     flex-wrap: wrap; | ||||
| } | ||||
|     .button-row { | ||||
|         display: flex; | ||||
|         justify-content: flex-end; | ||||
|         gap: 0.75rem; | ||||
|         flex-wrap: wrap; | ||||
|     } | ||||
| 
 | ||||
| .no-assignments { | ||||
|     text-align: center; | ||||
|     font-size: 1.2rem; | ||||
|     color: #777; | ||||
|     padding: 3rem 0; | ||||
| } | ||||
|     .no-assignments { | ||||
|         text-align: center; | ||||
|         font-size: 1.2rem; | ||||
|         color: #777; | ||||
|         padding: 3rem 0; | ||||
|     } | ||||
| </style> | ||||
|  |  | |||
		Reference in a new issue
	
	 Joyelle Ndagijimana
						Joyelle Ndagijimana