feat(frontend): "Volgende" en "vorige"-knop toegevoegd aan leerpadpagina.
This commit is contained in:
		
							parent
							
								
									728b04c9d8
								
							
						
					
					
						commit
						4356a1ccd2
					
				
					 8 changed files with 112 additions and 22 deletions
				
			
		|  | @ -35,6 +35,7 @@ | |||
| 
 | ||||
| <style scoped> | ||||
|   .loading-div { | ||||
|       padding: 20px; | ||||
|       text-align: center; | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,4 +1,6 @@ | |||
| { | ||||
|     "welcome": "Willkommen", | ||||
|     "error_title": "Fehler" | ||||
|     "error_title": "Fehler", | ||||
|     "previous": "Zurück", | ||||
|     "next": "Weiter" | ||||
| } | ||||
|  |  | |||
|  | @ -6,5 +6,7 @@ | |||
|     "classes": "classes", | ||||
|     "discussions": "discussions", | ||||
|     "logout": "log out", | ||||
|     "error_title": "Error" | ||||
|     "error_title": "Error", | ||||
|     "previous": "Previous", | ||||
|     "next": "Next" | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,6 @@ | |||
| { | ||||
|     "welcome": "Bienvenue", | ||||
|     "error_title": "Erreur" | ||||
|     "error_title": "Erreur", | ||||
|     "previous": "Précédente", | ||||
|     "next": "Suivante" | ||||
| } | ||||
|  |  | |||
|  | @ -6,5 +6,7 @@ | |||
|     "classes": "klassen", | ||||
|     "discussions": "discussies", | ||||
|     "logout": "log uit", | ||||
|     "error_title": "Fout" | ||||
|     "error_title": "Fout", | ||||
|     "previous": "Vorige", | ||||
|     "next": "Volgende" | ||||
| } | ||||
|  |  | |||
|  | @ -3,16 +3,21 @@ import {LearningPath, type LearningPathDTO} from "@/services/learning-content/le | |||
| import type {Language} from "@/services/learning-content/language.ts"; | ||||
| import {single} from "@/utils/response-assertions.ts"; | ||||
| 
 | ||||
| const learningPathEndpoint = new GetEndpoint<{}, {search?: string, hruid?: string, language?: Language}, LearningPathDTO[]>( | ||||
|     "/learningPath" | ||||
| ); | ||||
| const learningPathEndpoint = new GetEndpoint< | ||||
|     {}, | ||||
|     {search?: string, hruid?: string, language?: Language, forGroup?: string, forStudent?: string}, | ||||
|     LearningPathDTO[] | ||||
| >("/learningPath"); | ||||
| 
 | ||||
| export async function searchLearningPaths(query: string): Promise<LearningPath[]> { | ||||
|     let dtos = await learningPathEndpoint.get({}, {search: query}) | ||||
|     return dtos.map(dto => LearningPath.fromDTO(dto)); | ||||
| } | ||||
| 
 | ||||
| export async function getLearningPath(hruid: string, language: Language): Promise<LearningPath> { | ||||
|     let dtos = await learningPathEndpoint.get({}, {hruid, language}); | ||||
| export async function getLearningPath(hruid: string, language: Language, options?: {forGroup?: string, forStudent?: string}): Promise<LearningPath> { | ||||
|     let dtos = await learningPathEndpoint.get( | ||||
|         {}, | ||||
|         {hruid, language, forGroup: options?.forGroup, forStudent: options?.forStudent} | ||||
|     ); | ||||
|     return LearningPath.fromDTO(single(dtos)); | ||||
| } | ||||
|  |  | |||
|  | @ -49,7 +49,8 @@ export class LearningPathNode { | |||
|         public readonly language: Language, | ||||
|         public readonly transitions: {next: LearningPathNode, default: boolean}[], | ||||
|         public readonly createdAt: Date, | ||||
|         public readonly updatedAt: Date | ||||
|         public readonly updatedAt: Date, | ||||
|         public readonly done: boolean = false | ||||
|     ) { | ||||
|     } | ||||
| 
 | ||||
|  | @ -80,6 +81,7 @@ export class LearningPathNode { | |||
|             }), | ||||
|             new Date(dto.created_at), | ||||
|             new Date(dto.updatedAt), | ||||
|             dto.done | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -2,20 +2,36 @@ | |||
|     import {Language} from "@/services/learning-content/language.ts"; | ||||
|     import {getLearningPath} from "@/services/learning-content/learning-path-service.ts"; | ||||
|     import UsingRemoteResource from "@/components/UsingRemoteResource.vue"; | ||||
|     import {type LearningPath} from "@/services/learning-content/learning-path.ts"; | ||||
|     import {computed, watch, watchEffect} from "vue"; | ||||
|     import {type LearningPath, LearningPathNode} from "@/services/learning-content/learning-path.ts"; | ||||
|     import {computed, type ComputedRef, watch} from "vue"; | ||||
|     import type {LearningObject} from "@/services/learning-content/learning-object.ts"; | ||||
|     import {useRouter} from "vue-router"; | ||||
|     import {useRoute, useRouter} from "vue-router"; | ||||
|     import {loadResource, remoteResource, type SuccessState} from "@/services/api-client/remote-resource.ts"; | ||||
|     import LearningObjectView from "@/views/learning-paths/LearningObjectView.vue"; | ||||
|     import {useI18n} from "vue-i18n"; | ||||
| 
 | ||||
|     const router = useRouter(); | ||||
|     const route = useRoute(); | ||||
|     const { t } = useI18n(); | ||||
| 
 | ||||
|     const props = defineProps<{hruid: string, language: Language, learningObjectHruid?: string}>() | ||||
| 
 | ||||
|     interface QueryParams { | ||||
|         forStudent?: string, | ||||
|         forGroup?: string | ||||
|     } | ||||
| 
 | ||||
|     const learningPathResource = remoteResource<LearningPath>(); | ||||
|     watchEffect(() => { | ||||
|         loadResource(learningPathResource, getLearningPath(props.hruid, props.language)); | ||||
|     }); | ||||
|     watch([() => props.hruid, () => props.language, () => route.query.forStudent, () => route.query.forGroup], () => { | ||||
|         loadResource( | ||||
|             learningPathResource, | ||||
|             getLearningPath( | ||||
|                 props.hruid, | ||||
|                 props.language, | ||||
|                 route.query as QueryParams | ||||
|             ) | ||||
|         ) | ||||
|     }, {immediate: true}); | ||||
| 
 | ||||
|     const learningObjectListResource = remoteResource<LearningObject[]>(); | ||||
|     watch(learningPathResource, () => { | ||||
|  | @ -24,12 +40,36 @@ | |||
|         } | ||||
|     }, {immediate: true}); | ||||
| 
 | ||||
|     const currentNode = computed(() => { | ||||
|         let currentHruid = props.learningObjectHruid; | ||||
|     const nodesList: ComputedRef<LearningPathNode[] | null> = computed(() => { | ||||
|         if (learningPathResource.state.type === "success") { | ||||
|             return learningPathResource.state.data.nodesAsList.filter(it => it.learningobjectHruid === currentHruid)[0] | ||||
|             return learningPathResource.state.data.nodesAsList; | ||||
|         } else { | ||||
|             return undefined; | ||||
|             return null; | ||||
|         } | ||||
|     }) | ||||
| 
 | ||||
|     const currentNode = computed(() => { | ||||
|         const currentHruid = props.learningObjectHruid; | ||||
|         if (nodesList.value) { | ||||
|             return nodesList.value.filter(it => it.learningobjectHruid === currentHruid)[0] | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     const nextNode = computed(() => { | ||||
|         if (!currentNode.value || !nodesList.value) | ||||
|             return; | ||||
|         const currentIndex = nodesList.value?.indexOf(currentNode.value); | ||||
|         if (currentIndex < nodesList.value?.length) { | ||||
|             return nodesList.value?.[currentIndex + 1]; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     const previousNode = computed(() => { | ||||
|         if (!currentNode.value || !nodesList.value) | ||||
|             return; | ||||
|         const currentIndex = nodesList.value?.indexOf(currentNode.value); | ||||
|         if (currentIndex < nodesList.value?.length) { | ||||
|             return nodesList.value?.[currentIndex - 1]; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|  | @ -41,6 +81,17 @@ | |||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     function isLearningObjectCompleted(learningObject: LearningObject): boolean { | ||||
|         if (learningPathResource.state.type === "success") { | ||||
|             return learningPathResource.state.data.nodesAsList.filter(it => | ||||
|                 it.learningobjectHruid === learningObject.key | ||||
|                 && it.version === learningObject.version | ||||
|                 && it.language == learningObject.language | ||||
|             )[0].done; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -62,9 +113,10 @@ | |||
|                 > | ||||
|                     <v-list-item | ||||
|                         link | ||||
|                         :to="node.key" | ||||
|                         :to="{path: node.key, query: route.query}" | ||||
|                         :title="node.title" | ||||
|                         :active="node.key === props.learningObjectHruid" | ||||
|                         :prepend-icon="isLearningObjectCompleted(node) ? 'mdi-checkbox-marked-circle-outline' : 'mdi-checkbox-blank-circle-outline'" | ||||
|                         v-for="node in learningObjects.data" | ||||
|                     > | ||||
|                         <template v-slot:append> | ||||
|  | @ -80,9 +132,31 @@ | |||
|             :version="currentNode.version" | ||||
|             v-if="currentNode" | ||||
|         ></learning-object-view> | ||||
|         <div class="navigation-buttons-container"> | ||||
|             <v-btn | ||||
|                 prepend-icon="mdi-chevron-left" | ||||
|                 variant="text" | ||||
|                 :disabled="!previousNode" | ||||
|                 :to="previousNode ? {path: previousNode.learningobjectHruid, query: route.query} : undefined" | ||||
|             > | ||||
|                 {{ t("previous") }} | ||||
|             </v-btn> | ||||
|             <v-btn | ||||
|                 append-icon="mdi-chevron-right" | ||||
|                 variant="text" | ||||
|                 :disabled="!nextNode" | ||||
|                 :to="nextNode ? {path: nextNode.learningobjectHruid, query: route.query} : undefined" | ||||
|             > | ||||
|                 {{ t("next") }} | ||||
|             </v-btn> | ||||
|         </div> | ||||
|     </using-remote-resource> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| 
 | ||||
|     .navigation-buttons-container { | ||||
|         padding: 20px; | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|     } | ||||
| </style> | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger