style: fix linting issues met Prettier
This commit is contained in:
		
							parent
							
								
									ef5c51b463
								
							
						
					
					
						commit
						11600b8be4
					
				
					 11 changed files with 578 additions and 541 deletions
				
			
		|  | @ -1,21 +1,21 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import {ref, computed, defineEmits} from "vue"; |     import { ref, computed, defineEmits } from "vue"; | ||||||
| import {deadlineRules} from "@/utils/assignment-rules.ts"; |     import { deadlineRules } from "@/utils/assignment-rules.ts"; | ||||||
| 
 | 
 | ||||||
| const date = ref(""); |     const date = ref(""); | ||||||
| const time = ref("23:59"); |     const time = ref("23:59"); | ||||||
| const emit = defineEmits(["update:deadline"]); |     const emit = defineEmits(["update:deadline"]); | ||||||
| 
 | 
 | ||||||
| const formattedDeadline = computed(() => { |     const formattedDeadline = computed(() => { | ||||||
|     if (!date.value || !time.value) return ""; |         if (!date.value || !time.value) return ""; | ||||||
|     return `${date.value} ${time.value}`; |         return `${date.value} ${time.value}`; | ||||||
| }); |     }); | ||||||
| 
 | 
 | ||||||
| function updateDeadline(): void { |     function updateDeadline(): void { | ||||||
|     if (date.value && time.value) { |         if (date.value && time.value) { | ||||||
|         emit("update:deadline", formattedDeadline.value); |             emit("update:deadline", formattedDeadline.value); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| }; |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -46,6 +46,4 @@ function updateDeadline(): void { | ||||||
|     </div> |     </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped></style> | ||||||
| 
 |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
|  | @ -1,51 +1,49 @@ | ||||||
| <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 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"; | ||||||
| import {useClassStudentsQuery} from "@/queries/classes.ts"; |     import { useClassStudentsQuery } from "@/queries/classes.ts"; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ |     const props = defineProps<{ | ||||||
|     classId: string | undefined |         classId: string | undefined; | ||||||
|     groups: string[][], |         groups: string[][]; | ||||||
| }>(); |     }>(); | ||||||
| const emit = defineEmits(['groupCreated']); |     const emit = defineEmits(["groupCreated"]); | ||||||
| const {t} = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
| const selectedStudents = ref([]); |     const selectedStudents = ref([]); | ||||||
| 
 | 
 | ||||||
| const studentQueryResult = useClassStudentsQuery(() => props.classId, true); |     const studentQueryResult = useClassStudentsQuery(() => props.classId, true); | ||||||
| 
 | 
 | ||||||
| function filterStudents(data: StudentsResponse): { title: string, value: string }[] { |     function filterStudents(data: StudentsResponse): { title: string; value: string }[] { | ||||||
|     const students = data.students; |         const students = data.students; | ||||||
|     const studentsInGroups = props.groups.flat(); |         const studentsInGroups = props.groups.flat(); | ||||||
| 
 | 
 | ||||||
|     return students |         return students | ||||||
|         ?.map(st => ({ |             ?.map((st) => ({ | ||||||
|             title: `${st.firstName} ${st.lastName}`, |                 title: `${st.firstName} ${st.lastName}`, | ||||||
|             value: st.username, |                 value: st.username, | ||||||
|         })) |             })) | ||||||
|         .filter(student => !studentsInGroups.includes(student.value)); |             .filter((student) => !studentsInGroups.includes(student.value)); | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| function createGroup(): void { |  | ||||||
|     if (selectedStudents.value.length) { |  | ||||||
|         // Extract only usernames (student.value) |  | ||||||
|         const usernames = selectedStudents.value.map(student => student.value); |  | ||||||
|         emit('groupCreated', usernames); |  | ||||||
|         selectedStudents.value = []; // Reset selection after creating group |  | ||||||
|     } |     } | ||||||
| }; |  | ||||||
| </script> |  | ||||||
| 
 | 
 | ||||||
|  |     function createGroup(): void { | ||||||
|  |         if (selectedStudents.value.length) { | ||||||
|  |             // Extract only usernames (student.value) | ||||||
|  |             const usernames = selectedStudents.value.map((student) => student.value); | ||||||
|  |             emit("groupCreated", usernames); | ||||||
|  |             selectedStudents.value = []; // Reset selection after creating group | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <using-query-result |     <using-query-result | ||||||
|         :query-result="studentQueryResult" |         :query-result="studentQueryResult" | ||||||
|         v-slot="{ data }: { data: StudentsResponse }" |         v-slot="{ data }: { data: StudentsResponse }" | ||||||
|     > |     > | ||||||
|         <h3>{{ t('create-groups') }}</h3> |         <h3>{{ t("create-groups") }}</h3> | ||||||
|         <v-card-text> |         <v-card-text> | ||||||
|             <v-combobox |             <v-combobox | ||||||
|                 v-model="selectedStudents" |                 v-model="selectedStudents" | ||||||
|  | @ -62,14 +60,16 @@ function createGroup(): void { | ||||||
|                 append-inner-icon="mdi-magnify" |                 append-inner-icon="mdi-magnify" | ||||||
|             ></v-combobox> |             ></v-combobox> | ||||||
| 
 | 
 | ||||||
|             <v-btn @click="createGroup" color="primary" class="mt-2" size="small"> |             <v-btn | ||||||
|                 {{ t('create-group') }} |                 @click="createGroup" | ||||||
|  |                 color="primary" | ||||||
|  |                 class="mt-2" | ||||||
|  |                 size="small" | ||||||
|  |             > | ||||||
|  |                 {{ t("create-group") }} | ||||||
|             </v-btn> |             </v-btn> | ||||||
|         </v-card-text> |         </v-card-text> | ||||||
|     </using-query-result> |     </using-query-result> | ||||||
| 
 |  | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped></style> | ||||||
| 
 |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import {type MaybeRefOrGetter, toValue} from "vue"; | import { type MaybeRefOrGetter, toValue } from "vue"; | ||||||
| import type {Language} from "@/data-objects/language.ts"; | import type { Language } from "@/data-objects/language.ts"; | ||||||
| import {useQuery, type UseQueryReturnType} from "@tanstack/vue-query"; | import { useQuery, type UseQueryReturnType } from "@tanstack/vue-query"; | ||||||
| import {getLearningPathController} from "@/controllers/controllers"; | import { getLearningPathController } from "@/controllers/controllers"; | ||||||
| import type {LearningPath} from "@/data-objects/learning-paths/learning-path.ts"; | import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | ||||||
| 
 | 
 | ||||||
| export const LEARNING_PATH_KEY = "learningPath"; | export const LEARNING_PATH_KEY = "learningPath"; | ||||||
| const learningPathController = getLearningPathController(); | const learningPathController = getLearningPathController(); | ||||||
|  | @ -47,7 +47,8 @@ export function useSearchLearningPathQuery( | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function useGetAllLearningPaths(language: MaybeRefOrGetter<string | undefined> | export function useGetAllLearningPaths( | ||||||
|  |     language: MaybeRefOrGetter<string | undefined>, | ||||||
| ): UseQueryReturnType<LearningPath[], Error> { | ): UseQueryReturnType<LearningPath[], Error> { | ||||||
|     return useQuery({ |     return useQuery({ | ||||||
|         queryKey: [LEARNING_PATH_KEY, "getAllLearningPaths", language], |         queryKey: [LEARNING_PATH_KEY, "getAllLearningPaths", language], | ||||||
|  | @ -55,6 +56,6 @@ export function useGetAllLearningPaths(language: MaybeRefOrGetter<string | undef | ||||||
|             const lang = toValue(language); |             const lang = toValue(language); | ||||||
|             return learningPathController.getAllLearningPaths(lang); |             return learningPathController.getAllLearningPaths(lang); | ||||||
|         }, |         }, | ||||||
|         enabled: () => Boolean(toValue(language)) |         enabled: () => Boolean(toValue(language)), | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
| import {computed, type Ref, toValue} from "vue"; | import { computed, type Ref, toValue } from "vue"; | ||||||
| import type { MaybeRefOrGetter } from "vue"; | import type { MaybeRefOrGetter } from "vue"; | ||||||
| import { | import { | ||||||
|     type QueryObserverResult, |     type QueryObserverResult, | ||||||
|     useMutation, |     useMutation, | ||||||
|     type UseMutationReturnType, useQueries, |     type UseMutationReturnType, | ||||||
|  |     useQueries, | ||||||
|     useQuery, |     useQuery, | ||||||
|     useQueryClient, |     useQueryClient, | ||||||
|     type UseQueryReturnType, |     type UseQueryReturnType, | ||||||
|  | @ -72,7 +73,7 @@ export function useStudentQuery( | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function useStudentsByUsernamesQuery( | export function useStudentsByUsernamesQuery( | ||||||
|     usernames: MaybeRefOrGetter<string[] | undefined> |     usernames: MaybeRefOrGetter<string[] | undefined>, | ||||||
| ): Ref<QueryObserverResult<StudentResponse>[]> { | ): Ref<QueryObserverResult<StudentResponse>[]> { | ||||||
|     const resolvedUsernames = toValue(usernames) ?? []; |     const resolvedUsernames = toValue(usernames) ?? []; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -73,7 +73,7 @@ const router = createRouter({ | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             path: "/assignment", |             path: "/assignment", | ||||||
|             meta: {requiresAuth: true}, |             meta: { requiresAuth: true }, | ||||||
|             children: [ |             children: [ | ||||||
|                 { |                 { | ||||||
|                     path: "create", |                     path: "create", | ||||||
|  | @ -85,7 +85,7 @@ const router = createRouter({ | ||||||
|                     name: "SingleAssigment", |                     name: "SingleAssigment", | ||||||
|                     component: SingleAssignment, |                     component: SingleAssignment, | ||||||
|                 }, |                 }, | ||||||
|             ] |             ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             path: "/class/:id", |             path: "/class/:id", | ||||||
|  |  | ||||||
|  | @ -5,8 +5,10 @@ | ||||||
|  */ |  */ | ||||||
| export const assignmentTitleRules = [ | export const assignmentTitleRules = [ | ||||||
|     (value: string): string | boolean => { |     (value: string): string | boolean => { | ||||||
|         if (value?.length >= 1) {return true;}  // Title must not be empty
 |         if (value?.length >= 1) { | ||||||
|         return 'Title cannot be empty.'; |             return true; | ||||||
|  |         } // Title must not be empty
 | ||||||
|  |         return "Title cannot be empty."; | ||||||
|     }, |     }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
|  | @ -18,9 +20,9 @@ export const assignmentTitleRules = [ | ||||||
| export const learningPathRules = [ | export const learningPathRules = [ | ||||||
|     (value: { hruid: string; title: string }): string | boolean => { |     (value: { hruid: string; title: string }): string | boolean => { | ||||||
|         if (value && value.hruid) { |         if (value && value.hruid) { | ||||||
|             return true;  // Valid if hruid is present
 |             return true; // Valid if hruid is present
 | ||||||
|         } |         } | ||||||
|         return 'You must select a learning path.'; |         return "You must select a learning path."; | ||||||
|     }, |     }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
|  | @ -31,8 +33,10 @@ export const learningPathRules = [ | ||||||
|  */ |  */ | ||||||
| export const classRules = [ | export const classRules = [ | ||||||
|     (value: string): string | boolean => { |     (value: string): string | boolean => { | ||||||
|         if (value) {return true;} |         if (value) { | ||||||
|         return 'You must select at least one class.'; |             return true; | ||||||
|  |         } | ||||||
|  |         return "You must select at least one class."; | ||||||
|     }, |     }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
|  | @ -43,14 +47,20 @@ export const classRules = [ | ||||||
|  */ |  */ | ||||||
| export const deadlineRules = [ | export const deadlineRules = [ | ||||||
|     (value: string): string | boolean => { |     (value: string): string | boolean => { | ||||||
|         if (!value) {return "You must set a deadline.";} |         if (!value) { | ||||||
|  |             return "You must set a deadline."; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         const selectedDateTime = new Date(value); |         const selectedDateTime = new Date(value); | ||||||
|         const now = new Date(); |         const now = new Date(); | ||||||
| 
 | 
 | ||||||
|         if (isNaN(selectedDateTime.getTime())) {return "Invalid date or time.";} |         if (isNaN(selectedDateTime.getTime())) { | ||||||
|  |             return "Invalid date or time."; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         if (selectedDateTime <= now) {return "The deadline must be in the future.";} |         if (selectedDateTime <= now) { | ||||||
|  |             return "The deadline must be in the future."; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return true; |         return true; | ||||||
|     }, |     }, | ||||||
|  | @ -58,7 +68,9 @@ export const deadlineRules = [ | ||||||
| 
 | 
 | ||||||
| export const descriptionRules = [ | export const descriptionRules = [ | ||||||
|     (value: string): string | boolean => { |     (value: string): string | boolean => { | ||||||
|         if (!value || value.trim() === "") {return "Description cannot be empty.";} |         if (!value || value.trim() === "") { | ||||||
|  |             return "Description cannot be empty."; | ||||||
|  |         } | ||||||
|         return true; |         return true; | ||||||
|     }, |     }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
|  | @ -1,101 +1,111 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import {useI18n} from "vue-i18n"; |     import { useI18n } from "vue-i18n"; | ||||||
| import {computed, onMounted, ref, watch} from "vue"; |     import { computed, onMounted, ref, watch } from "vue"; | ||||||
| import GroupSelector from "@/components/assignments/GroupSelector.vue"; |     import GroupSelector from "@/components/assignments/GroupSelector.vue"; | ||||||
| import {assignmentTitleRules, classRules, descriptionRules, learningPathRules} from "@/utils/assignment-rules.ts"; |     import { assignmentTitleRules, classRules, descriptionRules, learningPathRules } from "@/utils/assignment-rules.ts"; | ||||||
| import DeadlineSelector from "@/components/assignments/DeadlineSelector.vue"; |     import DeadlineSelector from "@/components/assignments/DeadlineSelector.vue"; | ||||||
| import auth from "@/services/auth/auth-service.ts"; |     import auth from "@/services/auth/auth-service.ts"; | ||||||
| import {useTeacherClassesQuery} from "@/queries/teachers.ts"; |     import { useTeacherClassesQuery } from "@/queries/teachers.ts"; | ||||||
| import {useRouter} from "vue-router"; |     import { useRouter } from "vue-router"; | ||||||
| import {useGetAllLearningPaths} from "@/queries/learning-paths.ts"; |     import { useGetAllLearningPaths } from "@/queries/learning-paths.ts"; | ||||||
| import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
| import type {LearningPath} from "@/data-objects/learning-paths/learning-path.ts"; |     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 {useCreateAssignmentMutation} from "@/queries/assignments.ts"; |     import { useCreateAssignmentMutation } from "@/queries/assignments.ts"; | ||||||
| import {useRoute} from "vue-router"; |     import { useRoute } from "vue-router"; | ||||||
| 
 | 
 | ||||||
|  |     const route = useRoute(); | ||||||
|  |     const router = useRouter(); | ||||||
|  |     const { t, locale } = useI18n(); | ||||||
|  |     const role = ref(auth.authState.activeRole); | ||||||
|  |     const username = ref<string>(""); | ||||||
| 
 | 
 | ||||||
| const route = useRoute(); |     onMounted(async () => { | ||||||
| const router = useRouter(); |         // Redirect student | ||||||
| const {t, locale} = useI18n(); |         if (role.value === "student") { | ||||||
| const role = ref(auth.authState.activeRole); |             await router.push("/user"); | ||||||
| const username = ref<string>(""); |         } | ||||||
| 
 | 
 | ||||||
| onMounted(async () => { |         // Get the user's username | ||||||
|     // Redirect student |         const user = await auth.loadUser(); | ||||||
|     if (role.value === 'student') { |         username.value = user?.profile?.preferred_username ?? ""; | ||||||
|         await router.push('/user'); |     }); | ||||||
|  | 
 | ||||||
|  |     const language = computed(() => locale.value); | ||||||
|  |     const form = ref(); | ||||||
|  | 
 | ||||||
|  |     //Fetch all learning paths | ||||||
|  |     const learningPathsQueryResults = useGetAllLearningPaths(language); | ||||||
|  | 
 | ||||||
|  |     // Fetch and store all the teacher's classes | ||||||
|  |     const classesQueryResults = useTeacherClassesQuery(username, true); | ||||||
|  | 
 | ||||||
|  |     const selectedClass = ref(undefined); | ||||||
|  | 
 | ||||||
|  |     const assignmentTitle = ref(""); | ||||||
|  |     const selectedLearningPath = ref(route.query.hruid || undefined); | ||||||
|  | 
 | ||||||
|  |     // Disable combobox when learningPath prop is passed | ||||||
|  |     const lpIsSelected = route.query.hruid !== undefined; | ||||||
|  |     const deadline = ref(null); | ||||||
|  |     const description = ref(""); | ||||||
|  |     const groups = ref<string[][]>([]); | ||||||
|  | 
 | ||||||
|  |     // New group is added to the list | ||||||
|  |     function addGroupToList(students: string[]): void { | ||||||
|  |         if (students.length) { | ||||||
|  |             groups.value = [...groups.value, students]; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Get the user's username |     watch(selectedClass, () => { | ||||||
|     const user = await auth.loadUser(); |         groups.value = []; | ||||||
|     username.value = user?.profile?.preferred_username ?? ""; |     }); | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
|  |     const { mutate, data, isSuccess } = useCreateAssignmentMutation(); | ||||||
| 
 | 
 | ||||||
| const language = computed(() => locale.value); |     async function submitFormHandler(): Promise<void> { | ||||||
| const form = ref(); |         const { valid } = await form.value.validate(); | ||||||
|  |         if (!valid) return; | ||||||
| 
 | 
 | ||||||
| //Fetch all learning paths |         const assignmentDTO: AssignmentDTO = { | ||||||
| const learningPathsQueryResults = useGetAllLearningPaths(language); |             id: 0, | ||||||
|  |             within: selectedClass.value?.id || "", | ||||||
|  |             title: assignmentTitle.value, | ||||||
|  |             description: description.value, | ||||||
|  |             learningPath: selectedLearningPath.value?.hruid || "", | ||||||
|  |             language: language.value, | ||||||
|  |             groups: groups.value, | ||||||
|  |         }; | ||||||
| 
 | 
 | ||||||
| // Fetch and store all the teacher's classes |         mutate({ cid: assignmentDTO.within, data: assignmentDTO }); | ||||||
| const classesQueryResults = useTeacherClassesQuery(username, true); |         if (isSuccess) | ||||||
| 
 |             await router.push(`/assignment/class/${data.value?.assignment.within}/${data.value?.assignment.id}`); | ||||||
| const selectedClass = ref(undefined); |  | ||||||
| 
 |  | ||||||
| const assignmentTitle = ref(''); |  | ||||||
| const selectedLearningPath = ref(route.query.hruid || undefined); |  | ||||||
| 
 |  | ||||||
| // Disable combobox when learningPath prop is passed |  | ||||||
| const lpIsSelected = route.query.hruid !== undefined; |  | ||||||
| const deadline = ref(null); |  | ||||||
| const description = ref(''); |  | ||||||
| const groups = ref<string[][]>([]); |  | ||||||
| 
 |  | ||||||
| // New group is added to the list |  | ||||||
| function addGroupToList(students: string[]): void { |  | ||||||
|     if (students.length) { |  | ||||||
|         groups.value = [...groups.value, students]; |  | ||||||
|     } |     } | ||||||
| } |  | ||||||
| 
 |  | ||||||
| watch(selectedClass, () => { |  | ||||||
|     groups.value = []; |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| const {mutate, data, isSuccess} = useCreateAssignmentMutation(); |  | ||||||
| 
 |  | ||||||
| async function submitFormHandler(): Promise<void> { |  | ||||||
|     const {valid} = await form.value.validate(); |  | ||||||
|     if (!valid) return; |  | ||||||
| 
 |  | ||||||
|     const assignmentDTO: AssignmentDTO = { |  | ||||||
|         id: 0, |  | ||||||
|         within: selectedClass.value?.id || "", |  | ||||||
|         title: assignmentTitle.value, |  | ||||||
|         description: description.value, |  | ||||||
|         learningPath: selectedLearningPath.value?.hruid || "", |  | ||||||
|         language: language.value, |  | ||||||
|         groups: groups.value |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     mutate({cid: assignmentDTO.within, data: assignmentDTO}); |  | ||||||
|     if (isSuccess) await router.push(`/assignment/class/${data.value?.assignment.within}/${data.value?.assignment.id}`); |  | ||||||
| } |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| <template> | <template> | ||||||
|     <div class="main-container"> |     <div class="main-container"> | ||||||
|         <h1 class="title">{{ t("new-assignment") }}</h1> |         <h1 class="title">{{ t("new-assignment") }}</h1> | ||||||
|         <v-card class="form-card"> |         <v-card class="form-card"> | ||||||
|             <v-form ref="form" class="form-container" validate-on="submit lazy" @submit.prevent="submitFormHandler"> |             <v-form | ||||||
|  |                 ref="form" | ||||||
|  |                 class="form-container" | ||||||
|  |                 validate-on="submit lazy" | ||||||
|  |                 @submit.prevent="submitFormHandler" | ||||||
|  |             > | ||||||
|                 <v-container class="step-container"> |                 <v-container class="step-container"> | ||||||
|                     <v-card-text> |                     <v-card-text> | ||||||
|                         <v-text-field v-model="assignmentTitle" :label="t('title')" :rules="assignmentTitleRules" |                         <v-text-field | ||||||
|                                       density="compact" variant="outlined" clearable required></v-text-field> |                             v-model="assignmentTitle" | ||||||
|  |                             :label="t('title')" | ||||||
|  |                             :rules="assignmentTitleRules" | ||||||
|  |                             density="compact" | ||||||
|  |                             variant="outlined" | ||||||
|  |                             clearable | ||||||
|  |                             required | ||||||
|  |                         ></v-text-field> | ||||||
|                     </v-card-text> |                     </v-card-text> | ||||||
| 
 | 
 | ||||||
|                     <using-query-result |                     <using-query-result | ||||||
|  | @ -117,14 +127,16 @@ async function submitFormHandler(): Promise<void> { | ||||||
|                                 item-value="hruid" |                                 item-value="hruid" | ||||||
|                                 required |                                 required | ||||||
|                                 :disabled="lpIsSelected" |                                 :disabled="lpIsSelected" | ||||||
|                                 :filter="(item, query: string) => item.title.toLowerCase().includes(query.toLowerCase())" |                                 :filter=" | ||||||
|  |                                     (item, query: string) => item.title.toLowerCase().includes(query.toLowerCase()) | ||||||
|  |                                 " | ||||||
|                             ></v-combobox> |                             ></v-combobox> | ||||||
|                         </v-card-text> |                         </v-card-text> | ||||||
|                     </using-query-result> |                     </using-query-result> | ||||||
| 
 | 
 | ||||||
|                     <using-query-result |                     <using-query-result | ||||||
|                         :query-result="classesQueryResults" |                         :query-result="classesQueryResults" | ||||||
|                         v-slot="{ data }: {data: ClassesResponse}" |                         v-slot="{ data }: { data: ClassesResponse }" | ||||||
|                     > |                     > | ||||||
|                         <v-card-text> |                         <v-card-text> | ||||||
|                             <v-combobox |                             <v-combobox | ||||||
|  | @ -154,7 +166,7 @@ async function submitFormHandler(): Promise<void> { | ||||||
|                     <v-card-text v-if="groups.length"> |                     <v-card-text v-if="groups.length"> | ||||||
|                         <strong>Created Groups: {{ groups.length }}</strong> |                         <strong>Created Groups: {{ groups.length }}</strong> | ||||||
|                     </v-card-text> |                     </v-card-text> | ||||||
|                     <DeadlineSelector v-model:deadline="deadline"/> |                     <DeadlineSelector v-model:deadline="deadline" /> | ||||||
|                     <v-card-text> |                     <v-card-text> | ||||||
|                         <v-textarea |                         <v-textarea | ||||||
|                             v-model="description" |                             v-model="description" | ||||||
|  | @ -167,11 +179,20 @@ async function submitFormHandler(): Promise<void> { | ||||||
|                         ></v-textarea> |                         ></v-textarea> | ||||||
|                     </v-card-text> |                     </v-card-text> | ||||||
|                     <v-card-text> |                     <v-card-text> | ||||||
|                         <v-btn class="mt-2" color="secondary" type="submit" block>{{ t("submit") }}</v-btn> |                         <v-btn | ||||||
|                         <v-btn to="/user/assignment" color="grey" block>{{ t("cancel") }}</v-btn> |                             class="mt-2" | ||||||
|  |                             color="secondary" | ||||||
|  |                             type="submit" | ||||||
|  |                             block | ||||||
|  |                             >{{ t("submit") }}</v-btn | ||||||
|  |                         > | ||||||
|  |                         <v-btn | ||||||
|  |                             to="/user/assignment" | ||||||
|  |                             color="grey" | ||||||
|  |                             block | ||||||
|  |                             >{{ t("cancel") }}</v-btn | ||||||
|  |                         > | ||||||
|                     </v-card-text> |                     </v-card-text> | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|                 </v-container> |                 </v-container> | ||||||
|             </v-form> |             </v-form> | ||||||
|         </v-card> |         </v-card> | ||||||
|  | @ -179,50 +200,50 @@ async function submitFormHandler(): Promise<void> { | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| .main-container { |     .main-container { | ||||||
|     display: flex; |         display: flex; | ||||||
|     flex-direction: column; |         flex-direction: column; | ||||||
|     align-items: center; |         align-items: center; | ||||||
|     justify-content: center; |         justify-content: center; | ||||||
|     text-align: center; |         text-align: center; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .form-card { |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
|     align-items: center; |  | ||||||
|     justify-content: center; |  | ||||||
|     width: 55%; |  | ||||||
|     /*padding: 1%;*/ |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .form-container { |  | ||||||
|     width: 100%; |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .step-container { |  | ||||||
|     display: flex; |  | ||||||
|     justify-content: center; |  | ||||||
|     flex-direction: column; |  | ||||||
|     min-height: 200px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @media (max-width: 1000px) { |  | ||||||
|     .form-card { |     .form-card { | ||||||
|         width: 70%; |         display: flex; | ||||||
|         padding: 1%; |         flex-direction: column; | ||||||
|  |         align-items: center; | ||||||
|  |         justify-content: center; | ||||||
|  |         width: 55%; | ||||||
|  |         /*padding: 1%;*/ | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .form-container { | ||||||
|  |         width: 100%; | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: column; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .step-container { |     .step-container { | ||||||
|         min-height: 300px; |         display: flex; | ||||||
|  |         justify-content: center; | ||||||
|  |         flex-direction: column; | ||||||
|  |         min-height: 200px; | ||||||
|     } |     } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| @media (max-width: 650px) { |     @media (max-width: 1000px) { | ||||||
|     .form-card { |         .form-card { | ||||||
|         width: 95%; |             width: 70%; | ||||||
|  |             padding: 1%; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .step-container { | ||||||
|  |             min-height: 300px; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @media (max-width: 650px) { | ||||||
|  |         .form-card { | ||||||
|  |             width: 95%; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } |  | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,60 +1,58 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|  |     import auth from "@/services/auth/auth-service.ts"; | ||||||
|  |     import { computed, type Ref, ref, watchEffect } from "vue"; | ||||||
|  |     import StudentAssignment from "@/views/assignments/StudentAssignment.vue"; | ||||||
|  |     import TeacherAssignment from "@/views/assignments/TeacherAssignment.vue"; | ||||||
|  |     import { useRoute } from "vue-router"; | ||||||
|  |     import type { Language } from "@/data-objects/language.ts"; | ||||||
|  |     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; | ||||||
|  |     import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | ||||||
|  |     import type { GroupDTO } from "@dwengo-1/common/interfaces/group"; | ||||||
| 
 | 
 | ||||||
| import auth from "@/services/auth/auth-service.ts"; |     const role = auth.authState.activeRole; | ||||||
| import {computed, type Ref, ref, watchEffect} from "vue"; |     const isTeacher = computed(() => role === "teacher"); | ||||||
| import StudentAssignment from "@/views/assignments/StudentAssignment.vue"; |  | ||||||
| import TeacherAssignment from "@/views/assignments/TeacherAssignment.vue"; |  | ||||||
| import {useRoute} from "vue-router"; |  | ||||||
| import type {Language} from "@/data-objects/language.ts"; |  | ||||||
| import {useGetLearningPathQuery} from "@/queries/learning-paths.ts"; |  | ||||||
| import type {LearningPath} from "@/data-objects/learning-paths/learning-path.ts"; |  | ||||||
| import type {GroupDTO} from "@dwengo-1/common/interfaces/group"; |  | ||||||
| 
 | 
 | ||||||
| const role = auth.authState.activeRole; |     const route = useRoute(); | ||||||
| const isTeacher = computed(() => role === 'teacher'); |     const classId = ref<string>(route.params.classId as string); | ||||||
|  |     const assignmentId = ref(Number(route.params.id)); | ||||||
| 
 | 
 | ||||||
| const route = useRoute(); |     function useGroupsWithProgress( | ||||||
| const classId = ref<string>(route.params.classId as string); |         groups: Ref<GroupDTO[]>, | ||||||
| const assignmentId = ref(Number(route.params.id)); |         hruid: Ref<string>, | ||||||
|  |         language: Ref<string>, | ||||||
|  |     ): { groupProgressMap: Map<number, number> } { | ||||||
|  |         const groupProgressMap: Map<number, number> = new Map<number, number>(); | ||||||
| 
 | 
 | ||||||
| function useGroupsWithProgress( |         watchEffect(() => { | ||||||
|     groups: Ref<GroupDTO[]>, |             // Clear existing entries to avoid stale data | ||||||
|     hruid: Ref<string>, |             groupProgressMap.clear(); | ||||||
|     language: Ref<string> |  | ||||||
| ): { groupProgressMap: Map<number, number> } { |  | ||||||
|     const groupProgressMap: Map<number, number> = new Map<number, number>(); |  | ||||||
| 
 | 
 | ||||||
|     watchEffect(() => { |             const lang = ref(language.value as Language); | ||||||
|         // Clear existing entries to avoid stale data |  | ||||||
|         groupProgressMap.clear(); |  | ||||||
| 
 | 
 | ||||||
|         const lang = ref(language.value as Language); |             groups.value.forEach((group) => { | ||||||
|  |                 const groupKey = group.groupNumber; | ||||||
|  |                 const forGroup = ref({ | ||||||
|  |                     forGroup: groupKey, | ||||||
|  |                     assignmentNo: assignmentId, | ||||||
|  |                     classId: classId, | ||||||
|  |                 }); | ||||||
| 
 | 
 | ||||||
|         groups.value.forEach((group) => { |                 const query = useGetLearningPathQuery(hruid.value, lang, forGroup); | ||||||
|             const groupKey = group.groupNumber; | 
 | ||||||
|             const forGroup = ref({ |                 const data = query.data.value; | ||||||
|                 forGroup: groupKey, | 
 | ||||||
|                 assignmentNo: assignmentId, |                 groupProgressMap.set(groupKey, data ? calculateProgress(data) : 0); | ||||||
|                 classId: classId, |  | ||||||
|             }); |             }); | ||||||
| 
 |  | ||||||
|             const query = useGetLearningPathQuery(hruid.value, lang, forGroup); |  | ||||||
| 
 |  | ||||||
|             const data = query.data.value; |  | ||||||
| 
 |  | ||||||
|             groupProgressMap.set(groupKey, data ? calculateProgress(data) : 0); |  | ||||||
|         }); |         }); | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     return { |         return { | ||||||
|         groupProgressMap, |             groupProgressMap, | ||||||
|     }; |         }; | ||||||
| } |     } | ||||||
| 
 |  | ||||||
| function calculateProgress(lp: LearningPath): number { |  | ||||||
|     return ((lp.amountOfNodes - lp.amountOfNodesLeft) / lp.amountOfNodes) * 100; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
|  |     function calculateProgress(lp: LearningPath): number { | ||||||
|  |         return ((lp.amountOfNodes - lp.amountOfNodesLeft) / lp.amountOfNodes) * 100; | ||||||
|  |     } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -74,6 +72,4 @@ function calculateProgress(lp: LearningPath): number { | ||||||
|     </StudentAssignment> |     </StudentAssignment> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped></style> | ||||||
| </style> |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,58 +1,56 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import {ref, computed, defineProps, type Ref} from "vue"; |     import { ref, computed, defineProps, type Ref } 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 { useAssignmentQuery } from "@/queries/assignments.ts"; | ||||||
| import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
| import type {AssignmentResponse} from "@/controllers/assignments.ts"; |     import type { AssignmentResponse } from "@/controllers/assignments.ts"; | ||||||
| import {asyncComputed} from "@vueuse/core"; |     import { asyncComputed } from "@vueuse/core"; | ||||||
| import {useStudentsByUsernamesQuery} from "@/queries/students.ts"; |     import { useStudentsByUsernamesQuery } from "@/queries/students.ts"; | ||||||
| import {useGroupsQuery} from "@/queries/groups.ts"; |     import { useGroupsQuery } from "@/queries/groups.ts"; | ||||||
| import {useGetLearningPathQuery} from "@/queries/learning-paths.ts"; |     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; | ||||||
| import type {Language} from "@/data-objects/language.ts"; |     import type { Language } from "@/data-objects/language.ts"; | ||||||
| import type {GroupDTO} from "@dwengo-1/common/interfaces/group"; |     import type { GroupDTO } from "@dwengo-1/common/interfaces/group"; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ |     const props = defineProps<{ | ||||||
|     classId: string |         classId: string; | ||||||
|     assignmentId: number, |         assignmentId: number; | ||||||
|     useGroupsWithProgress: ( |         useGroupsWithProgress: ( | ||||||
|         groups: Ref<GroupDTO[]>, |             groups: Ref<GroupDTO[]>, | ||||||
|         hruid: Ref<string>, |             hruid: Ref<string>, | ||||||
|         language: Ref<Language> |             language: Ref<Language>, | ||||||
|     ) => { groupProgressMap: Map<number, number> }; |         ) => { groupProgressMap: Map<number, number> }; | ||||||
| }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
| const {t, locale} = useI18n(); |     const { t, locale } = useI18n(); | ||||||
| const language = ref<Language>(locale.value as Language); |     const language = ref<Language>(locale.value as Language); | ||||||
| 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 assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId); | ||||||
| learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; |     learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; | ||||||
| 
 | 
 | ||||||
| const submitted = ref(false);//TODO: update by fetching submissions and check if group submitted |     const submitted = ref(false); //TODO: update by fetching submissions and check if group submitted | ||||||
| 
 | 
 | ||||||
| const lpQueryResult = useGetLearningPathQuery( |     const lpQueryResult = useGetLearningPathQuery( | ||||||
|     computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""), |         computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""), | ||||||
|     computed(() => language.value) |         computed(() => language.value), | ||||||
| ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); | ||||||
|  |     const group = computed(() => | ||||||
|  |         groupsQueryResult?.data.value?.groups.find((group) => | ||||||
|  |             group.members?.some((m) => m.username === username.value), | ||||||
|  |         ), | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
| const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); |     const _groupArray = computed(() => (group.value ? [group.value] : [])); | ||||||
| const group = computed(() => |     const progressValue = ref(0); | ||||||
|     groupsQueryResult?.data.value?.groups.find(group => |     /* Crashes right now cause api data has inexistent hruid TODO: uncomment later and use it in progress bar | ||||||
|         group.members?.some(m => m.username === username.value) |  | ||||||
|     ) |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| const _groupArray = computed(() => (group.value ? [group.value] : [])); |  | ||||||
| const progressValue = ref(0); |  | ||||||
| /* Crashes right now cause api data has inexistent hruid TODO: uncomment later and use it in progress bar |  | ||||||
| Const {groupProgressMap} = props.useGroupsWithProgress( | Const {groupProgressMap} = props.useGroupsWithProgress( | ||||||
|     groupArray, |     groupArray, | ||||||
|     learningPath, |     learningPath, | ||||||
|  | @ -60,19 +58,20 @@ Const {groupProgressMap} = props.useGroupsWithProgress( | ||||||
| ); | ); | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| 
 |     // Assuming group.value.members is a list of usernames TODO: case when it's StudentDTO's | ||||||
| // Assuming group.value.members is a list of usernames TODO: case when it's StudentDTO's |     const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as string[]); | ||||||
| const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as string[]); |  | ||||||
| 
 |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <div class="container"> |     <div class="container"> | ||||||
|         <using-query-result |         <using-query-result | ||||||
|             :query-result="assignmentQueryResult" |             :query-result="assignmentQueryResult" | ||||||
|             v-slot="{ data }: {data: AssignmentResponse}" |             v-slot="{ data }: { data: AssignmentResponse }" | ||||||
|         > |         > | ||||||
|             <v-card v-if="data" class="assignment-card"> |             <v-card | ||||||
|  |                 v-if="data" | ||||||
|  |                 class="assignment-card" | ||||||
|  |             > | ||||||
|                 <div class="top-buttons"> |                 <div class="top-buttons"> | ||||||
|                     <v-btn |                     <v-btn | ||||||
|                         icon |                         icon | ||||||
|  | @ -99,10 +98,11 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as | ||||||
|                         :query-result="lpQueryResult" |                         :query-result="lpQueryResult" | ||||||
|                         v-slot="{ data: lpData }" |                         v-slot="{ data: lpData }" | ||||||
|                     > |                     > | ||||||
|                         <v-btn v-if="lpData" |                         <v-btn | ||||||
|                                :to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}`" |                             v-if="lpData" | ||||||
|                                variant="tonal" |                             :to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}`" | ||||||
|                                color="primary" |                             variant="tonal" | ||||||
|  |                             color="primary" | ||||||
|                         > |                         > | ||||||
|                             {{ t("learning-path") }} |                             {{ t("learning-path") }} | ||||||
|                         </v-btn> |                         </v-btn> | ||||||
|  | @ -113,7 +113,10 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as | ||||||
|                     {{ data.assignment.description }} |                     {{ data.assignment.description }} | ||||||
|                 </v-card-text> |                 </v-card-text> | ||||||
|                 <v-card-text> |                 <v-card-text> | ||||||
|                     <v-row align="center" no-gutters> |                     <v-row | ||||||
|  |                         align="center" | ||||||
|  |                         no-gutters | ||||||
|  |                     > | ||||||
|                         <v-col cols="auto"> |                         <v-col cols="auto"> | ||||||
|                             <span class="progress-label">{{ t("progress") + ": " }}</span> |                             <span class="progress-label">{{ t("progress") + ": " }}</span> | ||||||
|                         </v-col> |                         </v-col> | ||||||
|  | @ -136,12 +139,14 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as | ||||||
|                     <h3>{{ t("group") }}</h3> |                     <h3>{{ t("group") }}</h3> | ||||||
|                     <div v-if="studentQueries"> |                     <div v-if="studentQueries"> | ||||||
|                         <ul> |                         <ul> | ||||||
|                             <li v-for="student in group?.members" :key="student.username"> |                             <li | ||||||
|                                 {{ student.firstName + ' ' + student.lastName }} |                                 v-for="student in group?.members" | ||||||
|  |                                 :key="student.username" | ||||||
|  |                             > | ||||||
|  |                                 {{ student.firstName + " " + student.lastName }} | ||||||
|                             </li> |                             </li> | ||||||
|                         </ul> |                         </ul> | ||||||
|                     </div> |                     </div> | ||||||
| 
 |  | ||||||
|                 </v-card-text> |                 </v-card-text> | ||||||
|             </v-card> |             </v-card> | ||||||
|         </using-query-result> |         </using-query-result> | ||||||
|  | @ -149,16 +154,14 @@ const studentQueries = useStudentsByUsernamesQuery(() => group.value?.members as | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| @import "@/assets/assignment.css"; |     @import "@/assets/assignment.css"; | ||||||
| 
 | 
 | ||||||
| .progress-label { |     .progress-label { | ||||||
|     font-weight: bold; |         font-weight: bold; | ||||||
|     margin-right: 5px; |         margin-right: 5px; | ||||||
| } |     } | ||||||
| 
 |  | ||||||
| .progress-bar { |  | ||||||
|     width: 40%; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
|  |     .progress-bar { | ||||||
|  |         width: 40%; | ||||||
|  |     } | ||||||
| </style> | </style> | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,43 +1,43 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import {computed, defineProps, type Ref, ref} from "vue"; |     import { computed, defineProps, type Ref, ref } from "vue"; | ||||||
| import {useI18n} from "vue-i18n"; |     import { useI18n } from "vue-i18n"; | ||||||
| import {useAssignmentQuery, useDeleteAssignmentMutation} from "@/queries/assignments.ts"; |     import { useAssignmentQuery, useDeleteAssignmentMutation } from "@/queries/assignments.ts"; | ||||||
| import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
| import {useGroupsQuery} from "@/queries/groups.ts"; |     import { useGroupsQuery } from "@/queries/groups.ts"; | ||||||
| import {useGetLearningPathQuery} from "@/queries/learning-paths.ts"; |     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; | ||||||
| import type {Language} from "@/data-objects/language.ts"; |     import type { Language } from "@/data-objects/language.ts"; | ||||||
| import router from "@/router"; |     import router from "@/router"; | ||||||
| import type {AssignmentResponse} from "@/controllers/assignments.ts"; |     import type { AssignmentResponse } from "@/controllers/assignments.ts"; | ||||||
| import type {GroupDTO} from "@dwengo-1/common/interfaces/group"; |     import type { GroupDTO } from "@dwengo-1/common/interfaces/group"; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ |     const props = defineProps<{ | ||||||
|     classId: string |         classId: string; | ||||||
|     assignmentId: number, |         assignmentId: number; | ||||||
|     useGroupsWithProgress: ( |         useGroupsWithProgress: ( | ||||||
|         groups: Ref<GroupDTO[]>, |             groups: Ref<GroupDTO[]>, | ||||||
|         hruid: Ref<string>, |             hruid: Ref<string>, | ||||||
|         language: Ref<Language> |             language: Ref<Language>, | ||||||
|     ) => { groupProgressMap: Map<number, number> }; |         ) => { groupProgressMap: Map<number, number> }; | ||||||
| }>(); |     }>(); | ||||||
| 
 | 
 | ||||||
| const {t, locale} = useI18n(); |     const { t, locale } = useI18n(); | ||||||
| const language = computed(() => locale.value); |     const language = computed(() => locale.value); | ||||||
| const groups = ref(); |     const groups = ref(); | ||||||
| const learningPath = ref(); |     const learningPath = ref(); | ||||||
| 
 | 
 | ||||||
| const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId); |     const assignmentQueryResult = useAssignmentQuery(() => props.classId, props.assignmentId); | ||||||
| learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; |     learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath; | ||||||
| // Get learning path object |     // Get learning path object | ||||||
| const lpQueryResult = useGetLearningPathQuery( |     const lpQueryResult = useGetLearningPathQuery( | ||||||
|     computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""), |         computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""), | ||||||
|     computed(() => language.value as Language) |         computed(() => language.value as Language), | ||||||
| ); |     ); | ||||||
| 
 | 
 | ||||||
| // Get all the groups withing the assignment |     // Get all the groups withing the assignment | ||||||
| const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); |     const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true); | ||||||
| groups.value = groupsQueryResult.data.value?.groups; |     groups.value = groupsQueryResult.data.value?.groups; | ||||||
| 
 | 
 | ||||||
| /* Crashes right now cause api data has inexistent hruid TODO: uncomment later and use it in progress bar |     /* Crashes right now cause api data has inexistent hruid TODO: uncomment later and use it in progress bar | ||||||
| Const {groupProgressMap} = props.useGroupsWithProgress( | Const {groupProgressMap} = props.useGroupsWithProgress( | ||||||
|     groups, |     groups, | ||||||
|     learningPath, |     learningPath, | ||||||
|  | @ -45,55 +45,54 @@ Const {groupProgressMap} = props.useGroupsWithProgress( | ||||||
| ); | ); | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
|  |     const allGroups = computed(() => { | ||||||
|  |         const groups = groupsQueryResult.data.value?.groups; | ||||||
|  |         if (!groups) return []; | ||||||
| 
 | 
 | ||||||
| const allGroups = computed(() => { |         return groups.map((group) => ({ | ||||||
|     const groups = groupsQueryResult.data.value?.groups; |             name: `${t("group")} ${group.groupNumber}`, | ||||||
|     if (!groups) return []; |             progress: 0, //GroupProgressMap[group.groupNumber], | ||||||
| 
 |             members: group.members, | ||||||
|     return groups.map(group => ({ |             submitted: false, //TODO: fetch from submission | ||||||
|         name: `${t('group')} ${group.groupNumber}`, |         })); | ||||||
|         progress: 0,//GroupProgressMap[group.groupNumber], |  | ||||||
|         members: group.members, |  | ||||||
|         submitted: false,//TODO: fetch from submission |  | ||||||
|     })); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| const dialog = ref(false); |  | ||||||
| const selectedGroup = ref({}); |  | ||||||
| 
 |  | ||||||
| function openGroupDetails(group): void { |  | ||||||
|     selectedGroup.value = group; |  | ||||||
|     dialog.value = true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const headers = computed(() => [ |  | ||||||
|     { title: t('group'), align: 'start', key: 'name' }, |  | ||||||
|     { title: t('progress'), align: 'center', key: 'progress' }, |  | ||||||
|     { title: t('submission'), align: 'center', key: 'submission' } |  | ||||||
| ]); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| const {mutate, isSuccess} = useDeleteAssignmentMutation(); |  | ||||||
| 
 |  | ||||||
| async function deleteAssignment(num: number, clsId: string): Promise<void> { |  | ||||||
|     mutate({ |  | ||||||
|         cid: clsId, |  | ||||||
|         an: num |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     if (isSuccess) await router.push("/user/assignments"); |     const dialog = ref(false); | ||||||
| } |     const selectedGroup = ref({}); | ||||||
| 
 | 
 | ||||||
|  |     function openGroupDetails(group): void { | ||||||
|  |         selectedGroup.value = group; | ||||||
|  |         dialog.value = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const headers = computed(() => [ | ||||||
|  |         { title: t("group"), align: "start", key: "name" }, | ||||||
|  |         { title: t("progress"), align: "center", key: "progress" }, | ||||||
|  |         { title: t("submission"), align: "center", key: "submission" }, | ||||||
|  |     ]); | ||||||
|  | 
 | ||||||
|  |     const { mutate, isSuccess } = useDeleteAssignmentMutation(); | ||||||
|  | 
 | ||||||
|  |     async function deleteAssignment(num: number, clsId: string): Promise<void> { | ||||||
|  |         mutate({ | ||||||
|  |             cid: clsId, | ||||||
|  |             an: num, | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if (isSuccess) await router.push("/user/assignments"); | ||||||
|  |     } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <div class="container"> |     <div class="container"> | ||||||
|         <using-query-result |         <using-query-result | ||||||
|             :query-result="assignmentQueryResult" |             :query-result="assignmentQueryResult" | ||||||
|             v-slot="{ data }: {data: AssignmentResponse}" |             v-slot="{ data }: { data: AssignmentResponse }" | ||||||
|         > |         > | ||||||
|             <v-card v-if="data" class="assignment-card"> |             <v-card | ||||||
|  |                 v-if="data" | ||||||
|  |                 class="assignment-card" | ||||||
|  |             > | ||||||
|                 <div class="top-buttons"> |                 <div class="top-buttons"> | ||||||
|                     <v-btn |                     <v-btn | ||||||
|                         icon |                         icon | ||||||
|  | @ -119,15 +118,15 @@ async function deleteAssignment(num: number, clsId: string): Promise<void> { | ||||||
|                         :query-result="lpQueryResult" |                         :query-result="lpQueryResult" | ||||||
|                         v-slot="{ data: lpData }" |                         v-slot="{ data: lpData }" | ||||||
|                     > |                     > | ||||||
|                         <v-btn v-if="lpData" |                         <v-btn | ||||||
|                                :to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}`" |                             v-if="lpData" | ||||||
|                                variant="tonal" |                             :to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}`" | ||||||
|                                color="primary" |                             variant="tonal" | ||||||
|  |                             color="primary" | ||||||
|                         > |                         > | ||||||
|                             {{ t("learning-path") }} |                             {{ t("learning-path") }} | ||||||
|                         </v-btn> |                         </v-btn> | ||||||
|                     </using-query-result> |                     </using-query-result> | ||||||
| 
 |  | ||||||
|                 </v-card-subtitle> |                 </v-card-subtitle> | ||||||
| 
 | 
 | ||||||
|                 <v-card-text class="description"> |                 <v-card-text class="description"> | ||||||
|  | @ -144,7 +143,11 @@ async function deleteAssignment(num: number, clsId: string): Promise<void> { | ||||||
|                             class="elevation-1" |                             class="elevation-1" | ||||||
|                         > |                         > | ||||||
|                             <template #[`item.name`]="{ item }"> |                             <template #[`item.name`]="{ item }"> | ||||||
|                                 <v-btn @click="openGroupDetails(item)" variant="text" color="primary"> |                                 <v-btn | ||||||
|  |                                     @click="openGroupDetails(item)" | ||||||
|  |                                     variant="text" | ||||||
|  |                                     color="primary" | ||||||
|  |                                 > | ||||||
|                                     {{ item.name }} |                                     {{ item.name }} | ||||||
|                                 </v-btn> |                                 </v-btn> | ||||||
|                             </template> |                             </template> | ||||||
|  | @ -168,17 +171,19 @@ async function deleteAssignment(num: number, clsId: string): Promise<void> { | ||||||
|                                     variant="text" |                                     variant="text" | ||||||
|                                     class="text-capitalize" |                                     class="text-capitalize" | ||||||
|                                 > |                                 > | ||||||
|                                     {{ item.submitted ? t('see-submission') : t('no-submission') }} |                                     {{ item.submitted ? t("see-submission") : t("no-submission") }} | ||||||
|                                 </v-btn> |                                 </v-btn> | ||||||
|                             </template> |                             </template> | ||||||
| 
 |  | ||||||
|                         </v-data-table> |                         </v-data-table> | ||||||
|                     </div> |                     </div> | ||||||
|                 </v-card-text> |                 </v-card-text> | ||||||
| 
 | 
 | ||||||
|                 <v-dialog v-model="dialog" max-width="50%"> |                 <v-dialog | ||||||
|  |                     v-model="dialog" | ||||||
|  |                     max-width="50%" | ||||||
|  |                 > | ||||||
|                     <v-card> |                     <v-card> | ||||||
|                         <v-card-title class="headline">{{t("members")}}</v-card-title> |                         <v-card-title class="headline">{{ t("members") }}</v-card-title> | ||||||
|                         <v-card-text> |                         <v-card-text> | ||||||
|                             <v-list> |                             <v-list> | ||||||
|                                 <v-list-item |                                 <v-list-item | ||||||
|  | @ -186,16 +191,19 @@ async function deleteAssignment(num: number, clsId: string): Promise<void> { | ||||||
|                                     :key="index" |                                     :key="index" | ||||||
|                                 > |                                 > | ||||||
|                                     <v-list-item-content> |                                     <v-list-item-content> | ||||||
|                                         <v-list-item-title>{{ |                                         <v-list-item-title | ||||||
|                                                 member.firstName + ' ' + member.lastName |                                             >{{ member.firstName + " " + member.lastName }} | ||||||
|                                             }} |  | ||||||
|                                         </v-list-item-title> |                                         </v-list-item-title> | ||||||
|                                     </v-list-item-content> |                                     </v-list-item-content> | ||||||
|                                 </v-list-item> |                                 </v-list-item> | ||||||
|                             </v-list> |                             </v-list> | ||||||
|                         </v-card-text> |                         </v-card-text> | ||||||
|                         <v-card-actions> |                         <v-card-actions> | ||||||
|                             <v-btn color="primary" @click="dialog = false">Close</v-btn> |                             <v-btn | ||||||
|  |                                 color="primary" | ||||||
|  |                                 @click="dialog = false" | ||||||
|  |                                 >Close</v-btn | ||||||
|  |                             > | ||||||
|                         </v-card-actions> |                         </v-card-actions> | ||||||
|                     </v-card> |                     </v-card> | ||||||
|                 </v-dialog> |                 </v-dialog> | ||||||
|  | @ -216,11 +224,10 @@ async function deleteAssignment(num: number, clsId: string): Promise<void> { | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| @import "@/assets/assignment.css"; |     @import "@/assets/assignment.css"; | ||||||
| 
 | 
 | ||||||
| .table-scroll { |     .table-scroll { | ||||||
|     overflow-x: auto; |         overflow-x: auto; | ||||||
|     -webkit-overflow-scrolling: touch; |         -webkit-overflow-scrolling: touch; | ||||||
| } |     } | ||||||
| </style> | </style> | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,87 +1,85 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import {ref, computed, onMounted} from 'vue'; |     import { ref, computed, onMounted } from "vue"; | ||||||
| import {useI18n} from 'vue-i18n'; |     import { useI18n } from "vue-i18n"; | ||||||
| import {useRouter} from 'vue-router'; |     import { useRouter } from "vue-router"; | ||||||
| import auth from "@/services/auth/auth-service.ts"; |     import auth from "@/services/auth/auth-service.ts"; | ||||||
| import {useTeacherClassesQuery} from "@/queries/teachers.ts"; |     import { useTeacherClassesQuery } from "@/queries/teachers.ts"; | ||||||
| import {useStudentClassesQuery} from "@/queries/students.ts"; |     import { useStudentClassesQuery } from "@/queries/students.ts"; | ||||||
| import {ClassController} from "@/controllers/classes.ts"; |     import { ClassController } from "@/controllers/classes.ts"; | ||||||
| import type {ClassDTO} from "@dwengo-1/common/interfaces/class"; |     import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; | ||||||
| import {asyncComputed} from "@vueuse/core"; |     import { asyncComputed } from "@vueuse/core"; | ||||||
| import {useDeleteAssignmentMutation} from "@/queries/assignments.ts"; |     import { useDeleteAssignmentMutation } from "@/queries/assignments.ts"; | ||||||
| 
 | 
 | ||||||
| const {t} = useI18n(); |     const { t } = useI18n(); | ||||||
| const router = useRouter(); |     const router = useRouter(); | ||||||
| 
 | 
 | ||||||
| const role = ref(auth.authState.activeRole); |     const role = ref(auth.authState.activeRole); | ||||||
| const username = ref<string>(""); |     const username = ref<string>(""); | ||||||
| 
 | 
 | ||||||
| const isTeacher = computed(() => role.value === 'teacher'); |     const isTeacher = computed(() => role.value === "teacher"); | ||||||
| 
 | 
 | ||||||
| // Fetch and store all the teacher's classes |     // Fetch and store all the teacher's classes | ||||||
| let classesQueryResults = undefined; |     let classesQueryResults = undefined; | ||||||
| 
 | 
 | ||||||
| if (isTeacher.value) { |     if (isTeacher.value) { | ||||||
|     classesQueryResults = useTeacherClassesQuery(username, true) |         classesQueryResults = useTeacherClassesQuery(username, true); | ||||||
| } else { |     } else { | ||||||
|     classesQueryResults = useStudentClassesQuery(username, true); |         classesQueryResults = useStudentClassesQuery(username, true); | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| //TODO: remove later |     //TODO: remove later | ||||||
| const classController = new ClassController(); |     const classController = new ClassController(); | ||||||
| 
 | 
 | ||||||
|  |     //TODO: replace by query that fetches all user's assignment | ||||||
|  |     const assignments = asyncComputed(async () => { | ||||||
|  |         const classes = classesQueryResults?.data?.value?.classes; | ||||||
|  |         if (!classes) return []; | ||||||
|  |         const result = await Promise.all( | ||||||
|  |             (classes as ClassDTO[]).map(async (cls) => { | ||||||
|  |                 const { assignments } = await classController.getAssignments(cls.id); | ||||||
|  |                 return assignments.map((a) => ({ | ||||||
|  |                     id: a.id, | ||||||
|  |                     class: cls, | ||||||
|  |                     title: a.title, | ||||||
|  |                     description: a.description, | ||||||
|  |                     learningPath: a.learningPath, | ||||||
|  |                     language: a.language, | ||||||
|  |                     groups: a.groups, | ||||||
|  |                 })); | ||||||
|  |             }), | ||||||
|  |         ); | ||||||
| 
 | 
 | ||||||
| //TODO: replace by query that fetches all user's assignment |         return result.flat(); | ||||||
| const assignments = asyncComputed(async () => { |     }, []); | ||||||
|     const classes = classesQueryResults?.data?.value?.classes; |  | ||||||
|     if (!classes) return []; |  | ||||||
|     const result = await Promise.all( |  | ||||||
|         (classes as ClassDTO[]).map(async (cls) => { |  | ||||||
|             const {assignments} = await classController.getAssignments(cls.id); |  | ||||||
|             return assignments.map(a => ({ |  | ||||||
|                 id: a.id, |  | ||||||
|                 class: cls, |  | ||||||
|                 title: a.title, |  | ||||||
|                 description: a.description, |  | ||||||
|                 learningPath: a.learningPath, |  | ||||||
|                 language: a.language, |  | ||||||
|                 groups: a.groups |  | ||||||
|             })); |  | ||||||
|         }) |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     return result.flat(); |     async function goToCreateAssignment(): Promise<void> { | ||||||
| }, []); |         await router.push("/assignment/create"); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     async function goToAssignmentDetails(id: number, clsId: string): Promise<void> { | ||||||
|  |         await router.push(`/assignment/${clsId}/${id}`); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| async function goToCreateAssignment(): Promise<void> { |     const { mutate, isSuccess } = useDeleteAssignmentMutation(); | ||||||
|     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, isSuccess} = useDeleteAssignmentMutation(); |         if (isSuccess) await router.push("/user/assignment"); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| async function goToDeleteAssignment(num: number, clsId: string): Promise<void> { |     onMounted(async () => { | ||||||
|     mutate({ |         const user = await auth.loadUser(); | ||||||
|         cid: clsId, |         username.value = user?.profile?.preferred_username ?? ""; | ||||||
|         an: num |  | ||||||
|     }); |     }); | ||||||
| 
 |  | ||||||
|     if (isSuccess) await router.push("/user/assignment"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| onMounted(async () => { |  | ||||||
|     const user = await auth.loadUser(); |  | ||||||
|     username.value = user?.profile?.preferred_username ?? ""; |  | ||||||
| }); |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <div class="assignments-container"> |     <div class="assignments-container"> | ||||||
|         <h1>{{ t('assignments') }}</h1> |         <h1>{{ t("assignments") }}</h1> | ||||||
| 
 | 
 | ||||||
|         <v-btn |         <v-btn | ||||||
|             v-if="isTeacher" |             v-if="isTeacher" | ||||||
|  | @ -89,7 +87,7 @@ onMounted(async () => { | ||||||
|             class="mb-4 center-btn" |             class="mb-4 center-btn" | ||||||
|             @click="goToCreateAssignment" |             @click="goToCreateAssignment" | ||||||
|         > |         > | ||||||
|             {{ t('new-assignment') }} |             {{ t("new-assignment") }} | ||||||
|         </v-btn> |         </v-btn> | ||||||
| 
 | 
 | ||||||
|         <v-container> |         <v-container> | ||||||
|  | @ -103,94 +101,94 @@ onMounted(async () => { | ||||||
|                         <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 | ||||||
|                                    variant="text" |                                 color="primary" | ||||||
|                                    @click="goToAssignmentDetails(assignment.id, assignment.class.id)"> |                                 variant="text" | ||||||
|                                 {{ t('view-assignment') }} |                                 @click="goToAssignmentDetails(assignment.id, assignment.class.id)" | ||||||
|  |                             > | ||||||
|  |                                 {{ t("view-assignment") }} | ||||||
|                             </v-btn> |                             </v-btn> | ||||||
|                             <v-btn v-if="isTeacher" color="red" |                             <v-btn | ||||||
|                                    variant="text" |                                 v-if="isTeacher" | ||||||
|                                    @click="goToDeleteAssignment(assignment.id, assignment.class.id)"> |                                 color="red" | ||||||
|                                 {{ t('delete') }} |                                 variant="text" | ||||||
|  |                                 @click="goToDeleteAssignment(assignment.id, assignment.class.id)" | ||||||
|  |                             > | ||||||
|  |                                 {{ t("delete") }} | ||||||
|                             </v-btn> |                             </v-btn> | ||||||
|                         </div> |                         </div> | ||||||
|                     </v-card> |                     </v-card> | ||||||
| 
 |  | ||||||
|                 </v-col> |                 </v-col> | ||||||
|             </v-row> |             </v-row> | ||||||
|         </v-container> |         </v-container> | ||||||
|     </div> |     </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| <style scoped> | <style scoped> | ||||||
| .assignments-container { |     .assignments-container { | ||||||
|     width: 100%; |         width: 100%; | ||||||
|     margin: 0 auto; |         margin: 0 auto; | ||||||
|     padding: 2% 4%; |         padding: 2% 4%; | ||||||
|     box-sizing: border-box; |         box-sizing: border-box; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
| .center-btn { |     .center-btn { | ||||||
|     display: block; |         display: block; | ||||||
|     margin-left: auto; |         margin-left: auto; | ||||||
|     margin-right: auto; |         margin-right: auto; | ||||||
| } |     } | ||||||
| 
 | 
 | ||||||
|  |     .assignment-card { | ||||||
|  |         padding: 1rem; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| .assignment-card { |     .card-content { | ||||||
|     padding: 1rem; |         display: flex; | ||||||
| } |         flex-direction: column; | ||||||
|  |         height: 100%; | ||||||
|  |         min-height: 150px; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| .card-content { |     .top-content { | ||||||
|     display: flex; |         margin-bottom: 1rem; | ||||||
|     flex-direction: column; |         word-break: break-word; | ||||||
|     height: 100%; |     } | ||||||
|     min-height: 150px; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| .top-content { |     .spacer { | ||||||
|     margin-bottom: 1rem; |         flex: 1; | ||||||
|     word-break: break-word; |     } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| .spacer { |     .button-row { | ||||||
|     flex: 1; |         display: flex; | ||||||
| } |         justify-content: flex-end; | ||||||
|  |         gap: 0.5rem; | ||||||
|  |         flex-wrap: wrap; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| .button-row { |     .assignment-title { | ||||||
|     display: flex; |         font-weight: bold; | ||||||
|     justify-content: flex-end; |         font-size: 1.5rem; | ||||||
|     gap: 0.5rem; |         margin-bottom: 0.1rem; | ||||||
|     flex-wrap: wrap; |         word-break: break-word; | ||||||
| } |     } | ||||||
| 
 |  | ||||||
| .assignment-title { |  | ||||||
|     font-weight: bold; |  | ||||||
|     font-size: 1.5rem; |  | ||||||
|     margin-bottom: 0.1rem; |  | ||||||
|     word-break: break-word; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .assignment-class { |  | ||||||
|     color: #666; |  | ||||||
|     font-size: 0.95rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .class-name { |  | ||||||
|     font-weight: 500; |  | ||||||
|     color: #333; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
|  |     .assignment-class { | ||||||
|  |         color: #666; | ||||||
|  |         font-size: 0.95rem; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     .class-name { | ||||||
|  |         font-weight: 500; | ||||||
|  |         color: #333; | ||||||
|  |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Lint Action
						Lint Action