feat: edit assignment pagina aangemaakt
This commit is contained in:
		
							parent
							
								
									2c2aeca7ab
								
							
						
					
					
						commit
						4f09d004ab
					
				
					 11 changed files with 225 additions and 247 deletions
				
			
		|  | @ -41,6 +41,7 @@ | |||
|     }, | ||||
|     "read-more": "Mehr lesen", | ||||
|     "new-assignment": "Neue Aufgabe", | ||||
|     "edit-assignment": "Zuordnung bearbeiten", | ||||
|     "next": "nächste", | ||||
|     "previous": "vorherige", | ||||
|     "groups": "Gruppen", | ||||
|  |  | |||
|  | @ -41,6 +41,7 @@ | |||
|     }, | ||||
|     "read-more": "Read more", | ||||
|     "new-assignment": "New Assignment", | ||||
|     "edit-assignment": "Edit Assignment", | ||||
|     "next": "next", | ||||
|     "previous": "previous", | ||||
|     "groups": "Groups", | ||||
|  |  | |||
|  | @ -41,6 +41,7 @@ | |||
|     }, | ||||
|     "read-more": "En savoir plus", | ||||
|     "new-assignment": "Nouveau travail", | ||||
|     "edit-assignment": "Modifier le travail", | ||||
|     "next": "suivant", | ||||
|     "previous": "précédent", | ||||
|     "groups": "Groupes", | ||||
|  |  | |||
|  | @ -41,6 +41,7 @@ | |||
|     }, | ||||
|     "read-more": "Lees meer", | ||||
|     "new-assignment": "Nieuwe opdracht", | ||||
|     "edit-assignment": "Opdracht bewerken", | ||||
|     "next": "volgende", | ||||
|     "previous": "vorige", | ||||
|     "groups": "Groepen", | ||||
|  |  | |||
|  | @ -10,10 +10,11 @@ import CreateDiscussion from "@/views/discussions/CreateDiscussion.vue"; | |||
| import CallbackPage from "@/views/CallbackPage.vue"; | ||||
| import UserDiscussions from "@/views/discussions/UserDiscussions.vue"; | ||||
| import UserClasses from "@/views/classes/UserClasses.vue"; | ||||
| import UserAssignments from "@/views/classes/UserAssignments.vue"; | ||||
| import UserAssignments from "@/views/assignments/UserAssignments.vue"; | ||||
| import authState from "@/services/auth/auth-service.ts"; | ||||
| import UserHomePage from "@/views/homepage/UserHomePage.vue"; | ||||
| import SingleTheme from "@/views/SingleTheme.vue"; | ||||
| import EditAssignment from "@/views/assignments/EditAssignment.vue"; | ||||
| 
 | ||||
| const router = createRouter({ | ||||
|     history: createWebHistory(import.meta.env.BASE_URL), | ||||
|  | @ -85,6 +86,11 @@ const router = createRouter({ | |||
|                     name: "SingleAssigment", | ||||
|                     component: SingleAssignment, | ||||
|                 }, | ||||
|                 { | ||||
|                     path: ":id/edit", | ||||
|                     name: "EditAssignment", | ||||
|                     component: EditAssignment, | ||||
|                 }, | ||||
|             ] | ||||
|         }, | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										198
									
								
								frontend/src/views/assignments/AssignmentForm.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								frontend/src/views/assignments/AssignmentForm.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,198 @@ | |||
| <script setup lang="ts"> | ||||
| import {useI18n} from "vue-i18n"; | ||||
| import {computed, onMounted, ref, watch, defineProps, defineEmits} from "vue"; | ||||
| import GroupSelector from "@/components/GroupSelector.vue"; | ||||
| import DeadlineSelector from "@/components/DeadlineSelector.vue"; | ||||
| import { | ||||
|     assignmentTitleRules, | ||||
|     classesRules, | ||||
|     descriptionRules, | ||||
|     learningPathRules | ||||
| } from "@/utils/assignmentForm.ts"; | ||||
| 
 | ||||
| const {t, locale} = useI18n(); | ||||
| const emit = defineEmits(["submit"]); | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|     sort: {type: String}, | ||||
|     initialTitle: {type: String, default: ""}, | ||||
|     initialDeadline: {type: Date, default: null}, | ||||
|     initialDescription: {type: String, default: ""}, | ||||
|     initialLearningPath: {type: Object, default: null}, | ||||
|     initialClass: {type: Object, default: null}, | ||||
|     initialGroups: {type: Array, default: () => []} | ||||
| }); | ||||
| 
 | ||||
| const form = ref(); | ||||
| const language = ref(locale.value); | ||||
| const searchQuery = ref(""); | ||||
| const assignmentTitle = ref(props.initialTitle); | ||||
| const deadline = ref(props.initialDeadline); | ||||
| const description = ref(props.initialDescription); | ||||
| const selectedLearningPath = ref(props.initialLearningPath); | ||||
| const selectedClass = ref(props.initialClass); | ||||
| const groups = ref(props.initialGroups); | ||||
| const allLearningPaths = ref([]); | ||||
| const filteredLearningPaths = ref([]); | ||||
| const allClasses = ref([]); | ||||
| 
 | ||||
| const availableClass = computed(() => selectedClass.value); | ||||
| const allStudents = computed(() => selectedClass.value ? selectedClass.value.students : []); | ||||
| 
 | ||||
| const addGroupToList = (students: string[]) => { | ||||
|     if (students.length) { | ||||
|         groups.value = [...groups.value, students]; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| async function fetchAllLearningPaths() { | ||||
|     try { | ||||
|         //TODO: replace by function from controller | ||||
|         const response = await fetch(`http://localhost:3000/api/learningPath?language=${language.value}`); | ||||
|         if (!response.ok) throw new Error("Failed to fetch learning paths"); | ||||
|         const data = await response.json(); | ||||
|         allLearningPaths.value = data.map((lp: { hruid: string; title: string }) => ({ | ||||
|             hruid: lp.hruid, | ||||
|             name: lp.title | ||||
|         })); | ||||
|         filteredLearningPaths.value = [...allLearningPaths.value]; | ||||
|     } catch (error) { | ||||
|         console.error(error); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| watch(() => locale.value, fetchAllLearningPaths, {immediate: true}); | ||||
| watch(selectedClass, () => groups.value = []); | ||||
| 
 | ||||
| const searchResults = computed(() => filteredLearningPaths.value.filter((lp) => | ||||
|     lp.title.toLowerCase().includes(searchQuery.value.toLowerCase()) | ||||
| )); | ||||
| 
 | ||||
| onMounted(fetchAllLearningPaths); | ||||
| 
 | ||||
| const submitFormHandler = async () => { | ||||
|     const {valid} = await form.value.validate(); | ||||
|     if (!valid) return; | ||||
|     emit("submit", { | ||||
|         assignmentTitle: assignmentTitle.value, | ||||
|         selectedLearningPath: selectedLearningPath.value, | ||||
|         selectedClass: selectedClass.value, | ||||
|         groups: groups.value, | ||||
|         deadline: deadline.value, | ||||
|         description: description.value | ||||
|     }); | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <div class="main-container"> | ||||
|         <h1 class="title">{{ t(`${sort}-assignment`) }}</h1> | ||||
|         <v-card class="form-card"> | ||||
|             <v-form ref="form" class="form-container" validate-on="submit lazy" @submit.prevent="submitFormHandler"> | ||||
|                 <v-container class="step-container"> | ||||
|                     <v-card-text> | ||||
|                         <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> | ||||
|                         <v-combobox v-model="selectedLearningPath" | ||||
|                                     :items="searchResults" | ||||
|                                     item-title="name" | ||||
|                                     item-value="hruid" | ||||
|                                     :label="t('choose-lp')" | ||||
|                                     :rules="learningPathRules" | ||||
|                                     variant="outlined" | ||||
|                                     clearable | ||||
|                                     hide-details density="compact" | ||||
|                                     append-inner-icon="mdi-magnify" | ||||
|                                     required></v-combobox> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <v-combobox v-model="selectedClass" | ||||
|                                     :items="allClasses" | ||||
|                                     item-title="name" | ||||
|                                     item-value="id" | ||||
|                                     :label="t('pick-class')" | ||||
|                                     :rules="classesRules" | ||||
|                                     variant="outlined" | ||||
|                                     clearable | ||||
|                                     hide-details | ||||
|                                     density="compact" | ||||
|                                     append-inner-icon="mdi-magnify" | ||||
|                                     required></v-combobox> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <DeadlineSelector v-model:deadline="deadline"/> | ||||
| 
 | ||||
|                     <h3>{{ t('create-groups') }}</h3> | ||||
| 
 | ||||
|                     <GroupSelector :students="allStudents" | ||||
|                                    :availableClass="availableClass" | ||||
|                                    :groups="groups" | ||||
|                                    @groupCreated="addGroupToList"/> | ||||
| 
 | ||||
|                     <v-card-text v-if="groups.length"> | ||||
|                         <strong>Created Groups: {{ groups.length }}</strong> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <v-textarea v-model="description" :label="t('description')" variant="outlined" density="compact" | ||||
|                                     auto-grow rows="3" :rules="descriptionRules"></v-textarea> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-btn class="mt-2" color="secondary" type="submit" block>Submit</v-btn> | ||||
|                 </v-container> | ||||
|             </v-form> | ||||
|         </v-card> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| .main-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
| } | ||||
| 
 | ||||
| .title { | ||||
|     margin-bottom: 1%; | ||||
| } | ||||
| 
 | ||||
| .form-card { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     width: 55%; | ||||
| } | ||||
| 
 | ||||
| .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 { | ||||
|         width: 70%; | ||||
|         padding: 1%; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 650px) { | ||||
|     .form-card { | ||||
|         width: 95%; | ||||
|     } | ||||
| } | ||||
| </style> | ||||
|  | @ -1,241 +1,12 @@ | |||
| <script setup lang="ts"> | ||||
|     import {useI18n} from "vue-i18n"; | ||||
|     import {computed, onMounted, ref, watch} from "vue"; | ||||
|     import GroupSelector from "@/components/GroupSelector.vue"; | ||||
|     import {classes} from "@/utils/tempData.ts"; | ||||
|     import { | ||||
|         assignmentTitleRules, | ||||
|         classesRules, | ||||
|         descriptionRules, | ||||
|         learningPathRules, | ||||
|         submitForm | ||||
|     } from "@/utils/assignmentForm.ts"; | ||||
|     import DeadlineSelector from "@/components/DeadlineSelector.vue"; | ||||
|     import AssignmentForm from "@/views/assignments/AssignmentForm.vue"; | ||||
| 
 | ||||
|     const {t, locale} = useI18n(); | ||||
| 
 | ||||
|     const form = ref(); | ||||
| 
 | ||||
|     const language = ref(locale.value); | ||||
| 
 | ||||
|     const searchQuery = ref(''); | ||||
| 
 | ||||
|     const assignmentTitle = ref(''); | ||||
|     const deadline = ref(null); | ||||
|     const description = ref(''); | ||||
|     const allLearningPaths = ref([]); | ||||
|     const filteredLearningPaths = ref([]); | ||||
|     const selectedLearningPath = ref(null); | ||||
|     const allClasses = ref([...classes.map(cl => ({title: cl.displayName, value: cl.id}))]); | ||||
|     const selectedClass = ref(null); | ||||
|     const groups = ref<string[][]>([]); | ||||
| 
 | ||||
|     const availableClass = computed(() => { | ||||
|         //TODO: replace by real data | ||||
|         return classes.find(cl => selectedClass.value?.value === cl.id) || null; | ||||
|     }); | ||||
| 
 | ||||
|     const allStudents = computed(() => { | ||||
|         //TODO: replace by real data | ||||
|         if (!selectedClass.value) return []; | ||||
|         const cl = classes.find(c => c.id === selectedClass.value.value); | ||||
|         return cl ? cl.students.map(st => ({ | ||||
|             title: `${st.firstName} ${st.lastName}`, | ||||
|             value: st.username, | ||||
|             classes: cl | ||||
|         })) : []; | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     // New group is added to the list | ||||
|     const addGroupToList = (students: string[]) => { | ||||
|         if (students.length) { | ||||
|             groups.value = [...groups.value, students]; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     async function fetchAllLearningPaths() { | ||||
|         try { | ||||
|             //TODO: replace by function from controller | ||||
|             const response = await fetch(`http://localhost:3000/api/learningPath?language=${language.value}`); | ||||
|             if (!response.ok) throw new Error("Failed to fetch learning paths"); | ||||
|             const data = await response.json(); | ||||
|             allLearningPaths.value = data.map((lp: { hruid: string; title: string }) => ({ | ||||
|                 hruid: lp.hruid, | ||||
|                 title: lp.title | ||||
|             })); | ||||
|             filteredLearningPaths.value = [...allLearningPaths.value]; | ||||
|         } catch (error) { | ||||
|             console.error(error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     watch( | ||||
|         () => locale.value, | ||||
|         (newLocale) => { | ||||
|             if (!["nl", "en"].includes(newLocale)) { | ||||
|                 language.value = "en"; | ||||
|             } | ||||
|             fetchAllLearningPaths(); | ||||
|         }, | ||||
|         {immediate: true} | ||||
|     ); | ||||
| 
 | ||||
|     watch(selectedClass, () => { | ||||
|         groups.value = []; | ||||
|     }); | ||||
| 
 | ||||
|     const searchResults = computed(() => { | ||||
|         return filteredLearningPaths.value.filter((lp: { hruid: string; title: string }) => | ||||
|             lp.title.toLowerCase().includes(searchQuery.value.toLowerCase()) | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     onMounted(fetchAllLearningPaths); | ||||
| 
 | ||||
|     const submitFormHandler = async () => { | ||||
|         const { valid } = await form.value.validate(); | ||||
|         // Don't submit thr form if all rules don't apply | ||||
|         if (!valid) return; | ||||
|         submitForm(assignmentTitle.value, selectedLearningPath.value, selectedClass.value, groups.value, deadline.value, description.value); | ||||
|     }; | ||||
|     const sort = "new"; | ||||
| </script> | ||||
| 
 | ||||
| 
 | ||||
| <template> | ||||
|     <div class="main-container"> | ||||
|         <h1 class="title">{{ t("new-assignment") }}</h1> | ||||
|         <v-card class="form-card"> | ||||
|             <v-form ref="form" class="form-container" validate-on="submit lazy" @submit.prevent="submitFormHandler"> | ||||
|                 <v-container class="step-container"> | ||||
|                     <v-card-text> | ||||
|                         <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> | ||||
|                         <v-combobox | ||||
|                             v-model="selectedLearningPath" | ||||
|                             :items="searchResults" | ||||
|                             :label="t('choose-lp')" | ||||
|                             :rules="learningPathRules" | ||||
|                             variant="outlined" | ||||
|                             clearable | ||||
|                             hide-details | ||||
|                             density="compact" | ||||
|                             append-inner-icon="mdi-magnify" | ||||
|                             item-title="title" | ||||
|                             item-value="value" | ||||
|                             required | ||||
|                             :filter="(item, query: string) => item.title.toLowerCase().includes(query.toLowerCase())" | ||||
|                         ></v-combobox> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <v-combobox | ||||
|                             v-model="selectedClass" | ||||
|                             :items="allClasses" | ||||
|                             :label="t('pick-class')" | ||||
|                             :rules="classesRules" | ||||
|                             variant="outlined" | ||||
|                             clearable | ||||
|                             hide-details | ||||
|                             density="compact" | ||||
|                             append-inner-icon="mdi-magnify" | ||||
|                             item-title="title" | ||||
|                             item-value="value" | ||||
|                             required | ||||
|                         ></v-combobox> | ||||
| 
 | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <DeadlineSelector v-model:deadline="deadline" /> | ||||
| 
 | ||||
|                     <h3>{{ t('create-groups') }}</h3> | ||||
| 
 | ||||
|                     <GroupSelector | ||||
|                         :students="allStudents" | ||||
|                         :availableClass="availableClass" | ||||
|                         :groups="groups" | ||||
|                         @groupCreated="addGroupToList" | ||||
|                     /> | ||||
| 
 | ||||
|                     <!-- Counter for created groups --> | ||||
|                     <v-card-text v-if="groups.length"> | ||||
|                         <strong>Created Groups: {{ groups.length }}</strong> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
|                     <v-card-text> | ||||
|                         <v-textarea | ||||
|                             v-model="description" | ||||
|                             :label="t('description')" | ||||
|                             variant="outlined" | ||||
|                             density="compact" | ||||
|                             auto-grow | ||||
|                             rows="3" | ||||
|                             :rules="descriptionRules" | ||||
|                         ></v-textarea> | ||||
|                     </v-card-text> | ||||
| 
 | ||||
| 
 | ||||
|                     <v-btn class="mt-2" color="secondary" type="submit" block>Submit</v-btn> | ||||
|                 </v-container> | ||||
|             </v-form> | ||||
|         </v-card> | ||||
|     </div> | ||||
|     <AssignmentForm :sort="sort"></AssignmentForm> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| .main-container { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|     text-align: center; | ||||
| } | ||||
| 
 | ||||
| .title { | ||||
|     margin-bottom: 1%; | ||||
| } | ||||
| 
 | ||||
| .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; | ||||
| } | ||||
| 
 | ||||
| /* Responsive adjustments */ | ||||
| @media (max-width: 1000px) { | ||||
|     .form-card { | ||||
|         width: 70%; | ||||
|         padding: 1%; | ||||
|     } | ||||
| 
 | ||||
|     .step-container { | ||||
|         min-height: 300px; /* Gives enough space */ | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* Responsive adjustments */ | ||||
| @media (max-width: 650px) { | ||||
|     .form-card { | ||||
|         width: 95%; | ||||
|     } | ||||
| } | ||||
| </style> | ||||
|  |  | |||
							
								
								
									
										13
									
								
								frontend/src/views/assignments/EditAssignment.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/src/views/assignments/EditAssignment.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| <script setup lang="ts"> | ||||
| import AssignmentForm from "@/views/assignments/AssignmentForm.vue"; | ||||
| 
 | ||||
| const sort = "edit"; | ||||
| //TODO: use the id param to extract info about the assignment | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <AssignmentForm :sort="sort"></AssignmentForm> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| </style> | ||||
|  | @ -1,7 +0,0 @@ | |||
| <script setup lang="ts"></script> | ||||
| 
 | ||||
| <template> | ||||
|     <main></main> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped></style> | ||||
|  | @ -1,7 +0,0 @@ | |||
| <script setup lang="ts"></script> | ||||
| 
 | ||||
| <template> | ||||
|     <main></main> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped></style> | ||||
		Reference in a new issue
	
	 Joyelle Ndagijimana
						Joyelle Ndagijimana