Move document upload to cockpit
This commit is contained in:
parent
7c6d448268
commit
417b2c58b8
|
|
@ -20,14 +20,19 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
withDefaults(
|
||||||
doc: {
|
defineProps<{
|
||||||
url: string;
|
doc: {
|
||||||
name: string;
|
url: string;
|
||||||
};
|
name: string;
|
||||||
canDelete: boolean;
|
};
|
||||||
subtitle: string;
|
canDelete?: boolean;
|
||||||
}>();
|
subtitle: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
canDelete: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const emit = defineEmits(["delete"]);
|
const emit = defineEmits(["delete"]);
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -9,9 +9,9 @@ const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
const circleDates = computed(() => {
|
const circleDates = computed(() => {
|
||||||
const dueDates = courseSession.value.due_dates.filter((dueDate) => {
|
const dueDates = courseSession.value.due_dates.filter((dueDate) => {
|
||||||
if (!cockpitStore.selectedCircle) return false;
|
if (!cockpitStore.currentCircle) return false;
|
||||||
return (
|
return (
|
||||||
cockpitStore.selectedCircle.translation_key == dueDate?.circle?.translation_key
|
cockpitStore.currentCircle.translation_key == dueDate?.circle?.translation_key
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return dueDates.slice(0, 4);
|
return dueDates.slice(0, 4);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { useCompetenceStore } from "@/stores/competence";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import CockpitDates from "@/pages/cockpit/cockpitPage/CockpitDates.vue";
|
import CockpitDates from "@/pages/cockpit/cockpitPage/CockpitDates.vue";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -22,12 +23,13 @@ const cockpitStore = useCockpitStore();
|
||||||
const competenceStore = useCompetenceStore();
|
const competenceStore = useCompetenceStore();
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
|
||||||
function userCountStatusForCircle(userId: string) {
|
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(
|
const criteria = competenceStore.flatPerformanceCriteria(
|
||||||
userId,
|
userId,
|
||||||
cockpitStore.selectedCircle.id
|
cockpitStore.currentCircle.id
|
||||||
);
|
);
|
||||||
return competenceStore.calcStatusCount(criteria);
|
return competenceStore.calcStatusCount(criteria);
|
||||||
}
|
}
|
||||||
|
|
@ -35,146 +37,173 @@ function userCountStatusForCircle(userId: string) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-gray-200">
|
<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">
|
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||||
<h1>Cockpit</h1>
|
<h1>Cockpit</h1>
|
||||||
<ItDropdownSelect
|
<ItDropdownSelect
|
||||||
v-model="cockpitStore.selectedCircle"
|
:model-value="cockpitStore.selectedCircle"
|
||||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||||
:items="cockpitStore.circles"
|
:items="cockpitStore.circles"
|
||||||
|
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
||||||
></ItDropdownSelect>
|
></ItDropdownSelect>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="cockpitStore.selectedCircle">
|
<!-- Status -->
|
||||||
<!-- Status -->
|
<div class="mb-4 gap-4 lg:grid lg:grid-cols-3 lg:grid-rows-none">
|
||||||
<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 class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
<div>
|
||||||
<div>
|
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
{{ $t("Trainerunterlagen") }}
|
||||||
{{ $t("Trainerunterlagen") }}
|
</h3>
|
||||||
</h3>
|
<div class="mb-4">
|
||||||
<div class="mb-4">
|
{{ $t("cockpit.trainerFilesText") }}
|
||||||
{{ $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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
<div>
|
||||||
<div>
|
<a
|
||||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
href="https://vbvbern.sharepoint.com/sites/myVBV-AFA_K-CI"
|
||||||
{{ $t("Anwesenheitskontrolle Präsenzkurse") }}
|
class="btn-secondary min-w-min"
|
||||||
</h3>
|
target="_blank"
|
||||||
<div class="mb-4">
|
>
|
||||||
{{
|
{{ $t("MS Teams öffnen") }}
|
||||||
$t(
|
</a>
|
||||||
"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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SubmissionsOverview
|
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||||
:course-session="courseSession"
|
<div>
|
||||||
:selected-circle="cockpitStore.selectedCircle.id"
|
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||||
></SubmissionsOverview>
|
{{ $t("a.Unterlagen für Teilnehmenden") }}
|
||||||
<div class="pt-4">
|
</h3>
|
||||||
<!-- progress -->
|
<div class="mb-4">
|
||||||
<div v-if="cockpitStore.courseSessionMembers" class="bg-white p-6">
|
{{ $t("a.Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.") }}
|
||||||
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
</div>
|
||||||
<ul>
|
<div
|
||||||
<ItPersonRow
|
v-if="courseSessionsStore.circleDocuments.length"
|
||||||
v-for="csu in cockpitStore.courseSessionMembers"
|
class="mb-4 flex items-center gap-x-2"
|
||||||
:key="csu.user_id + csu.session_title"
|
>
|
||||||
:name="`${csu.first_name} ${csu.last_name}`"
|
<it-icon-document />
|
||||||
:avatar-url="csu.avatar_url"
|
{{ courseSessionsStore.circleDocuments.length }} {{ $t("a.Unterlagen") }}
|
||||||
>
|
</div>
|
||||||
<template #center>
|
</div>
|
||||||
<div
|
<div>
|
||||||
class="mt-2 flex w-full flex-col items-center justify-between lg:mt-0 lg:flex-row"
|
<router-link
|
||||||
>
|
:to="`/course/${props.courseSlug}/cockpit/documents`"
|
||||||
<LearningPathDiagram
|
class="btn-secondary min-w-min"
|
||||||
v-if="
|
>
|
||||||
learningPathStore.learningPathForUser(
|
{{ $t("a.Zum Unterlagen-Upload") }}
|
||||||
props.courseSlug,
|
</router-link>
|
||||||
csu.user_id
|
</div>
|
||||||
)
|
</div>
|
||||||
"
|
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||||
:learning-path="
|
<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(
|
learningPathStore.learningPathForUser(
|
||||||
props.courseSlug,
|
props.courseSlug,
|
||||||
csu.user_id
|
csu.user_id
|
||||||
) as LearningPath
|
) as LearningPath
|
||||||
"
|
"
|
||||||
:show-circle-translation-keys="[
|
:show-circle-translation-keys="[
|
||||||
cockpitStore.selectedCircle.translation_key,
|
cockpitStore.currentCircle.translation_key,
|
||||||
]"
|
]"
|
||||||
diagram-type="singleSmall"
|
diagram-type="singleSmall"
|
||||||
class="mr-4"
|
class="mr-4"
|
||||||
></LearningPathDiagram>
|
></LearningPathDiagram>
|
||||||
<p class="lg:min-w-[150px]">
|
<p class="lg:min-w-[150px]">
|
||||||
{{ cockpitStore.selectedCircle.name }}
|
{{ cockpitStore.currentCircle.title }}
|
||||||
</p>
|
</p>
|
||||||
<div class="ml-4 flex flex-row items-center">
|
<div class="ml-4 flex flex-row items-center">
|
||||||
<div class="mr-6 flex flex-row items-center">
|
<div class="mr-6 flex flex-row items-center">
|
||||||
<it-icon-smiley-thinking
|
<it-icon-smiley-thinking
|
||||||
class="mr-2 inline-block h-8 w-8"
|
class="mr-2 inline-block h-8 w-8"
|
||||||
></it-icon-smiley-thinking>
|
></it-icon-smiley-thinking>
|
||||||
<p class="text-bold inline-block w-6">
|
<p class="text-bold inline-block w-6">
|
||||||
{{ userCountStatusForCircle(csu.user_id).FAIL }}
|
{{ userCountStatusForCircle(csu.user_id).FAIL }}
|
||||||
</p>
|
</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>
|
</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>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template #link>
|
</template>
|
||||||
<router-link
|
<template #link>
|
||||||
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
<router-link
|
||||||
class="link w-full lg:text-right"
|
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
||||||
>
|
class="link w-full lg:text-right"
|
||||||
{{ $t("general.profileLink") }}
|
>
|
||||||
</router-link>
|
{{ $t("general.profileLink") }}
|
||||||
</template>
|
</router-link>
|
||||||
</ItPersonRow>
|
</template>
|
||||||
</ul>
|
</ItPersonRow>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<span v-else class="text-lg text-orange-600">
|
</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.") }}
|
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
<div class="mt-4 leading-relaxed">
|
<div class="mt-4 leading-relaxed">
|
||||||
<template v-if="!courseSessionsStore.canUploadCircleDocuments">
|
{{ $t("circlePage.documents.userDescription") }}
|
||||||
{{ $t("circlePage.documents.userDescription") }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ $t("circlePage.documents.expertDescription") }}
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
|
|
@ -25,103 +20,16 @@
|
||||||
v-for="doc of learningSequence.documents"
|
v-for="doc of learningSequence.documents"
|
||||||
:key="doc.url"
|
:key="doc.url"
|
||||||
:subtitle="learningSequence.title"
|
:subtitle="learningSequence.title"
|
||||||
:can-delete="courseSessionsStore.canUploadCircleDocuments"
|
|
||||||
:doc="doc"
|
:doc="doc"
|
||||||
@delete="deleteDocument(doc)"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type { CircleDocument, DocumentUploadData } from "@/types";
|
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
|
||||||
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";
|
|
||||||
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
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>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,11 @@ const router = createRouter({
|
||||||
import("@/pages/cockpit/attendanceCheckPage/AttendanceCheckPage.vue"),
|
import("@/pages/cockpit/attendanceCheckPage/AttendanceCheckPage.vue"),
|
||||||
props: true,
|
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 type { CourseSessionUser, ExpertSessionUser } from "@/types";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
|
|
||||||
|
import { useCircleStore } from "@/stores/circle";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
export type CockpitStoreState = {
|
export type CockpitStoreState = {
|
||||||
courseSessionMembers: CourseSessionUser[] | undefined;
|
courseSessionMembers: CourseSessionUser[] | undefined;
|
||||||
selectedCircle:
|
|
||||||
| {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
translation_key: string;
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
circles: {
|
circles: {
|
||||||
id: number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
|
||||||
translation_key: string;
|
|
||||||
}[];
|
}[];
|
||||||
|
currentCourseSlug: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCockpitStore = defineStore({
|
export const useCockpitStore = defineStore({
|
||||||
|
|
@ -29,29 +21,38 @@ export const useCockpitStore = defineStore({
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
courseSessionMembers: undefined,
|
courseSessionMembers: undefined,
|
||||||
selectedCircle: undefined,
|
|
||||||
circles: [],
|
circles: [],
|
||||||
|
currentCourseSlug: undefined,
|
||||||
} as CockpitStoreState;
|
} as CockpitStoreState;
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadCircles(courseSlug: string, courseSessionId: number) {
|
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) => {
|
this.circles = f.map((c) => {
|
||||||
return {
|
return {
|
||||||
id: c.id,
|
id: c.slug,
|
||||||
name: c.title,
|
name: c.title,
|
||||||
slug: c.slug,
|
|
||||||
translation_key: c.translation_key,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.circles.length > 0) {
|
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) {
|
async loadCourseSessionMembers(courseSessionId: number, reload = false) {
|
||||||
log.debug("loadCourseSessionMembers called");
|
log.debug("loadCourseSessionMembers called");
|
||||||
const users = (await itGetCached(
|
const users = (await itGetCached(
|
||||||
|
|
@ -65,6 +66,19 @@ export const useCockpitStore = defineStore({
|
||||||
return this.courseSessionMembers;
|
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) {
|
async function courseCircles(courseSlug: string, courseSessionId: number) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue