fix: UI-imperfecties & diverse bugs omtrent het verwijderen en editeren van leerpaden opgelost
This commit is contained in:
		
							parent
							
								
									9400b7f33c
								
							
						
					
					
						commit
						9a58126c7c
					
				
					 6 changed files with 126 additions and 28 deletions
				
			
		|  | @ -4,7 +4,6 @@ import { Language } from '@dwengo-1/common/util/language'; | |||
| import { LearningPathNode } from '../../entities/content/learning-path-node.entity.js'; | ||||
| import { RequiredEntityData } from '@mikro-orm/core'; | ||||
| import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity.js'; | ||||
| import { EntityAlreadyExistsException } from '../../exceptions/entity-already-exists-exception.js'; | ||||
| 
 | ||||
| export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | ||||
|     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { | ||||
|  | @ -53,21 +52,6 @@ export class LearningPathRepository extends DwengoEntityRepository<LearningPath> | |||
|         return this.em.create(LearningPathTransition, transitionData); | ||||
|     } | ||||
| 
 | ||||
|     public async saveLearningPathNodesAndTransitions( | ||||
|         path: LearningPath, | ||||
|         nodes: LearningPathNode[], | ||||
|         transitions: LearningPathTransition[], | ||||
|         options?: { preventOverwrite?: boolean } | ||||
|     ): Promise<void> { | ||||
|         if (options?.preventOverwrite && (await this.findOne(path))) { | ||||
|             throw new EntityAlreadyExistsException('A learning path with this hruid/language combination already exists.'); | ||||
|         } | ||||
|         const em = this.getEntityManager(); | ||||
|         await em.persistAndFlush(path); | ||||
|         await Promise.all(nodes.map(async (it) => em.persistAndFlush(it))); | ||||
|         await Promise.all(transitions.map(async (it) => em.persistAndFlush(it))); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Deletes the learning path with the given hruid and language. | ||||
|      * @returns the deleted learning path or null if it was not found. | ||||
|  |  | |||
|  | @ -134,8 +134,14 @@ const learningPathService = { | |||
|      */ | ||||
|     async createNewLearningPath(dto: LearningPath, admins: TeacherDTO[]): Promise<LearningPathEntity> { | ||||
|         const repo = getLearningPathRepository(); | ||||
| 
 | ||||
|         const path = mapToLearningPath(dto, admins); | ||||
|         await repo.save(path, { preventOverwrite: true }); | ||||
|         try { | ||||
|             await repo.save(path, { preventOverwrite: true }); | ||||
|         } catch (e: unknown) { | ||||
|             repo.getEntityManager().clear(); | ||||
|             throw e; | ||||
|         } | ||||
|         return path; | ||||
|     }, | ||||
| 
 | ||||
|  | @ -146,6 +152,7 @@ const learningPathService = { | |||
|      */ | ||||
|     async deleteLearningPath(id: LearningPathIdentifier): Promise<LearningPathEntity> { | ||||
|         const repo = getLearningPathRepository(); | ||||
| 
 | ||||
|         const deletedPath = await repo.deleteByHruidAndLanguage(id.hruid, id.language); | ||||
|         if (deletedPath) { | ||||
|             return deletedPath; | ||||
|  |  | |||
|  | @ -0,0 +1,61 @@ | |||
| <script setup lang="ts"> | ||||
|     import { useI18n } from 'vue-i18n'; | ||||
| 
 | ||||
|     const props = defineProps<{ | ||||
|         text: string, | ||||
|         prependIcon?: string, | ||||
|         appendIcon?: string, | ||||
|         confirmQueryText: string, | ||||
|         variant?: "flat" | "text" | "elevated" | "tonal" | "outlined" | "plain" | undefined, | ||||
|         color?: string, | ||||
|         disabled?: boolean | ||||
|     }>(); | ||||
| 
 | ||||
|     const emit = defineEmits<{ (e: 'confirm'): void }>() | ||||
| 
 | ||||
|     const { t } = useI18n(); | ||||
| 
 | ||||
|     function confirm(): void { | ||||
|         emit("confirm"); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <v-dialog max-width="500"> | ||||
|   <template v-slot:activator="{ props: activatorProps }"> | ||||
|     <v-btn | ||||
|       v-bind="activatorProps" | ||||
|       :text="props.text" | ||||
|       :prependIcon="props.prependIcon" | ||||
|       :appendIcon="props.appendIcon" | ||||
|       :variant="props.variant" | ||||
|       :color="color" | ||||
|       :disabled="props.disabled" | ||||
|     ></v-btn> | ||||
|   </template> | ||||
| 
 | ||||
|   <template v-slot:default="{ isActive }"> | ||||
|     <v-card :title="t('confirmDialogTitle')"> | ||||
|       <v-card-text> | ||||
|         {{ props.confirmQueryText }} | ||||
|       </v-card-text> | ||||
| 
 | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
| 
 | ||||
|         <v-btn | ||||
|           :text="t('yes')" | ||||
|           @click="confirm(); isActive.value = false" | ||||
|         ></v-btn> | ||||
|         <v-btn | ||||
|           :text="t('cancel')" | ||||
|           @click="isActive.value = false" | ||||
|         ></v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </template> | ||||
| </v-dialog> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| </style> | ||||
|  | @ -139,5 +139,10 @@ | |||
|     "editLearningPath": "Edit learning path", | ||||
|     "newLearningPath": "Create a new learning path", | ||||
|     "saveChanges": "Save changes", | ||||
|     "newLearningObject": "Upload learning object" | ||||
|     "newLearningObject": "Upload learning object", | ||||
|     "confirmDialogTitle": "Please confirm", | ||||
|     "learningPathDeleteQuery": "Are you sure you want to delete this learning path?", | ||||
|     "learningObjectDeleteQuery": "Are you sure you want to delete this learning object?", | ||||
|     "learningPathCantModifyId": "The HRUID or language of a learning path cannot be modified.", | ||||
|     "error": "Error" | ||||
| } | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
|     import type { LearningObject } from '@/data-objects/learning-objects/learning-object'; | ||||
|     import UsingQueryResult from '@/components/UsingQueryResult.vue'; | ||||
|     import LearningObjectContentView from '../../learning-paths/learning-object/content/LearningObjectContentView.vue'; | ||||
|     import ButtonWithConfirmation from '@/components/ButtonWithConfirmation.vue'; | ||||
|     import { useDeleteLearningObjectMutation, useLearningObjectHTMLQuery } from '@/queries/learning-objects'; | ||||
|     import { useI18n } from 'vue-i18n'; | ||||
| 
 | ||||
|  | @ -41,7 +42,13 @@ | |||
|             </using-query-result> | ||||
|         </template> | ||||
|         <template v-slot:actions> | ||||
|             <v-btn text="Delete" @click="deleteLearningObject()"></v-btn> | ||||
|             <button-with-confirmation | ||||
|                 @confirm="deleteLearningObject" | ||||
|                 prepend-icon="mdi mdi-delete" | ||||
|                 color="red" | ||||
|                 :text="t('delete')" | ||||
|                 :confirmQueryText="t('learningObjectDeleteQuery')" | ||||
|             /> | ||||
|         </template> | ||||
|     </v-card> | ||||
| </template> | ||||
|  |  | |||
|  | @ -2,10 +2,12 @@ | |||
|     import { useI18n } from 'vue-i18n'; | ||||
|     import { computed, ref, watch, type Ref } from 'vue'; | ||||
|     import JsonEditorVue from 'json-editor-vue' | ||||
|     import ButtonWithConfirmation from '@/components/ButtonWithConfirmation.vue' | ||||
|     import { useDeleteLearningPathMutation, usePostLearningPathMutation, usePutLearningPathMutation } from '@/queries/learning-paths'; | ||||
|     import { Language } from '@/data-objects/language'; | ||||
|     import type { LearningPath } from '@dwengo-1/common/interfaces/learning-content'; | ||||
|     import type { AxiosError } from 'axios'; | ||||
| import { parse } from 'uuid'; | ||||
| 
 | ||||
|     const { t } = useI18n(); | ||||
| 
 | ||||
|  | @ -63,7 +65,7 @@ | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function deleteLearningObject(): void { | ||||
|     function deleteLearningPath(): void { | ||||
|         if (props.selectedLearningPath) { | ||||
|             mutate({ | ||||
|                 hruid: props.selectedLearningPath.hruid, | ||||
|  | @ -72,8 +74,28 @@ | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function extractErrorMessage(error: AxiosError | null): string | undefined { | ||||
|         return (error?.response?.data as {error: string}).error ?? error?.message; | ||||
|     function extractErrorMessage(error: AxiosError): string { | ||||
|         return (error.response?.data as {error: string}).error ?? error.message; | ||||
|     } | ||||
| 
 | ||||
|     const isIdModified = computed(() => | ||||
|         props.selectedLearningPath !== undefined && ( | ||||
|             props.selectedLearningPath.hruid !== parsedLearningPath.value.hruid | ||||
|             || props.selectedLearningPath.language !== parsedLearningPath.value.language | ||||
|         ) | ||||
|     ); | ||||
| 
 | ||||
|     function getErrorMessage(): string | null { | ||||
|         if (postError.value) { | ||||
|             return t(extractErrorMessage(postError.value)); | ||||
|         } else if (putError.value) { | ||||
|             return t(extractErrorMessage(putError.value)); | ||||
|         } else if (deleteError.value) { | ||||
|             return t(extractErrorMessage(deleteError.value)); | ||||
|         } else if (isIdModified.value) { | ||||
|             return t('learningPathCantModifyId'); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
|  | @ -84,22 +106,34 @@ | |||
|         <template v-slot:text> | ||||
|             <json-editor-vue v-model="learningPath"></json-editor-vue> | ||||
|             <v-alert | ||||
|                  v-if="postError || putError || deleteError" | ||||
|                  v-if="postError || putError || deleteError || isIdModified" | ||||
|                  icon="mdi mdi-alert-circle" | ||||
|                  type="error" | ||||
|                  :title="t('error')" | ||||
|                  :text="t(extractErrorMessage(postError) || extractErrorMessage(putError) || extractErrorMessage(deleteError)!)" | ||||
|                  :text="getErrorMessage()!" | ||||
|             ></v-alert> | ||||
|         </template> | ||||
|         <template v-slot:actions> | ||||
|             <v-btn @click="uploadLearningPath" :loading="isPostPending || isPutPending" :disabled="parsedLearningPath.hruid === DEFAULT_LEARNING_PATH.hruid"> | ||||
|             <v-btn | ||||
|                 @click="uploadLearningPath" | ||||
|                 prependIcon="mdi mdi-check" | ||||
|                 :loading="isPostPending || isPutPending" | ||||
|                 :disabled="parsedLearningPath.hruid === DEFAULT_LEARNING_PATH.hruid || isIdModified" | ||||
|             > | ||||
|                 {{ props.selectedLearningPath ? t('saveChanges') : t('create') }} | ||||
|             </v-btn> | ||||
|             <v-btn @click="deleteLearningObject" :disabled="!props.selectedLearningPath"> | ||||
|                 {{ t('delete') }} | ||||
|             </v-btn> | ||||
|             <button-with-confirmation | ||||
|                 @confirm="deleteLearningPath" | ||||
|                 :disabled="!props.selectedLearningPath" | ||||
|                 :text="t('delete')" | ||||
|                 color="red" | ||||
|                 prependIcon="mdi mdi-delete" | ||||
|                 :confirmQueryText="t('learningPathDeleteQuery')" | ||||
|             /> | ||||
|             <v-btn | ||||
|                 :href="`/learningPath/${props.selectedLearningPath?.hruid}/${props.selectedLearningPath?.language}/start`" | ||||
|                 target="_blank" | ||||
|                 prepend-icon="mdi mdi-open-in-new" | ||||
|                 :disabled="!props.selectedLearningPath" | ||||
|             > | ||||
|                 {{ t('open') }} | ||||
|  |  | |||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger