351 lines
12 KiB
Vue
351 lines
12 KiB
Vue
<script setup lang="ts">
|
|
import CircleDiagram from "@/components/learningPath/CircleDiagram.vue";
|
|
import CircleOverview from "@/components/learningPath/CircleOverview.vue";
|
|
import DocumentUploadForm from "@/components/learningPath/DocumentUploadForm.vue";
|
|
import LearningSequence from "@/components/learningPath/LearningSequence.vue";
|
|
import ItModal from "@/components/ui/ItModal.vue";
|
|
import * as log from "loglevel";
|
|
import { computed, onMounted, ref, watch } from "vue";
|
|
|
|
import { uploadCircleDocument } from "@/services/files";
|
|
import { useAppStore } from "@/stores/app";
|
|
import { useCircleStore } from "@/stores/circle";
|
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
|
import type { CourseSessionUser, DocumentUploadData } from "@/types";
|
|
import { humanizeDuration } from "@/utils/humanizeDuration";
|
|
import _ from "lodash";
|
|
import { useRoute } from "vue-router";
|
|
|
|
const route = useRoute();
|
|
const courseSessionsStore = useCourseSessionsStore();
|
|
|
|
interface Props {
|
|
courseSlug: string;
|
|
circleSlug: string;
|
|
profileUser?: CourseSessionUser;
|
|
readonly?: boolean;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
readonly: false,
|
|
profileUser: undefined,
|
|
});
|
|
|
|
log.debug("CirclePage created", props.readonly, props.profileUser);
|
|
|
|
const showUploadModal = ref(false);
|
|
const showUploadErrorMessage = ref(false);
|
|
const isUploading = ref(false);
|
|
|
|
const appStore = useAppStore();
|
|
appStore.showMainNavigationBar = true;
|
|
|
|
const circleStore = useCircleStore();
|
|
|
|
const duration = computed(() => {
|
|
if (circleStore.circle) {
|
|
const minutes = _.sumBy(circleStore.circle.learningSequences, "minutes");
|
|
return humanizeDuration(minutes);
|
|
}
|
|
|
|
return "";
|
|
});
|
|
|
|
const dropdownLearningSequences = computed(() =>
|
|
circleStore.circle?.learningSequences.map((sequence) => ({
|
|
id: sequence.id,
|
|
name: sequence.title,
|
|
}))
|
|
);
|
|
|
|
watch(showUploadModal, (_v) => (showUploadErrorMessage.value = false));
|
|
|
|
onMounted(async () => {
|
|
log.debug(
|
|
"CirclePage mounted",
|
|
props.courseSlug,
|
|
props.circleSlug,
|
|
props.profileUser
|
|
);
|
|
|
|
try {
|
|
if (props.profileUser) {
|
|
await circleStore.loadCircle(
|
|
props.courseSlug,
|
|
props.circleSlug,
|
|
props.profileUser.user_id
|
|
);
|
|
} else {
|
|
await circleStore.loadCircle(props.courseSlug, props.circleSlug);
|
|
}
|
|
|
|
if (route.hash.startsWith("#ls-") || route.hash.startsWith("#lu-")) {
|
|
const slugEnd = route.hash.replace("#", "");
|
|
let wagtailPage = null;
|
|
|
|
if (slugEnd.startsWith("ls-")) {
|
|
wagtailPage = circleStore.circle?.learningSequences.find((ls) => {
|
|
return ls.slug.endsWith(slugEnd);
|
|
});
|
|
} else if (slugEnd.startsWith("lu-")) {
|
|
const learningUnits = circleStore.circle?.learningSequences.flatMap(
|
|
(ls) => ls.learningUnits
|
|
);
|
|
if (learningUnits) {
|
|
wagtailPage = learningUnits.find((lu) => {
|
|
return lu.slug.endsWith(slugEnd);
|
|
});
|
|
}
|
|
}
|
|
if (wagtailPage) {
|
|
document
|
|
.getElementById(wagtailPage.slug)
|
|
?.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
}
|
|
} catch (error) {
|
|
log.error(error);
|
|
}
|
|
});
|
|
|
|
async function uploadDocument(data: DocumentUploadData) {
|
|
isUploading.value = true;
|
|
showUploadErrorMessage.value = false;
|
|
try {
|
|
if (!courseSessionsStore.courseSessionForRoute) {
|
|
throw new Error("No course session found");
|
|
}
|
|
const newDocument = await uploadCircleDocument(
|
|
data,
|
|
courseSessionsStore.courseSessionForRoute.id
|
|
);
|
|
const courseSessionStore = useCourseSessionsStore();
|
|
courseSessionStore.addDocument(newDocument);
|
|
showUploadModal.value = false;
|
|
isUploading.value = false;
|
|
} catch (error) {
|
|
log.error(error);
|
|
showUploadErrorMessage.value = true;
|
|
isUploading.value = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<Teleport to="body">
|
|
<CircleOverview
|
|
:circle="circleStore.circle"
|
|
:show="circleStore.page === 'OVERVIEW'"
|
|
@closemodal="circleStore.page = 'INDEX'"
|
|
/>
|
|
</Teleport>
|
|
<Transition mode="out-in">
|
|
<div>
|
|
<div class="circle-container bg-gray-200">
|
|
<div v-if="profileUser" class="user-profile">
|
|
<header class="relative flex flex-row items-center bg-white p-8 shadow-xl">
|
|
<img class="mr-8 h-32 w-32 rounded-full" :src="profileUser.avatar_url" />
|
|
<div>
|
|
<h1 class="mb-2">
|
|
{{ profileUser.first_name }} {{ profileUser.last_name }}
|
|
</h1>
|
|
<div>
|
|
<router-link
|
|
class="link"
|
|
:to="`/course/${courseSlug}/cockpit/profile/${profileUser.user_id}`"
|
|
>
|
|
{{ $t("general.profileLink") }}
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
</div>
|
|
<div class="circle max-w-9xl">
|
|
<div class="flex flex-col lg:flex-row">
|
|
<div class="flex-initial bg-white px-4 py-4 lg:w-128 lg:px-8 lg:pt-4">
|
|
<router-link
|
|
v-if="!props.readonly"
|
|
:to="`/course/${props.courseSlug}/learn`"
|
|
class="btn-text inline-flex items-center px-3 py-4"
|
|
data-cy="back-to-learning-path-button"
|
|
>
|
|
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
|
<span class="inline">{{ $t("general.backToLearningPath") }}</span>
|
|
</router-link>
|
|
|
|
<h1 class="text-blue-dark text-4xl lg:text-6xl" data-cy="circle-title">
|
|
{{ circleStore.circle?.title }}
|
|
</h1>
|
|
|
|
<div class="mt-2">{{ $t("circlePage.duration") }}: {{ duration }}</div>
|
|
|
|
<div class="mt-8 w-full">
|
|
<CircleDiagram></CircleDiagram>
|
|
</div>
|
|
<div v-if="!props.readonly" class="mt-4 border-t-2 lg:hidden">
|
|
<div
|
|
class="mt-4 inline-flex items-center"
|
|
@click="circleStore.page = 'OVERVIEW'"
|
|
>
|
|
<it-icon-info class="mr-2" />
|
|
{{ $t("circlePage.circleContentBoxTitle") }}
|
|
</div>
|
|
<div v-if="!props.readonly" class="inline-flex items-center">
|
|
<it-icon-message class="mr-2" />
|
|
{{ $t("circlePage.contactExpertButton") }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="hidden lg:block">
|
|
<div class="mt-8 block border p-6">
|
|
<h3 class="text-blue-dark">
|
|
{{ $t("circlePage.circleContentBoxTitle") }}
|
|
</h3>
|
|
<div class="mt-4 leading-relaxed">
|
|
{{ circleStore.circle?.description }}
|
|
</div>
|
|
|
|
<button
|
|
class="btn-primary mt-4 text-xl"
|
|
@click="circleStore.page = 'OVERVIEW'"
|
|
>
|
|
{{ $t("circlePage.learnMore") }}
|
|
</button>
|
|
</div>
|
|
<div v-if="!props.readonly" class="mt-8 block border p-6">
|
|
<h3 class="text-blue-dark">
|
|
{{ $t("circlePage.documents.title") }}
|
|
</h3>
|
|
<div v-if="!courseSessionsStore.canUploadCircleDocuments">
|
|
<div class="mt-4 leading-relaxed">
|
|
{{ $t("circlePage.documents.userDescription") }}
|
|
</div>
|
|
</div>
|
|
<ol
|
|
v-if="
|
|
courseSessionsStore &&
|
|
courseSessionsStore.circleDocuments &&
|
|
courseSessionsStore.circleDocuments.length > 0
|
|
"
|
|
>
|
|
<li
|
|
v-for="learningSequence of courseSessionsStore.circleDocuments"
|
|
:key="learningSequence.id"
|
|
>
|
|
<h4 class="text-bold mt-4">{{ learningSequence.title }}</h4>
|
|
<ul>
|
|
<li
|
|
v-for="document of learningSequence.documents"
|
|
:key="document.url"
|
|
>
|
|
<a :href="document.url" download>
|
|
<span>{{ document.name }}</span>
|
|
</a>
|
|
<button
|
|
v-if="courseSessionsStore.canUploadCircleDocuments"
|
|
type="button"
|
|
class="relative top-[1px] ml-2 inline-block h-3 w-3 cursor-pointer leading-6"
|
|
@click="courseSessionsStore.removeDocument(document.id)"
|
|
>
|
|
<it-icon-close class="h-3 w-3"></it-icon-close>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ol>
|
|
<div v-if="courseSessionsStore.canUploadCircleDocuments">
|
|
<div class="mt-4 leading-relaxed">
|
|
{{ $t("circlePage.documents.expertDescription") }}
|
|
</div>
|
|
<button
|
|
class="btn-primary mt-4 text-xl"
|
|
@click="showUploadModal = true"
|
|
>
|
|
{{ $t("circlePage.documents.action") }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="!props.readonly" class="expert mt-8 border p-6">
|
|
<h3 class="text-blue-dark">{{ $t("circlePage.gotQuestions") }}</h3>
|
|
<div class="mt-4 leading-relaxed">
|
|
{{
|
|
$t("circlePage.contactExpertDescription", {
|
|
circleName: circleStore.circle?.title,
|
|
})
|
|
}}
|
|
</div>
|
|
<div
|
|
v-for="expert in courseSessionsStore.circleExperts"
|
|
:key="expert.user_id"
|
|
>
|
|
<div class="mt-2 mb-2 flex flex-row items-center">
|
|
<img
|
|
class="mr-2 h-[45px] rounded-full"
|
|
:src="expert.avatar_url"
|
|
/>
|
|
<p class="lg:leading-[45px]">
|
|
{{ expert.first_name }} {{ expert.last_name }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<button class="btn-secondary mt-4 text-xl">
|
|
{{ $t("circlePage.contactExpertButton") }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<ol class="flex-auto bg-gray-200 px-4 py-8 lg:px-24">
|
|
<li
|
|
v-for="learningSequence in circleStore.circle?.learningSequences ||
|
|
[]"
|
|
:key="learningSequence.translation_key"
|
|
>
|
|
<LearningSequence
|
|
:learning-sequence="learningSequence"
|
|
:readonly="props.readonly"
|
|
></LearningSequence>
|
|
</li>
|
|
</ol>
|
|
</div>
|
|
</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>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="postcss" scoped>
|
|
.circle-container {
|
|
/*background: linear-gradient(to right, white 0%, white 50%, theme(colors.gray.200) 50%, theme(colors.gray.200) 100%);*/
|
|
}
|
|
|
|
.circle {
|
|
/*max-width: 1440px;*/
|
|
/*margin: 0 auto;*/
|
|
}
|
|
|
|
.v-enter-active,
|
|
.v-leave-active {
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.v-enter-from,
|
|
.v-leave-to {
|
|
opacity: 0;
|
|
}
|
|
</style>
|