Merged develop into feature/VBV-524
This commit is contained in:
commit
8b4b00170a
|
|
@ -20,14 +20,19 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
doc: {
|
||||
url: string;
|
||||
name: string;
|
||||
};
|
||||
canDelete: boolean;
|
||||
subtitle: string;
|
||||
}>();
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
doc: {
|
||||
url: string;
|
||||
name: string;
|
||||
};
|
||||
canDelete?: boolean;
|
||||
subtitle: string;
|
||||
}>(),
|
||||
{
|
||||
canDelete: false,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(["delete"]);
|
||||
</script>
|
||||
|
|
@ -9,9 +9,9 @@ const courseSession = useCurrentCourseSession();
|
|||
|
||||
const circleDates = computed(() => {
|
||||
const dueDates = courseSession.value.due_dates.filter((dueDate) => {
|
||||
if (!cockpitStore.selectedCircle) return false;
|
||||
if (!cockpitStore.currentCircle) return false;
|
||||
return (
|
||||
cockpitStore.selectedCircle.translation_key == dueDate?.circle?.translation_key
|
||||
cockpitStore.currentCircle.translation_key == dueDate?.circle?.translation_key
|
||||
);
|
||||
});
|
||||
return dueDates.slice(0, 4);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { useCompetenceStore } from "@/stores/competence";
|
|||
import { useLearningPathStore } from "@/stores/learningPath";
|
||||
import log from "loglevel";
|
||||
import CockpitDates from "@/pages/cockpit/cockpitPage/CockpitDates.vue";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -22,12 +23,13 @@ const cockpitStore = useCockpitStore();
|
|||
const competenceStore = useCompetenceStore();
|
||||
const learningPathStore = useLearningPathStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
|
||||
function userCountStatusForCircle(userId: string) {
|
||||
if (!cockpitStore.selectedCircle) return { FAIL: 0, SUCCESS: 0, UNKNOWN: 0 };
|
||||
if (!cockpitStore.currentCircle) return { FAIL: 0, SUCCESS: 0, UNKNOWN: 0 };
|
||||
const criteria = competenceStore.flatPerformanceCriteria(
|
||||
userId,
|
||||
cockpitStore.selectedCircle.id
|
||||
cockpitStore.currentCircle.id
|
||||
);
|
||||
return competenceStore.calcStatusCount(criteria);
|
||||
}
|
||||
|
|
@ -35,146 +37,173 @@ function userCountStatusForCircle(userId: string) {
|
|||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<div class="container-large">
|
||||
<div v-if="cockpitStore.currentCircle" class="container-large">
|
||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h1>Cockpit</h1>
|
||||
<ItDropdownSelect
|
||||
v-model="cockpitStore.selectedCircle"
|
||||
:model-value="cockpitStore.selectedCircle"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
:items="cockpitStore.circles"
|
||||
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<template v-if="cockpitStore.selectedCircle">
|
||||
<!-- Status -->
|
||||
<div class="mb-4 gap-4 lg:grid lg:grid-cols-3 lg:grid-rows-none">
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("Trainerunterlagen") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{ $t("cockpit.trainerFilesText") }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://vbvbern.sharepoint.com/sites/myVBV-AFA_K-CI"
|
||||
class="btn-secondary min-w-min"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t("MS Teams öffnen") }}
|
||||
</a>
|
||||
<!-- Status -->
|
||||
<div class="mb-4 gap-4 lg:grid lg:grid-cols-3 lg:grid-rows-none">
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("Trainerunterlagen") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{ $t("cockpit.trainerFilesText") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("Anwesenheitskontrolle Präsenzkurse") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{
|
||||
$t(
|
||||
"Hier überprüfst und bestätigst du die Anwesenheit deiner Teilnehmenden."
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/attendance`"
|
||||
class="btn-secondary min-w-min"
|
||||
>
|
||||
{{ $t("Anwesenheit prüfen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white p-6">
|
||||
<CockpitDates></CockpitDates>
|
||||
<div>
|
||||
<a
|
||||
href="https://vbvbern.sharepoint.com/sites/myVBV-AFA_K-CI"
|
||||
class="btn-secondary min-w-min"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t("MS Teams öffnen") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<SubmissionsOverview
|
||||
:course-session="courseSession"
|
||||
:selected-circle="cockpitStore.selectedCircle.id"
|
||||
></SubmissionsOverview>
|
||||
<div class="pt-4">
|
||||
<!-- progress -->
|
||||
<div v-if="cockpitStore.courseSessionMembers" class="bg-white p-6">
|
||||
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
||||
<ul>
|
||||
<ItPersonRow
|
||||
v-for="csu in cockpitStore.courseSessionMembers"
|
||||
:key="csu.user_id + csu.session_title"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
>
|
||||
<template #center>
|
||||
<div
|
||||
class="mt-2 flex w-full flex-col items-center justify-between lg:mt-0 lg:flex-row"
|
||||
>
|
||||
<LearningPathDiagram
|
||||
v-if="
|
||||
learningPathStore.learningPathForUser(
|
||||
props.courseSlug,
|
||||
csu.user_id
|
||||
)
|
||||
"
|
||||
:learning-path="
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("a.Unterlagen für Teilnehmenden") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{ $t("a.Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.") }}
|
||||
</div>
|
||||
<div
|
||||
v-if="courseSessionsStore.circleDocuments.length"
|
||||
class="mb-4 flex items-center gap-x-2"
|
||||
>
|
||||
<it-icon-document />
|
||||
{{ courseSessionsStore.circleDocuments.length }} {{ $t("a.Unterlagen") }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/documents`"
|
||||
class="btn-secondary min-w-min"
|
||||
>
|
||||
{{ $t("a.Zum Unterlagen-Upload") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("Anwesenheitskontrolle Präsenzkurse") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{
|
||||
$t(
|
||||
"Hier überprüfst und bestätigst du die Anwesenheit deiner Teilnehmenden."
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/attendance`"
|
||||
class="btn-secondary min-w-min"
|
||||
>
|
||||
{{ $t("Anwesenheit prüfen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 bg-white p-6">
|
||||
<CockpitDates></CockpitDates>
|
||||
</div>
|
||||
<SubmissionsOverview
|
||||
:course-session="courseSession"
|
||||
:selected-circle="cockpitStore.currentCircle.id"
|
||||
></SubmissionsOverview>
|
||||
<div class="pt-4">
|
||||
<!-- progress -->
|
||||
<div v-if="cockpitStore.courseSessionMembers" class="bg-white p-6">
|
||||
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
||||
<ul>
|
||||
<ItPersonRow
|
||||
v-for="csu in cockpitStore.courseSessionMembers"
|
||||
:key="csu.user_id + csu.session_title"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
>
|
||||
<template #center>
|
||||
<div
|
||||
class="mt-2 flex w-full flex-col items-center justify-between lg:mt-0 lg:flex-row"
|
||||
>
|
||||
<LearningPathDiagram
|
||||
v-if="
|
||||
learningPathStore.learningPathForUser(
|
||||
props.courseSlug,
|
||||
csu.user_id
|
||||
)
|
||||
"
|
||||
:learning-path="
|
||||
learningPathStore.learningPathForUser(
|
||||
props.courseSlug,
|
||||
csu.user_id
|
||||
) as LearningPath
|
||||
"
|
||||
:show-circle-translation-keys="[
|
||||
cockpitStore.selectedCircle.translation_key,
|
||||
]"
|
||||
diagram-type="singleSmall"
|
||||
class="mr-4"
|
||||
></LearningPathDiagram>
|
||||
<p class="lg:min-w-[150px]">
|
||||
{{ cockpitStore.selectedCircle.name }}
|
||||
</p>
|
||||
<div class="ml-4 flex flex-row items-center">
|
||||
<div class="mr-6 flex flex-row items-center">
|
||||
<it-icon-smiley-thinking
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-thinking>
|
||||
<p class="text-bold inline-block w-6">
|
||||
{{ userCountStatusForCircle(csu.user_id).FAIL }}
|
||||
</p>
|
||||
</div>
|
||||
<li class="mr-6 flex flex-row items-center">
|
||||
<it-icon-smiley-happy
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-happy>
|
||||
<p class="text-bold inline-block w-6">
|
||||
{{ userCountStatusForCircle(csu.user_id).SUCCESS }}
|
||||
</p>
|
||||
</li>
|
||||
<li class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-neutral>
|
||||
<p class="text-bold inline-block w-6">
|
||||
{{ userCountStatusForCircle(csu.user_id).UNKNOWN }}
|
||||
</p>
|
||||
</li>
|
||||
:show-circle-translation-keys="[
|
||||
cockpitStore.currentCircle.translation_key,
|
||||
]"
|
||||
diagram-type="singleSmall"
|
||||
class="mr-4"
|
||||
></LearningPathDiagram>
|
||||
<p class="lg:min-w-[150px]">
|
||||
{{ cockpitStore.currentCircle.title }}
|
||||
</p>
|
||||
<div class="ml-4 flex flex-row items-center">
|
||||
<div class="mr-6 flex flex-row items-center">
|
||||
<it-icon-smiley-thinking
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-thinking>
|
||||
<p class="text-bold inline-block w-6">
|
||||
{{ userCountStatusForCircle(csu.user_id).FAIL }}
|
||||
</p>
|
||||
</div>
|
||||
<li class="mr-6 flex flex-row items-center">
|
||||
<it-icon-smiley-happy
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-happy>
|
||||
<p class="text-bold inline-block w-6">
|
||||
{{ userCountStatusForCircle(csu.user_id).SUCCESS }}
|
||||
</p>
|
||||
</li>
|
||||
<li class="flex flex-row items-center">
|
||||
<it-icon-smiley-neutral
|
||||
class="mr-2 inline-block h-8 w-8"
|
||||
></it-icon-smiley-neutral>
|
||||
<p class="text-bold inline-block w-6">
|
||||
{{ userCountStatusForCircle(csu.user_id).UNKNOWN }}
|
||||
</p>
|
||||
</li>
|
||||
</div>
|
||||
</template>
|
||||
<template #link>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
||||
class="link w-full lg:text-right"
|
||||
>
|
||||
{{ $t("general.profileLink") }}
|
||||
</router-link>
|
||||
</template>
|
||||
</ItPersonRow>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #link>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
||||
class="link w-full lg:text-right"
|
||||
>
|
||||
{{ $t("general.profileLink") }}
|
||||
</router-link>
|
||||
</template>
|
||||
</ItPersonRow>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else class="text-lg text-orange-600">
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container-large mt-4">
|
||||
<span class="text-lg text-orange-600">
|
||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { useCockpitStore } from "@/stores/cockpit";
|
||||
import ItModal from "@/components/ui/ItModal.vue";
|
||||
import DocumentUploadForm from "@/pages/cockpit/documentPage/DocumentUploadForm.vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useCircleStore } from "@/stores/circle";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import type { CircleDocument, DocumentUploadData } from "@/types";
|
||||
import dialog from "@/utils/confirm-dialog";
|
||||
import log from "loglevel";
|
||||
import { uploadCircleDocument } from "@/services/files";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
|
||||
|
||||
const cockpitStore = useCockpitStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
|
||||
const circleStore = useCircleStore();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const showUploadModal = ref(false);
|
||||
const showUploadErrorMessage = ref(false);
|
||||
const isUploading = ref(false);
|
||||
const dropdownLearningSequences = computed(() =>
|
||||
circleStore.circle?.learningSequences.map((sequence) => ({
|
||||
id: sequence.id,
|
||||
name: sequence.title,
|
||||
}))
|
||||
);
|
||||
|
||||
const deleteDocument = async (doc: CircleDocument) => {
|
||||
const options = {
|
||||
title: t("circlePage.documents.deleteModalTitle"),
|
||||
content: t("circlePage.documents.deleteModalWarning", { title: doc.name }),
|
||||
};
|
||||
try {
|
||||
await dialog.confirm(options);
|
||||
courseSessionsStore.removeDocument(doc.id);
|
||||
} catch (e) {
|
||||
log.debug("rejected");
|
||||
}
|
||||
};
|
||||
watch(showUploadModal, () => (showUploadErrorMessage.value = false));
|
||||
|
||||
async function uploadDocument(data: DocumentUploadData) {
|
||||
isUploading.value = true;
|
||||
showUploadErrorMessage.value = false;
|
||||
try {
|
||||
if (!courseSessionsStore.currentCourseSession) {
|
||||
throw new Error("No course session found");
|
||||
}
|
||||
const newDocument = await uploadCircleDocument(
|
||||
data,
|
||||
courseSessionsStore.currentCourseSession.id
|
||||
);
|
||||
courseSessionsStore.addDocument(newDocument);
|
||||
showUploadModal.value = false;
|
||||
isUploading.value = false;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
showUploadErrorMessage.value = true;
|
||||
isUploading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<div v-if="courseSession" class="container-large">
|
||||
<nav class="py-4 pb-4">
|
||||
<router-link
|
||||
class="btn-text inline-flex items-center pl-0"
|
||||
:to="`/course/${courseSession.course.slug}/cockpit`"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>{{ t("general.back") }}</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
<main>
|
||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h2>{{ t("a.Unterlagen für Teilnehmenden") }}</h2>
|
||||
<ItDropdownSelect
|
||||
:model-value="cockpitStore.selectedCircle"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
:items="cockpitStore.circles"
|
||||
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6">
|
||||
<button class="btn-primary text-xl" @click="showUploadModal = true">
|
||||
{{ t("circlePage.documents.action") }}
|
||||
</button>
|
||||
|
||||
<ul
|
||||
v-if="courseSessionsStore.circleDocuments.length"
|
||||
class="mt-8 border-t border-t-gray-500"
|
||||
>
|
||||
<template
|
||||
v-for="learningSequence of courseSessionsStore.circleDocuments"
|
||||
:key="learningSequence.id"
|
||||
>
|
||||
<DocumentListItem
|
||||
v-for="doc of learningSequence.documents"
|
||||
:key="doc.url"
|
||||
:subtitle="learningSequence.title"
|
||||
:can-delete="courseSessionsStore.canUploadCircleDocuments"
|
||||
:doc="doc"
|
||||
@delete="deleteDocument(doc)"
|
||||
/>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<ItModal v-model="showUploadModal">
|
||||
<template #title>{{ t("circlePage.documents.action") }}</template>
|
||||
<template #body>
|
||||
<DocumentUploadForm
|
||||
:learning-sequences="dropdownLearningSequences"
|
||||
:show-upload-error-message="showUploadErrorMessage"
|
||||
:is-uploading="isUploading"
|
||||
@form-submit="uploadDocument"
|
||||
/>
|
||||
</template>
|
||||
</ItModal>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -5,12 +5,7 @@
|
|||
</h3>
|
||||
<div>
|
||||
<div class="mt-4 leading-relaxed">
|
||||
<template v-if="!courseSessionsStore.canUploadCircleDocuments">
|
||||
{{ $t("circlePage.documents.userDescription") }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t("circlePage.documents.expertDescription") }}
|
||||
</template>
|
||||
{{ $t("circlePage.documents.userDescription") }}
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
|
|
@ -25,103 +20,16 @@
|
|||
v-for="doc of learningSequence.documents"
|
||||
:key="doc.url"
|
||||
:subtitle="learningSequence.title"
|
||||
:can-delete="courseSessionsStore.canUploadCircleDocuments"
|
||||
:doc="doc"
|
||||
@delete="deleteDocument(doc)"
|
||||
/>
|
||||
</template>
|
||||
</ul>
|
||||
<div v-if="courseSessionsStore.canUploadCircleDocuments">
|
||||
<button class="btn-primary mt-8 text-xl" @click="showUploadModal = true">
|
||||
{{ $t("circlePage.documents.action") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ItModal v-model="showUploadModal">
|
||||
<template #title>{{ $t("circlePage.documents.action") }}</template>
|
||||
<template #body>
|
||||
<DocumentUploadForm
|
||||
:learning-sequences="dropdownLearningSequences"
|
||||
:show-upload-error-message="showUploadErrorMessage"
|
||||
:is-uploading="isUploading"
|
||||
@form-submit="uploadDocument"
|
||||
/>
|
||||
</template>
|
||||
</ItModal>
|
||||
<div
|
||||
v-if="courseSessionsStore.canUploadCircleDocuments"
|
||||
class="mt-8 flex flex-col gap-y-4 border p-6"
|
||||
>
|
||||
<h3 class="text-blue-dark">
|
||||
{{ $t("circlePage.documents.trainerTitle") }}
|
||||
</h3>
|
||||
<div class="leading-relaxed">
|
||||
{{ $t("circlePage.documents.trainerDescription") }}
|
||||
</div>
|
||||
<a target="_blank" class="link" :href="$t('circlePage.documents.trainerLinkSrc')">
|
||||
{{ $t("circlePage.documents.trainerLinkText") }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ItModal from "@/components/ui/ItModal.vue";
|
||||
import { uploadCircleDocument } from "@/services/files";
|
||||
import { useCircleStore } from "@/stores/circle";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import type { CircleDocument, DocumentUploadData } from "@/types";
|
||||
import dialog from "@/utils/confirm-dialog";
|
||||
import log from "loglevel";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import DocumentListItem from "./DocumentListItem.vue";
|
||||
import DocumentUploadForm from "./DocumentUploadForm.vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const circleStore = useCircleStore();
|
||||
const showUploadModal = ref(false);
|
||||
const showUploadErrorMessage = ref(false);
|
||||
const isUploading = ref(false);
|
||||
const dropdownLearningSequences = computed(() =>
|
||||
circleStore.circle?.learningSequences.map((sequence) => ({
|
||||
id: sequence.id,
|
||||
name: sequence.title,
|
||||
}))
|
||||
);
|
||||
|
||||
// confirm dialog
|
||||
const { t } = useTranslation();
|
||||
const deleteDocument = async (doc: CircleDocument) => {
|
||||
const options = {
|
||||
title: t("circlePage.documents.deleteModalTitle"),
|
||||
content: t("circlePage.documents.deleteModalWarning", { title: doc.name }),
|
||||
};
|
||||
try {
|
||||
await dialog.confirm(options);
|
||||
courseSessionsStore.removeDocument(doc.id);
|
||||
} catch (e) {
|
||||
log.debug("rejected");
|
||||
}
|
||||
};
|
||||
watch(showUploadModal, () => (showUploadErrorMessage.value = false));
|
||||
async function uploadDocument(data: DocumentUploadData) {
|
||||
isUploading.value = true;
|
||||
showUploadErrorMessage.value = false;
|
||||
try {
|
||||
if (!courseSessionsStore.currentCourseSession) {
|
||||
throw new Error("No course session found");
|
||||
}
|
||||
const newDocument = await uploadCircleDocument(
|
||||
data,
|
||||
courseSessionsStore.currentCourseSession.id
|
||||
);
|
||||
courseSessionsStore.addDocument(newDocument);
|
||||
showUploadModal.value = false;
|
||||
isUploading.value = false;
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
showUploadErrorMessage.value = true;
|
||||
isUploading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -165,6 +165,11 @@ const router = createRouter({
|
|||
import("@/pages/cockpit/attendanceCheckPage/AttendanceCheckPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "documents",
|
||||
component: () => import("@/pages/cockpit/documentPage/DocumentPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,26 +2,18 @@ import { itGetCached } from "@/fetchHelpers";
|
|||
import type { CourseSessionUser, ExpertSessionUser } from "@/types";
|
||||
import log from "loglevel";
|
||||
|
||||
import { useCircleStore } from "@/stores/circle";
|
||||
import { useLearningPathStore } from "@/stores/learningPath";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export type CockpitStoreState = {
|
||||
courseSessionMembers: CourseSessionUser[] | undefined;
|
||||
selectedCircle:
|
||||
| {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
translation_key: string;
|
||||
}
|
||||
| undefined;
|
||||
circles: {
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
translation_key: string;
|
||||
}[];
|
||||
currentCourseSlug: string | undefined;
|
||||
};
|
||||
|
||||
export const useCockpitStore = defineStore({
|
||||
|
|
@ -29,29 +21,38 @@ export const useCockpitStore = defineStore({
|
|||
state: () => {
|
||||
return {
|
||||
courseSessionMembers: undefined,
|
||||
selectedCircle: undefined,
|
||||
circles: [],
|
||||
currentCourseSlug: undefined,
|
||||
} as CockpitStoreState;
|
||||
},
|
||||
actions: {
|
||||
async loadCircles(courseSlug: string, courseSessionId: number) {
|
||||
log.debug("loadCircles called");
|
||||
log.debug("loadCircles called", courseSlug, courseSessionId);
|
||||
this.currentCourseSlug = courseSlug;
|
||||
|
||||
const f = await courseCircles(courseSlug, courseSessionId);
|
||||
const f = await courseCircles(this.currentCourseSlug, courseSessionId);
|
||||
|
||||
this.circles = f.map((c) => {
|
||||
return {
|
||||
id: c.id,
|
||||
id: c.slug,
|
||||
name: c.title,
|
||||
slug: c.slug,
|
||||
translation_key: c.translation_key,
|
||||
};
|
||||
});
|
||||
|
||||
if (this.circles.length > 0) {
|
||||
this.selectedCircle = this.circles[0];
|
||||
await this.setCurrentCourseCircle(this.circles[0].id);
|
||||
}
|
||||
},
|
||||
async setCurrentCourseCircle(circleSlug: string) {
|
||||
if (!this.currentCourseSlug) {
|
||||
throw new Error("currentCourseSlug is undefined");
|
||||
}
|
||||
const circleStore = useCircleStore();
|
||||
await circleStore.loadCircle(this.currentCourseSlug, circleSlug);
|
||||
},
|
||||
async setCurrentCourseCircleFromEvent(event: { id: string }) {
|
||||
await this.setCurrentCourseCircle(event.id);
|
||||
},
|
||||
async loadCourseSessionMembers(courseSessionId: number, reload = false) {
|
||||
log.debug("loadCourseSessionMembers called");
|
||||
const users = (await itGetCached(
|
||||
|
|
@ -65,6 +66,19 @@ export const useCockpitStore = defineStore({
|
|||
return this.courseSessionMembers;
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
currentCircle: () => {
|
||||
const circleStore = useCircleStore();
|
||||
return circleStore.circle;
|
||||
},
|
||||
selectedCircle: () => {
|
||||
const circleStore = useCircleStore();
|
||||
return {
|
||||
id: circleStore.circle?.id || 0,
|
||||
name: circleStore.circle?.title || "",
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function courseCircles(courseSlug: string, courseSessionId: number) {
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ TRANSLATIONS = {
|
|||
}
|
||||
|
||||
T2L_IGNORE_FIELDS = ["Vorname", "Name", "Email", "Sprache", "Durchführungen"]
|
||||
EDONIQ_TEST_PERIOD = 14
|
||||
|
||||
|
||||
class DataImportError(Exception):
|
||||
|
|
@ -487,7 +488,9 @@ def create_or_update_course_session_edoniq_test(
|
|||
# trigger save to update due date
|
||||
cset.save()
|
||||
|
||||
cset.deadline.start = timezone.make_aware(start) + timezone.timedelta(days=10)
|
||||
cset.deadline.start = timezone.make_aware(start) + timezone.timedelta(
|
||||
days=EDONIQ_TEST_PERIOD
|
||||
)
|
||||
cset.deadline.end = None
|
||||
cset.deadline.save()
|
||||
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ class CreateOrUpdateEdoniqTestCase(TestCase):
|
|||
self._create_or_update_edonqi_test("2023-06-06T11:30:00+00:00")
|
||||
|
||||
test = CourseSessionEdoniqTest.objects.first()
|
||||
self.assertEqual(test.deadline.start.isoformat(), "2023-06-16T11:30:00+00:00")
|
||||
self.assertEqual(test.deadline.start.isoformat(), "2023-06-20T11:30:00+00:00")
|
||||
|
||||
def test_update_course_session(self):
|
||||
self._create_or_update_edonqi_test("2023-06-06T11:30:00+00:00")
|
||||
|
|
@ -318,4 +318,4 @@ class CreateOrUpdateEdoniqTestCase(TestCase):
|
|||
self.assertEqual(duedate_count, DueDate.objects.count())
|
||||
|
||||
test = CourseSessionEdoniqTest.objects.first()
|
||||
self.assertEqual(test.deadline.start.isoformat(), "2023-07-16T11:30:00+00:00")
|
||||
self.assertEqual(test.deadline.start.isoformat(), "2023-07-20T11:30:00+00:00")
|
||||
|
|
|
|||
Loading…
Reference in New Issue