fix: UI-imperfecties & diverse bugs omtrent het verwijderen en editeren van leerpaden opgelost

This commit is contained in:
Gerald Schmittinger 2025-05-14 00:27:28 +02:00
parent 9400b7f33c
commit 9a58126c7c
6 changed files with 126 additions and 28 deletions

View file

@ -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.

View file

@ -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;

View file

@ -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>

View file

@ -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"
}

View file

@ -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>

View file

@ -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') }}