chore(frontend): QuestionBox component
This commit is contained in:
		
							parent
							
								
									965ba5dd20
								
							
						
					
					
						commit
						bf8d331253
					
				
					 3 changed files with 203 additions and 197 deletions
				
			
		
							
								
								
									
										160
									
								
								frontend/src/components/QuestionBox.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								frontend/src/components/QuestionBox.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,160 @@ | |||
| <script setup lang="ts"> | ||||
| 
 | ||||
| import authService from '@/services/auth/auth-service.ts'; | ||||
| import { Language } from '@/data-objects/language.ts'; | ||||
| import { computed, type ComputedRef, ref } from 'vue'; | ||||
| import type { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; | ||||
| import { useStudentAssignmentsQuery, useStudentGroupsQuery } from '@/queries/students.ts'; | ||||
| import type { GroupDTO, GroupDTOId } from '@dwengo-1/common/interfaces/group'; | ||||
| import type { QuestionData } from '@dwengo-1/common/interfaces/question'; | ||||
| import type { LearningObjectIdentifierDTO } from '@dwengo-1/interfaces/learning-content'; | ||||
| import { useCreateQuestionMutation, useQuestionsQuery } from '@/queries/questions.ts'; | ||||
| import { LearningPathNode } from '@/data-objects/learning-paths/learning-path-node.ts'; | ||||
| import { useGetLearningPathQuery } from '@/queries/learning-paths.ts'; | ||||
| import { useLearningObjectListForPathQuery } from '@/queries/learning-objects.ts'; | ||||
| import { useI18n } from 'vue-i18n'; | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|     hruid: string; | ||||
|     language: Language; | ||||
|     learningObjectHruid?: string; | ||||
|     forGroup?: GroupDTOId | undefined | ||||
| }>(); | ||||
| 
 | ||||
| const { t } = useI18n(); | ||||
| 
 | ||||
| const studentAssignmentsQueryResult = useStudentAssignmentsQuery( | ||||
|     authService.authState.user?.profile.preferred_username, | ||||
| ); | ||||
| const learningPathQueryResult = useGetLearningPathQuery(props.hruid, props.language, props.forGroup); | ||||
| const learningObjectListQueryResult = useLearningObjectListForPathQuery(learningPathQueryResult.data); | ||||
| 
 | ||||
| const pathIsAssignment = computed(() => { | ||||
|     const assignments = (studentAssignmentsQueryResult.data.value?.assignments as AssignmentDTO[]) || []; | ||||
|     return assignments.some( | ||||
|         (assignment) => assignment.learningPath === props.hruid && assignment.language === props.language, | ||||
|     ); | ||||
| }); | ||||
| 
 | ||||
| const nodesList: ComputedRef<LearningPathNode[] | null> = computed( | ||||
|     () => learningPathQueryResult.data.value?.nodesAsList ?? null, | ||||
| ); | ||||
| 
 | ||||
| 
 | ||||
| const currentNode = computed(() => { | ||||
|     const currentHruid = props.learningObjectHruid; | ||||
|     return nodesList.value?.find((it) => it.learningobjectHruid === currentHruid); | ||||
| }); | ||||
| 
 | ||||
| const getQuestionsQuery = useQuestionsQuery( | ||||
|     computed( | ||||
|         () => | ||||
|             ({ | ||||
|                 language: currentNode.value?.language, | ||||
|                 hruid: currentNode.value?.learningobjectHruid, | ||||
|                 version: currentNode.value?.version, | ||||
|             }) as LearningObjectIdentifierDTO, | ||||
|     ), | ||||
| ); | ||||
| 
 | ||||
| const questionInput = ref(''); | ||||
| 
 | ||||
| const loID: LearningObjectIdentifierDTO = { | ||||
|     hruid: props.learningObjectHruid as string, | ||||
|     language: props.language, | ||||
| }; | ||||
| const createQuestionMutation = useCreateQuestionMutation(loID); | ||||
| const groupsQueryResult = useStudentGroupsQuery(authService.authState.user?.profile.preferred_username); | ||||
| 
 | ||||
| function submitQuestion(): void { | ||||
|     const assignments = studentAssignmentsQueryResult.data.value?.assignments as AssignmentDTO[]; | ||||
|     const assignment = assignments.find( | ||||
|         (assignment) => assignment.learningPath === props.hruid && assignment.language === props.language, | ||||
|     ); | ||||
|     const groups = groupsQueryResult.data.value?.groups as GroupDTO[]; | ||||
|     const group = groups?.find((group) => group.assignment === assignment?.id) as GroupDTO; | ||||
|     const questionData: QuestionData = { | ||||
|         author: authService.authState.user?.profile.preferred_username, | ||||
|         content: questionInput.value, | ||||
|         inGroup: group, | ||||
|     }; | ||||
|     if (questionInput.value !== '') { | ||||
|         createQuestionMutation.mutate(questionData, { | ||||
|             onSuccess: async () => { | ||||
|                 questionInput.value = ''; // Clear the input field after submission | ||||
|                 await getQuestionsQuery.refetch(); // Reload the questions | ||||
|             }, | ||||
|             onError: (_) => { | ||||
|                 // TODO Handle error | ||||
|                 // - console.error(e); | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <div | ||||
|         v-if="authService.authState.activeRole === 'student' && pathIsAssignment" | ||||
|         class="question-box" | ||||
|     > | ||||
|         <div class="input-wrapper"> | ||||
|             <input | ||||
|                 type="text" | ||||
|                 :placeholder="`${t(`question-input-placeholder`)}`" | ||||
|                 class="question-input" | ||||
|                 v-model="questionInput" | ||||
|             /> | ||||
|             <button | ||||
|                 @click="submitQuestion" | ||||
|                 class="send-button" | ||||
|             > | ||||
|                 ▶ | ||||
|             </button> | ||||
|         </div> | ||||
|     </div> | ||||
| 
 | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| .question-box { | ||||
|     width: 100%; | ||||
|     max-width: 400px; | ||||
|     margin: 20px auto; | ||||
|     font-family: sans-serif; | ||||
| } | ||||
| 
 | ||||
| .input-wrapper { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     border: 1px solid #ccc; | ||||
|     border-radius: 999px; | ||||
|     padding: 8px 12px; | ||||
|     box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | ||||
| } | ||||
| 
 | ||||
| .question-input { | ||||
|     flex: 1; | ||||
|     border: none; | ||||
|     outline: none; | ||||
|     font-size: 14px; | ||||
|     background-color: transparent; | ||||
| } | ||||
| 
 | ||||
| .question-input::placeholder { | ||||
|     color: #999; | ||||
| } | ||||
| 
 | ||||
| .send-button { | ||||
|     background: none; | ||||
|     border: none; | ||||
|     cursor: pointer; | ||||
|     font-size: 16px; | ||||
|     color: #555; | ||||
|     transition: color 0.2s ease; | ||||
| } | ||||
| 
 | ||||
| .send-button:hover { | ||||
|     color: #000; | ||||
| } | ||||
| </style> | ||||
|  | @ -7,17 +7,14 @@ import { useRoute } from 'vue-router'; | |||
| import { useGetAllLearningPaths, useGetLearningPathQuery } from '@/queries/learning-paths.ts'; | ||||
| import { useLearningObjectListForPathQuery } from '@/queries/learning-objects.ts'; | ||||
| import UsingQueryResult from '@/components/UsingQueryResult.vue'; | ||||
| import authService from '@/services/auth/auth-service.ts'; | ||||
| import { LearningPathNode } from '@/data-objects/learning-paths/learning-path-node.ts'; | ||||
| import { useCreateQuestionMutation, useQuestionsQuery } from '@/queries/questions'; | ||||
| import { useQuestionsQuery } from '@/queries/questions'; | ||||
| import type { QuestionsResponse } from '@/controllers/questions'; | ||||
| import type { LearningObjectIdentifierDTO } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import QandA from '@/components/QandA.vue'; | ||||
| import type { QuestionData, QuestionDTO } from '@dwengo-1/common/interfaces/question'; | ||||
| import { useStudentAssignmentsQuery, useStudentGroupsQuery } from '@/queries/students'; | ||||
| import type { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; | ||||
| import type { GroupDTO } from '@dwengo-1/common/interfaces/group'; | ||||
| import type { QuestionDTO } from '@dwengo-1/common/interfaces/question'; | ||||
| import DiscussionsSideBar from '@/components/DiscussionsSideBar.vue'; | ||||
| import QuestionBox from '@/components/QuestionBox.vue'; | ||||
| 
 | ||||
| const route = useRoute(); | ||||
| 
 | ||||
|  | @ -88,8 +85,6 @@ watch( | |||
| 
 | ||||
| const learningPathQueryResult = useGetLearningPathQuery(props.hruid, props.language, forGroup); | ||||
| 
 | ||||
| const learningObjectListQueryResult = useLearningObjectListForPathQuery(learningPathQueryResult.data); | ||||
| 
 | ||||
| const nodesList: ComputedRef<LearningPathNode[] | null> = computed( | ||||
|     () => learningPathQueryResult.data.value?.nodesAsList ?? null, | ||||
| ); | ||||
|  | @ -110,23 +105,6 @@ const getQuestionsQuery = useQuestionsQuery( | |||
|     ), | ||||
| ); | ||||
| 
 | ||||
| const studentAssignmentsQueryResult = useStudentAssignmentsQuery( | ||||
|     authService.authState.user?.profile.preferred_username, | ||||
| ); | ||||
| const pathIsAssignment = computed(() => { | ||||
|     const assignments = (studentAssignmentsQueryResult.data.value?.assignments as AssignmentDTO[]) || []; | ||||
|     return assignments.some( | ||||
|         (assignment) => assignment.learningPath === props.hruid && assignment.language === props.language, | ||||
|     ); | ||||
| }); | ||||
| const loID: ComputedRef<LearningObjectIdentifierDTO> = computed(() => ({ | ||||
|     hruid: props.learningObjectHruid as string, | ||||
|     language: props.language, | ||||
|     version: currentNode.value?.version, | ||||
| })); | ||||
| 
 | ||||
| const createQuestionMutation = useCreateQuestionMutation(loID.value); | ||||
| 
 | ||||
| watch( | ||||
|     () => [route.params.hruid, route.params.language, route.params.learningObjectHruid], | ||||
|     () => { | ||||
|  | @ -135,61 +113,16 @@ watch( | |||
|     }, | ||||
| ); | ||||
| 
 | ||||
| const groupsQueryResult = useStudentGroupsQuery(authService.authState.user?.profile.preferred_username); | ||||
| 
 | ||||
| const questionInput = ref(''); | ||||
| 
 | ||||
| function submitQuestion(): void { | ||||
|     const assignments = studentAssignmentsQueryResult.data.value?.assignments as AssignmentDTO[]; | ||||
|     const assignment = assignments.find( | ||||
|         (assignment) => assignment.learningPath === props.hruid && assignment.language === props.language, | ||||
|     ); | ||||
|     const groups = groupsQueryResult.data.value?.groups as GroupDTO[]; | ||||
|     const group = groups?.find((group) => group.assignment === assignment?.id) as GroupDTO; | ||||
|     const questionData: QuestionData = { | ||||
|         author: authService.authState.user?.profile.preferred_username, | ||||
|         content: questionInput.value, | ||||
|         inGroup: group, | ||||
|     }; | ||||
|     if (questionInput.value !== '') { | ||||
|         createQuestionMutation.mutate(questionData, { | ||||
|             onSuccess: async () => { | ||||
|                 questionInput.value = ''; // Clear the input field after submission | ||||
|                 await getQuestionsQuery.refetch(); // Reload the questions | ||||
|             }, | ||||
|             onError: (_) => { | ||||
|                 // TODO Handle error | ||||
|                 // - console.error(e); | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <DiscussionsSideBar></DiscussionsSideBar> | ||||
| 
 | ||||
|     <div | ||||
|         v-if="authService.authState.activeRole === 'student' && pathIsAssignment" | ||||
|         class="question-box" | ||||
|     > | ||||
|         <div class="input-wrapper"> | ||||
|             <input | ||||
|                 type="text" | ||||
|                 placeholder="question : ..." | ||||
|                 class="question-input" | ||||
|                 v-model="questionInput" | ||||
|             /> | ||||
|             <button | ||||
|                 @click="submitQuestion" | ||||
|                 class="send-button" | ||||
|             > | ||||
|                 ▶ | ||||
|             </button> | ||||
|         </div> | ||||
|     </div> | ||||
|     <QuestionBox | ||||
|         :hruid=props.hruid | ||||
|         :language=props.language | ||||
|         :learningObjectHruid=props.learningObjectHruid | ||||
|         :forGroup=forGroup | ||||
|     /> | ||||
|     <using-query-result | ||||
|         :query-result="getQuestionsQuery" | ||||
|         v-slot="questionsResponse: { data: QuestionsResponse }" | ||||
|  |  | |||
|  | @ -1,29 +1,29 @@ | |||
| <script setup lang="ts"> | ||||
|     import { Language } from "@/data-objects/language.ts"; | ||||
|     import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | ||||
|     import { computed, type ComputedRef, ref } from "vue"; | ||||
|     import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts"; | ||||
|     import { useRoute, useRouter } from "vue-router"; | ||||
|     import LearningObjectView from "@/views/learning-paths/learning-object/LearningObjectView.vue"; | ||||
|     import { useI18n } from "vue-i18n"; | ||||
|     import LearningPathSearchField from "@/components/LearningPathSearchField.vue"; | ||||
|     import { useGetLearningPathQuery } from "@/queries/learning-paths.ts"; | ||||
|     import { useLearningObjectListForPathQuery } from "@/queries/learning-objects.ts"; | ||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||
|     import authService from "@/services/auth/auth-service.ts"; | ||||
|     import { LearningPathNode } from "@/data-objects/learning-paths/learning-path-node.ts"; | ||||
|     import LearningPathGroupSelector from "@/views/learning-paths/LearningPathGroupSelector.vue"; | ||||
|     import { useCreateQuestionMutation, useQuestionsQuery } from "@/queries/questions"; | ||||
|     import type { QuestionsResponse } from "@/controllers/questions"; | ||||
|     import type { LearningObjectIdentifierDTO } from "@dwengo-1/common/interfaces/learning-content"; | ||||
|     import QandA from "@/components/QandA.vue"; | ||||
|     import type { QuestionData, QuestionDTO } from "@dwengo-1/common/interfaces/question"; | ||||
|     import { useStudentAssignmentsQuery, useStudentGroupsQuery } from "@/queries/students"; | ||||
|     import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; | ||||
|     import type { GroupDTO } from "@dwengo-1/common/interfaces/group"; | ||||
|     import QuestionNotification from "@/components/QuestionNotification.vue"; | ||||
| import { Language } from '@/data-objects/language.ts'; | ||||
| import type { LearningPath } from '@/data-objects/learning-paths/learning-path.ts'; | ||||
| import { computed, type ComputedRef, ref } from 'vue'; | ||||
| import type { LearningObject } from '@/data-objects/learning-objects/learning-object.ts'; | ||||
| import { useRoute, useRouter } from 'vue-router'; | ||||
| import LearningObjectView from '@/views/learning-paths/learning-object/LearningObjectView.vue'; | ||||
| import { useI18n } from 'vue-i18n'; | ||||
| import LearningPathSearchField from '@/components/LearningPathSearchField.vue'; | ||||
| import { useGetLearningPathQuery } from '@/queries/learning-paths.ts'; | ||||
| import { useLearningObjectListForPathQuery } from '@/queries/learning-objects.ts'; | ||||
| import UsingQueryResult from '@/components/UsingQueryResult.vue'; | ||||
| import authService from '@/services/auth/auth-service.ts'; | ||||
| import { LearningPathNode } from '@/data-objects/learning-paths/learning-path-node.ts'; | ||||
| import LearningPathGroupSelector from '@/views/learning-paths/LearningPathGroupSelector.vue'; | ||||
| import { useQuestionsQuery } from '@/queries/questions'; | ||||
| import type { QuestionsResponse } from '@/controllers/questions'; | ||||
| import type { LearningObjectIdentifierDTO } from '@dwengo-1/common/interfaces/learning-content'; | ||||
| import QandA from '@/components/QandA.vue'; | ||||
| import type { QuestionDTO } from '@dwengo-1/common/interfaces/question'; | ||||
| import { useStudentAssignmentsQuery } from '@/queries/students'; | ||||
| import type { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; | ||||
| import QuestionNotification from '@/components/QuestionNotification.vue'; | ||||
| import QuestionBox from '@/components/QuestionBox.vue'; | ||||
| 
 | ||||
|     const router = useRouter(); | ||||
| const router = useRouter(); | ||||
|     const route = useRoute(); | ||||
|     const { t } = useI18n(); | ||||
| 
 | ||||
|  | @ -156,41 +156,6 @@ | |||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     const loID: LearningObjectIdentifierDTO = { | ||||
|         hruid: props.learningObjectHruid as string, | ||||
|         language: props.language, | ||||
|     }; | ||||
|     const createQuestionMutation = useCreateQuestionMutation(loID); | ||||
|     const groupsQueryResult = useStudentGroupsQuery(authService.authState.user?.profile.preferred_username); | ||||
| 
 | ||||
|     const questionInput = ref(""); | ||||
| 
 | ||||
|     function submitQuestion(): void { | ||||
|         const assignments = studentAssignmentsQueryResult.data.value?.assignments as AssignmentDTO[]; | ||||
|         const assignment = assignments.find( | ||||
|             (assignment) => assignment.learningPath === props.hruid && assignment.language === props.language, | ||||
|         ); | ||||
|         const groups = groupsQueryResult.data.value?.groups as GroupDTO[]; | ||||
|         const group = groups?.find((group) => group.assignment === assignment?.id) as GroupDTO; | ||||
|         const questionData: QuestionData = { | ||||
|             author: authService.authState.user?.profile.preferred_username, | ||||
|             content: questionInput.value, | ||||
|             inGroup: group, //TODO: POST response zegt dat dit null is??? | ||||
|         }; | ||||
|         if (questionInput.value !== "") { | ||||
|             createQuestionMutation.mutate(questionData, { | ||||
|                 onSuccess: async () => { | ||||
|                     questionInput.value = ""; // Clear the input field after submission | ||||
|                     await getQuestionsQuery.refetch(); // Reload the questions | ||||
|                 }, | ||||
|                 onError: (_) => { | ||||
|                     // TODO Handle error | ||||
|                     // - console.error(e); | ||||
|                 }, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const discussionLink = computed(() => | ||||
|         "/discussion" | ||||
|         + "/" + props.hruid | ||||
|  | @ -329,25 +294,12 @@ | |||
|                 v-if="currentNode" | ||||
|             ></learning-object-view> | ||||
|         </div> | ||||
|         <div | ||||
|             v-if="authService.authState.activeRole === 'student' && pathIsAssignment" | ||||
|             class="question-box" | ||||
|         > | ||||
|             <div class="input-wrapper"> | ||||
|                 <input | ||||
|                     type="text" | ||||
|                     :placeholder="t('question-input-placeholder')" | ||||
|                     class="question-input" | ||||
|                     v-model="questionInput" | ||||
|                 /> | ||||
|                 <button | ||||
|                     @click="submitQuestion" | ||||
|                     class="send-button" | ||||
|                 > | ||||
|                     ▶ | ||||
|                 </button> | ||||
|             </div> | ||||
|         </div> | ||||
|         <QuestionBox | ||||
|             :hruid=props.hruid | ||||
|             :language=props.language | ||||
|             :learningObjectHruid=props.learningObjectHruid | ||||
|             :forGroup=forGroup | ||||
|         /> | ||||
|         <div class="navigation-buttons-container"> | ||||
|             <v-btn | ||||
|                 prepend-icon="mdi-chevron-left" | ||||
|  | @ -441,45 +393,6 @@ | |||
|         text-transform: uppercase; | ||||
|         z-index: 2; /* Less than modals/popups */ | ||||
|     } | ||||
|     .question-box { | ||||
|         width: 100%; | ||||
|         max-width: 400px; | ||||
|         margin: 20px auto; | ||||
|         font-family: sans-serif; | ||||
|     } | ||||
|     .input-wrapper { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         border: 1px solid #ccc; | ||||
|         border-radius: 999px; | ||||
|         padding: 8px 12px; | ||||
|         box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | ||||
|     } | ||||
| 
 | ||||
|     .question-input { | ||||
|         flex: 1; | ||||
|         border: none; | ||||
|         outline: none; | ||||
|         font-size: 14px; | ||||
|         background-color: transparent; | ||||
|     } | ||||
| 
 | ||||
|     .question-input::placeholder { | ||||
|         color: #999; | ||||
|     } | ||||
| 
 | ||||
|     .send-button { | ||||
|         background: none; | ||||
|         border: none; | ||||
|         cursor: pointer; | ||||
|         font-size: 16px; | ||||
|         color: #555; | ||||
|         transition: color 0.2s ease; | ||||
|     } | ||||
| 
 | ||||
|     .send-button:hover { | ||||
|         color: #000; | ||||
|     } | ||||
| 
 | ||||
|     .discussion-link { | ||||
|         margin-top: 8px; | ||||
|  |  | |||
		Reference in a new issue