feat(frontend): Heel ruwe eerste versie van leerpadbeheerpagina toegevoegd
This commit is contained in:
		
							parent
							
								
									1a768fedcc
								
							
						
					
					
						commit
						2db5d77296
					
				
					 15 changed files with 732 additions and 5820 deletions
				
			
		|  | @ -21,6 +21,7 @@ | ||||||
|         "@tanstack/vue-query": "^5.69.0", |         "@tanstack/vue-query": "^5.69.0", | ||||||
|         "@vueuse/core": "^13.1.0", |         "@vueuse/core": "^13.1.0", | ||||||
|         "axios": "^1.8.2", |         "axios": "^1.8.2", | ||||||
|  |         "json-editor-vue": "^0.18.1", | ||||||
|         "oidc-client-ts": "^3.1.0", |         "oidc-client-ts": "^3.1.0", | ||||||
|         "rollup": "^4.40.0", |         "rollup": "^4.40.0", | ||||||
|         "uuid": "^11.1.0", |         "uuid": "^11.1.0", | ||||||
|  |  | ||||||
|  | @ -7,8 +7,10 @@ | ||||||
| 
 | 
 | ||||||
|     // Import assets |     // Import assets | ||||||
|     import dwengoLogo from "../../../assets/img/dwengo-groen-zwart.svg"; |     import dwengoLogo from "../../../assets/img/dwengo-groen-zwart.svg"; | ||||||
|  | import { useLocale } from "vuetify"; | ||||||
| 
 | 
 | ||||||
|     const { t, locale } = useI18n(); |     const { t, locale } = useI18n(); | ||||||
|  |     const { current: vuetifyLocale } = useLocale(); | ||||||
| 
 | 
 | ||||||
|     const role = auth.authState.activeRole; |     const role = auth.authState.activeRole; | ||||||
|     const _router = useRouter(); // Zonder '_' gaf dit een linter error voor unused variable |     const _router = useRouter(); // Zonder '_' gaf dit een linter error voor unused variable | ||||||
|  | @ -31,6 +33,7 @@ | ||||||
|     // Logic to change the language of the website to the selected language |     // Logic to change the language of the website to the selected language | ||||||
|     function changeLanguage(langCode: string): void { |     function changeLanguage(langCode: string): void { | ||||||
|         locale.value = langCode; |         locale.value = langCode; | ||||||
|  |         vuetifyLocale.value = langCode; | ||||||
|         localStorage.setItem("user-lang", langCode); |         localStorage.setItem("user-lang", langCode); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -37,9 +37,8 @@ export class LearningPathController extends BaseController { | ||||||
|         return dtos.map((dto) => LearningPath.fromDTO(dto)); |         return dtos.map((dto) => LearningPath.fromDTO(dto)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async getAllByAdmin(admin: string): Promise<LearningPath[]> { |     async getAllByAdminRaw(admin: string): Promise<LearningPathDTO[]> { | ||||||
|         const dtos = await this.get<LearningPathDTO[]>("/", { admin }); |         return await this.get<LearningPathDTO[]>("/", { admin }); | ||||||
|         return dtos.map((dto) => LearningPath.fromDTO(dto)); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async postLearningPath(learningPath: LearningPathDTO): Promise<LearningPathDTO> { |     async postLearningPath(learningPath: LearningPathDTO): Promise<LearningPathDTO> { | ||||||
|  |  | ||||||
|  | @ -121,5 +121,18 @@ | ||||||
|     "invite": "invite", |     "invite": "invite", | ||||||
|     "assignmentIndicator": "ASSIGNMENT", |     "assignmentIndicator": "ASSIGNMENT", | ||||||
|     "searchAllLearningPathsTitle": "Search all learning paths", |     "searchAllLearningPathsTitle": "Search all learning paths", | ||||||
|     "searchAllLearningPathsDescription": "You didn't find what you were looking for? Click here to search our whole database of available learning paths." |     "searchAllLearningPathsDescription": "You didn't find what you were looking for? Click here to search our whole database of available learning paths.", | ||||||
|  |     "learningObjects": "Learning objects", | ||||||
|  |     "learningPaths": "Learning paths", | ||||||
|  |     "hruid": "HRUID", | ||||||
|  |     "language": "Language", | ||||||
|  |     "version": "Version", | ||||||
|  |     "previewFor": "Preview for ", | ||||||
|  |     "upload": "Upload", | ||||||
|  |     "learningObjectUploadTitle": "Upload a learning object", | ||||||
|  |     "uploadFailed": "Upload failed", | ||||||
|  |     "invalidZip": "This is not a valid zip file.", | ||||||
|  |     "emptyZip": "This zip file is empty", | ||||||
|  |     "missingMetadata": "This learning object is missing a metadata.json file.", | ||||||
|  |     "missingContent": "This learning object is missing a content.* file." | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,15 +7,20 @@ import * as components from "vuetify/components"; | ||||||
| import * as directives from "vuetify/directives"; | import * as directives from "vuetify/directives"; | ||||||
| import i18n from "./i18n/i18n.ts"; | import i18n from "./i18n/i18n.ts"; | ||||||
| 
 | 
 | ||||||
|  | // JSON-editor
 | ||||||
|  | import JsonEditorVue from 'json-editor-vue'; | ||||||
|  | 
 | ||||||
| // Components
 | // Components
 | ||||||
| import App from "./App.vue"; | import App from "./App.vue"; | ||||||
| import router from "./router"; | import router from "./router"; | ||||||
| import { aliases, mdi } from "vuetify/iconsets/mdi"; | import { aliases, mdi } from "vuetify/iconsets/mdi"; | ||||||
| import { VueQueryPlugin, QueryClient } from "@tanstack/vue-query"; | import { VueQueryPlugin, QueryClient } from "@tanstack/vue-query"; | ||||||
|  | import { de, en, fr, nl } from "vuetify/locale"; | ||||||
| 
 | 
 | ||||||
| const app = createApp(App); | const app = createApp(App); | ||||||
| 
 | 
 | ||||||
| app.use(router); | app.use(router); | ||||||
|  | app.use(JsonEditorVue, {}) | ||||||
| 
 | 
 | ||||||
| const link = document.createElement("link"); | const link = document.createElement("link"); | ||||||
| link.rel = "stylesheet"; | link.rel = "stylesheet"; | ||||||
|  | @ -32,6 +37,11 @@ const vuetify = createVuetify({ | ||||||
|             mdi, |             mdi, | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|  |     locale: { | ||||||
|  |         locale: i18n.global.locale, | ||||||
|  |         fallback: 'en', | ||||||
|  |         messages: { nl, en, de, fr } | ||||||
|  |     } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const queryClient = new QueryClient({ | const queryClient = new QueryClient({ | ||||||
|  |  | ||||||
|  | @ -5,7 +5,6 @@ import { getLearningObjectController } from "@/controllers/controllers.ts"; | ||||||
| import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts"; | import type { LearningObject } from "@/data-objects/learning-objects/learning-object.ts"; | ||||||
| import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | import type { LearningPath } from "@/data-objects/learning-paths/learning-path.ts"; | ||||||
| import type { AxiosError } from "axios"; | import type { AxiosError } from "axios"; | ||||||
| import type { LearningObjectIdentifierDTO } from "@dwengo-1/common/interfaces/learning-content"; |  | ||||||
| 
 | 
 | ||||||
| export const LEARNING_OBJECT_KEY = "learningObject"; | export const LEARNING_OBJECT_KEY = "learningObject"; | ||||||
| const learningObjectController = getLearningObjectController(); | const learningObjectController = getLearningObjectController(); | ||||||
|  |  | ||||||
|  | @ -1,8 +1,10 @@ | ||||||
| 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 { useMutation, useQuery, useQueryClient, type UseMutationReturnType, 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"; | ||||||
|  | import type { AxiosError } from "axios"; | ||||||
|  | import type { LearningPathDTO } from "@/data-objects/learning-paths/learning-path-dto"; | ||||||
| 
 | 
 | ||||||
| export const LEARNING_PATH_KEY = "learningPath"; | export const LEARNING_PATH_KEY = "learningPath"; | ||||||
| const learningPathController = getLearningPathController(); | const learningPathController = getLearningPathController(); | ||||||
|  | @ -32,6 +34,46 @@ export function useGetAllLearningPathsByThemeQuery( | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function useGetAllLearningPathsByAdminQuery( | ||||||
|  |     admin: MaybeRefOrGetter<string | undefined> | ||||||
|  | ): UseQueryReturnType<LearningPathDTO[], AxiosError> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryKey: [LEARNING_PATH_KEY, "getAllByAdmin", admin], | ||||||
|  |         queryFn: async () => learningPathController.getAllByAdminRaw(toValue(admin)!), | ||||||
|  |         enabled: () => Boolean(toValue(admin)) | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function usePostLearningPathMutation(): | ||||||
|  |     UseMutationReturnType<LearningPathDTO, AxiosError, { learningPath: LearningPathDTO }, unknown> { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ learningPath }) => learningPathController.postLearningPath(learningPath), | ||||||
|  |         onSuccess: async () => queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY] }) | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function usePutLearningPathMutation(): | ||||||
|  |     UseMutationReturnType<LearningPathDTO, AxiosError, { learningPath: LearningPathDTO }, unknown> { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ learningPath }) => learningPathController.putLearningPath(learningPath), | ||||||
|  |         onSuccess: async () => queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY] }) | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useDeleteLearningPathMutation(): | ||||||
|  |     UseMutationReturnType<LearningPathDTO, AxiosError, { hruid: string, language: Language }, unknown> { | ||||||
|  |     const queryClient = useQueryClient(); | ||||||
|  | 
 | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async ({ hruid, language }) => learningPathController.deleteLearningPath(hruid, language), | ||||||
|  |         onSuccess: async () => queryClient.invalidateQueries({ queryKey: [LEARNING_PATH_KEY] }) | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function useSearchLearningPathQuery( | export function useSearchLearningPathQuery( | ||||||
|     query: MaybeRefOrGetter<string | undefined>, |     query: MaybeRefOrGetter<string | undefined>, | ||||||
|     language: MaybeRefOrGetter<string | undefined>, |     language: MaybeRefOrGetter<string | undefined>, | ||||||
|  |  | ||||||
|  | @ -1,18 +1,23 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import {useLearningObjectListForAdminQuery} from "@/queries/learning-objects.ts"; |     import {useLearningObjectListForAdminQuery} from "@/queries/learning-objects.ts"; | ||||||
|     import OwnLearningObjectsView from "@/views/own-learning-content/OwnLearningObjectsView.vue" |     import OwnLearningObjectsView from "@/views/own-learning-content/learning-objects/OwnLearningObjectsView.vue" | ||||||
|     import OwnLearningPathsView from "@/views/own-learning-content/OwnLearningPathsView.vue" |     import OwnLearningPathsView from "@/views/own-learning-content/learning-paths/OwnLearningPathsView.vue" | ||||||
|     import authService from "@/services/auth/auth-service.ts"; |     import authService from "@/services/auth/auth-service.ts"; | ||||||
|     import UsingQueryResult from "@/components/UsingQueryResult.vue"; |     import UsingQueryResult from "@/components/UsingQueryResult.vue"; | ||||||
|     import type { LearningObject } from "@/data-objects/learning-objects/learning-object"; |     import type { LearningObject } from "@/data-objects/learning-objects/learning-object"; | ||||||
|     import { ref, type Ref } from "vue"; |     import { ref, type Ref } from "vue"; | ||||||
| import { useI18n } from "vue-i18n"; | import { useI18n } from "vue-i18n"; | ||||||
|  | import { useGetAllLearningPathsByAdminQuery } from "@/queries/learning-paths"; | ||||||
|  | import type { LearningPathDTO } from "@/data-objects/learning-paths/learning-path-dto"; | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
| 
 | 
 | ||||||
|     const learningObjectsQuery = |     const learningObjectsQuery = | ||||||
|         useLearningObjectListForAdminQuery(authService.authState.user?.profile.preferred_username); |         useLearningObjectListForAdminQuery(authService.authState.user?.profile.preferred_username); | ||||||
| 
 | 
 | ||||||
|  |     const learningPathsQuery = | ||||||
|  |         useGetAllLearningPathsByAdminQuery(authService.authState.user?.profile.preferred_username); | ||||||
|  | 
 | ||||||
|     type Tab = "learningObjects" | "learningPaths"; |     type Tab = "learningObjects" | "learningPaths"; | ||||||
|     const tab: Ref<Tab> = ref("learningObjects"); |     const tab: Ref<Tab> = ref("learningObjects"); | ||||||
| </script> | </script> | ||||||
|  | @ -34,7 +39,12 @@ import { useI18n } from "vue-i18n"; | ||||||
|                 </using-query-result> |                 </using-query-result> | ||||||
|             </v-tabs-window-item> |             </v-tabs-window-item> | ||||||
|             <v-tabs-window-item value="learningPaths"> |             <v-tabs-window-item value="learningPaths"> | ||||||
|                 <own-learning-paths-view/> |                 <using-query-result | ||||||
|  |                     :query-result="learningPathsQuery" | ||||||
|  |                     v-slot="response: { data: LearningPathDTO[] }" | ||||||
|  |                 > | ||||||
|  |                     <own-learning-paths-view :learning-paths="response.data"/> | ||||||
|  |                 </using-query-result> | ||||||
|             </v-tabs-window-item> |             </v-tabs-window-item> | ||||||
|         </v-tabs-window> |         </v-tabs-window> | ||||||
|     </div> |     </div> | ||||||
|  | @ -45,8 +55,10 @@ import { useI18n } from "vue-i18n"; | ||||||
|         display: flex; |         display: flex; | ||||||
|         flex-direction: column; |         flex-direction: column; | ||||||
|         height: 100%; |         height: 100%; | ||||||
|  |         padding: 20px 30px; | ||||||
|     } |     } | ||||||
|     .main-content { |     .main-content { | ||||||
|         flex: 1; |         flex: 1 1; | ||||||
|  |         height: 100%; | ||||||
|     } |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| <script lang="ts"> |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| <template> |  | ||||||
|     <p>Own learning paths</p> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <style> |  | ||||||
| </style> |  | ||||||
|  | @ -0,0 +1,50 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  |     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 { useDeleteLearningObjectMutation, useLearningObjectHTMLQuery } from '@/queries/learning-objects'; | ||||||
|  |     import { useI18n } from 'vue-i18n'; | ||||||
|  | 
 | ||||||
|  |     const { t } = useI18n(); | ||||||
|  | 
 | ||||||
|  |     const props = defineProps<{ | ||||||
|  |         selectedLearningObject?: LearningObject | ||||||
|  |     }>(); | ||||||
|  | 
 | ||||||
|  |     const learningObjectQueryResult = useLearningObjectHTMLQuery( | ||||||
|  |         () => props.selectedLearningObject?.key, | ||||||
|  |         () => props.selectedLearningObject?.language, | ||||||
|  |         () => props.selectedLearningObject?.version | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     const { isPending, mutate } = useDeleteLearningObjectMutation(); | ||||||
|  | 
 | ||||||
|  |     function deleteLearningObject(): void { | ||||||
|  |         if (props.selectedLearningObject) { | ||||||
|  |             mutate({ | ||||||
|  |                 hruid: props.selectedLearningObject.key, | ||||||
|  |                 language: props.selectedLearningObject.language, | ||||||
|  |                 version: props.selectedLearningObject.version | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <v-card | ||||||
|  |         v-if="selectedLearningObject" | ||||||
|  |         :title="t('previewFor') + selectedLearningObject.title" | ||||||
|  |     > | ||||||
|  |         <template v-slot:text> | ||||||
|  |             <using-query-result :query-result="learningObjectQueryResult" v-slot="response: { data: Document }"> | ||||||
|  |                 <learning-object-content-view :learning-object-content="response.data"></learning-object-content-view> | ||||||
|  |             </using-query-result> | ||||||
|  |         </template> | ||||||
|  |         <template v-slot:actions> | ||||||
|  |             <v-btn text="Delete" @click="deleteLearningObject()"></v-btn> | ||||||
|  |         </template> | ||||||
|  |     </v-card> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | </style> | ||||||
|  | @ -32,17 +32,14 @@ | ||||||
| <template> | <template> | ||||||
|     <v-dialog max-width="500" v-model="dialogOpen"> |     <v-dialog max-width="500" v-model="dialogOpen"> | ||||||
|         <template v-slot:activator="{ props: activatorProps }"> |         <template v-slot:activator="{ props: activatorProps }"> | ||||||
|             <v-fab icon="mdi mdi-plus" v-bind="activatorProps"></v-fab> |             <v-fab icon="mdi mdi-plus" v-bind="activatorProps" color="rgb(14, 105, 66)" size="large"></v-fab> | ||||||
|         </template> |         </template> | ||||||
| 
 | 
 | ||||||
|         <template v-slot:default="{ isActive }"> |         <template v-slot:default="{ isActive }"> | ||||||
|             <v-card :title="t('learning_object_upload_title')"> |             <v-card :title="t('learningObjectUploadTitle')"> | ||||||
|                 <v-card-text> |                 <v-card-text> | ||||||
|                     <v-file-upload |                     <v-file-upload | ||||||
|                         :browse-text="t('upload_browse')" |  | ||||||
|                         :divider-text="t('upload_divider')" |  | ||||||
|                         icon="mdi-upload" |                         icon="mdi-upload" | ||||||
|                         :title="t('upload_drag_and_drop')" |  | ||||||
|                         v-model="fileToUpload" |                         v-model="fileToUpload" | ||||||
|                         :disabled="isPending" |                         :disabled="isPending" | ||||||
|                     ></v-file-upload> |                     ></v-file-upload> | ||||||
|  | @ -50,7 +47,7 @@ | ||||||
|                         v-if="error" |                         v-if="error" | ||||||
|                         icon="mdi mdi-alert-circle" |                         icon="mdi mdi-alert-circle" | ||||||
|                         type="error" |                         type="error" | ||||||
|                         :title="t('upload_failed')" |                         :title="t('uploadFailed')" | ||||||
|                         :text="t((error.response?.data as ContainsErrorString).error ?? error.message)" |                         :text="t((error.response?.data as ContainsErrorString).error ?? error.message)" | ||||||
|                     ></v-alert> |                     ></v-alert> | ||||||
|                 </v-card-text> |                 </v-card-text> | ||||||
|  | @ -1,11 +1,9 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import type { LearningObject } from '@/data-objects/learning-objects/learning-object'; |     import type { LearningObject } from '@/data-objects/learning-objects/learning-object'; | ||||||
|     import LearningObjectUploadButton from '@/views/own-learning-content/LearningObjectUploadButton.vue' |     import LearningObjectUploadButton from '@/views/own-learning-content/learning-objects/LearningObjectUploadButton.vue' | ||||||
|     import LearningObjectContentView from '../learning-paths/learning-object/content/LearningObjectContentView.vue'; |     import LearningObjectPreviewCard from './LearningObjectPreviewCard.vue'; | ||||||
|     import { computed, ref, type Ref } from 'vue'; |     import { computed, ref, type Ref } from 'vue'; | ||||||
|     import { useI18n } from 'vue-i18n'; |     import { useI18n } from 'vue-i18n'; | ||||||
| import { useLearningObjectHTMLQuery } from '@/queries/learning-objects'; |  | ||||||
| import UsingQueryResult from '@/components/UsingQueryResult.vue'; |  | ||||||
| 
 | 
 | ||||||
|     const { t } = useI18n(); |     const { t } = useI18n(); | ||||||
|     const props = defineProps<{ |     const props = defineProps<{ | ||||||
|  | @ -19,17 +17,12 @@ import UsingQueryResult from '@/components/UsingQueryResult.vue'; | ||||||
|         { title: t("title"), key: "title" } |         { title: t("title"), key: "title" } | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     const selectedLearningObjects: Ref<LearningObject[]> = ref([]) |     const selectedLearningObjects: Ref<LearningObject[]> = ref([]); | ||||||
| 
 | 
 | ||||||
|     const selectedLearningObject = computed(() => |     const selectedLearningObject = computed(() => | ||||||
|         selectedLearningObjects.value ? selectedLearningObjects.value[0] : undefined |         selectedLearningObjects.value ? selectedLearningObjects.value[0] : undefined | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     const learningObjectQueryResult = useLearningObjectHTMLQuery( |  | ||||||
|         () => selectedLearningObject.value?.key, |  | ||||||
|         () => selectedLearningObject.value?.language, |  | ||||||
|         () => selectedLearningObject.value?.version |  | ||||||
|     ); |     ); | ||||||
|  | 
 | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  | @ -43,17 +36,7 @@ import UsingQueryResult from '@/components/UsingQueryResult.vue'; | ||||||
|             show-select |             show-select | ||||||
|             return-object |             return-object | ||||||
|         /> |         /> | ||||||
|         <v-card |         <learning-object-preview-card class="preview" :selectedLearningObject="selectedLearningObject"/> | ||||||
|             class="preview" |  | ||||||
|             v-if="selectedLearningObjects.length > 0" |  | ||||||
|             :title="t('preview_for') + selectedLearningObjects[0].title" |  | ||||||
|         > |  | ||||||
|             <template v-slot:text> |  | ||||||
|                 <using-query-result :query-result="learningObjectQueryResult" v-slot="response: { data: Document }"> |  | ||||||
|                     <learning-object-content-view :learning-object-content="response.data"></learning-object-content-view> |  | ||||||
|                 </using-query-result> |  | ||||||
|             </template> |  | ||||||
|         </v-card> |  | ||||||
|     </div> |     </div> | ||||||
|     <div class="fab"> |     <div class="fab"> | ||||||
|         <learning-object-upload-button/> |         <learning-object-upload-button/> | ||||||
|  | @ -70,9 +53,11 @@ import UsingQueryResult from '@/components/UsingQueryResult.vue'; | ||||||
|         display: flex; |         display: flex; | ||||||
|         gap: 20px; |         gap: 20px; | ||||||
|         padding: 20px; |         padding: 20px; | ||||||
|  |         flex-wrap: wrap; | ||||||
|     } |     } | ||||||
|     .preview { |     .preview { | ||||||
|         flex: 1; |         flex: 1; | ||||||
|  |         min-width: 400px; | ||||||
|     } |     } | ||||||
|     .table { |     .table { | ||||||
|         flex: 1; |         flex: 1; | ||||||
|  | @ -0,0 +1,62 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  |     import UsingQueryResult from '@/components/UsingQueryResult.vue'; | ||||||
|  |     import { useI18n } from 'vue-i18n'; | ||||||
|  |     import type { LearningPathDTO } from '@/data-objects/learning-paths/learning-path-dto'; | ||||||
|  |     import { computed, ref, watch } from 'vue'; | ||||||
|  |     import JsonEditorVue from 'json-editor-vue' | ||||||
|  | import { useMutation } from '@tanstack/vue-query'; | ||||||
|  | import { usePostLearningPathMutation, usePutLearningPathMutation } from '@/queries/learning-paths'; | ||||||
|  | 
 | ||||||
|  |     const { t } = useI18n(); | ||||||
|  | 
 | ||||||
|  |     const props = defineProps<{ | ||||||
|  |         selectedLearningPath?: LearningPathDTO | ||||||
|  |     }>(); | ||||||
|  | 
 | ||||||
|  |     const INDENT = 4; | ||||||
|  |     const DEFAULT_LEARNING_PATH: LearningPathDTO = { | ||||||
|  |         language: '', | ||||||
|  |         hruid: '', | ||||||
|  |         title: '', | ||||||
|  |         description: '', | ||||||
|  |         num_nodes: 0, | ||||||
|  |         num_nodes_left: 0, | ||||||
|  |         nodes: [], | ||||||
|  |         keywords: '', | ||||||
|  |         target_ages: [], | ||||||
|  |         min_age: 0, | ||||||
|  |         max_age: 0, | ||||||
|  |         __order: 0 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const { isPending: isPostPending, mutate: doPost } = usePostLearningPathMutation(); | ||||||
|  |     const { isPending: isPutPending, mutate: doPut } = usePutLearningPathMutation(); | ||||||
|  | 
 | ||||||
|  |     const learningPath = ref(DEFAULT_LEARNING_PATH); | ||||||
|  | 
 | ||||||
|  |     watch(() => props.selectedLearningPath, () => learningPath.value = props.selectedLearningPath ?? DEFAULT_LEARNING_PATH); | ||||||
|  | 
 | ||||||
|  |     function uploadLearningPath(): void { | ||||||
|  |         if (props.selectedLearningPath) { | ||||||
|  |             doPut({ learningPath: learningPath.value }); | ||||||
|  |         } else { | ||||||
|  |             doPost({ learningPath: learningPath.value }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <v-card | ||||||
|  |         :title="props.selectedLearningPath ? t('editLearningPath') : t('newLearningPath')" | ||||||
|  |     > | ||||||
|  |         <template v-slot:text> | ||||||
|  |             <json-editor-vue v-model="learningPath"></json-editor-vue> | ||||||
|  |         </template> | ||||||
|  |         <template v-slot:actions> | ||||||
|  |             <v-btn @click="uploadLearningPath" :loading="isPostPending || isPutPending">{{ props.selectedLearningPath ? t('saveChanges') : t('create') }}</v-btn> | ||||||
|  |         </template> | ||||||
|  |     </v-card> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,60 @@ | ||||||
|  | <script setup lang="ts"> | ||||||
|  |     import LearningPathPreviewCard from './LearningPathPreviewCard.vue'; | ||||||
|  |     import { computed, ref, type Ref } from 'vue'; | ||||||
|  |     import { useI18n } from 'vue-i18n'; | ||||||
|  |     import type { LearningPathDTO } from '@/data-objects/learning-paths/learning-path-dto'; | ||||||
|  | 
 | ||||||
|  |     const { t } = useI18n(); | ||||||
|  |     const props = defineProps<{ | ||||||
|  |         learningPaths: LearningPathDTO[] | ||||||
|  |     }>(); | ||||||
|  | 
 | ||||||
|  |     const tableHeaders = [ | ||||||
|  |         { title: t("hruid"), width: "250px", key: "key" }, | ||||||
|  |         { title: t("language"), width: "50px", key: "language" }, | ||||||
|  |         { title: t("title"), key: "title" } | ||||||
|  |     ]; | ||||||
|  | 
 | ||||||
|  |     const selectedLearningPaths: Ref<LearningPathDTO[]> = ref([]); | ||||||
|  | 
 | ||||||
|  |     const selectedLearningPath = computed(() => | ||||||
|  |         selectedLearningPaths.value ? selectedLearningPaths.value[0] : undefined | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <div class="root"> | ||||||
|  |         <v-data-table | ||||||
|  |             class="table" | ||||||
|  |             v-model="selectedLearningPaths" | ||||||
|  |             :items="props.learningPaths" | ||||||
|  |             :headers="tableHeaders" | ||||||
|  |             select-strategy="single" | ||||||
|  |             show-select | ||||||
|  |             return-object | ||||||
|  |         /> | ||||||
|  |         <learning-path-preview-card class="preview" :selectedLearningPath="selectedLearningPath"/> | ||||||
|  |     </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  |     .fab { | ||||||
|  |         position: absolute; | ||||||
|  |         right: 20px; | ||||||
|  |         bottom: 20px; | ||||||
|  |     } | ||||||
|  |     .root { | ||||||
|  |         display: flex; | ||||||
|  |         gap: 20px; | ||||||
|  |         padding: 20px; | ||||||
|  |         flex-wrap: wrap; | ||||||
|  |     } | ||||||
|  |     .preview { | ||||||
|  |         flex: 1; | ||||||
|  |         min-width: 400px; | ||||||
|  |     } | ||||||
|  |     .table { | ||||||
|  |         flex: 1; | ||||||
|  |     } | ||||||
|  | </style> | ||||||
							
								
								
									
										6234
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										6234
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger