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
Loading…
Add table
Add a link
Reference in a new issue