vbv/client/src/pages/learningPath/circlePage/CirclePage.vue

324 lines
10 KiB
Vue

<script setup lang="ts">
import type { CourseSessionUser } from "@/types";
import log from "loglevel";
import { computed, watch } from "vue";
import { useRoute } from "vue-router";
import CircleDiagram from "./CircleDiagram.vue";
import CircleOverview from "./CircleOverview.vue";
import DocumentSection from "./DocumentSection.vue";
import {
useCourseDataWithCompletion,
useCourseSessionDetailQuery,
useCurrentCourseSession,
} from "@/composables";
import { stringifyParse } from "@/utils/utils";
import { useCircleStore } from "@/stores/circle";
import LearningSequence from "@/pages/learningPath/circlePage/LearningSequence.vue";
import { useCSRFFetch } from "@/fetchHelpers";
export interface Props {
courseSlug: string;
circleSlug: string;
profileUser?: CourseSessionUser;
readonly?: boolean;
}
const courseSession = useCurrentCourseSession();
const route = useRoute();
const courseSessionDetailResult = useCourseSessionDetailQuery();
const circleStore = useCircleStore();
const props = withDefaults(defineProps<Props>(), {
readonly: false,
profileUser: undefined,
});
log.debug("CirclePage created", stringifyParse(props));
const lpQueryResult = useCourseDataWithCompletion(
props.courseSlug,
props.profileUser?.user_id
);
const circle = computed(() => {
return lpQueryResult.findCircle(props.circleSlug);
});
const circleExperts = computed(() => {
if (circle.value) {
return courseSessionDetailResult.filterCircleExperts(circle.value.slug);
}
return [];
});
const learningContentReadonly = computed(() => {
const actions = courseSession.value.actions;
return props.readonly || !actions.includes("complete-learning-content");
});
const duration = computed(() => {
// if (circleStore.circle) {
// const minutes = sumBy(circleStore.circle.learningSequences, "minutes");
// return humanizeDuration(minutes);
// }
return "";
});
const showDuration = computed(() => {
// return (
// circleStore.circle && sumBy(circleStore.circle.learningSequences, "minutes") > 0
// );
return false;
});
const showDocumentSection = computed(() => {
return lpQueryResult.course.value?.enable_circle_documents && !props.readonly;
});
const courseConfig = computed(() => {
if (lpQueryResult.course.value?.circle_contact_type === "EXPERT") {
return {
contactDescription: "circlePage.contactExpertDescription",
contactButton: "circlePage.contactExpertButton",
showContact: true,
};
} else if (lpQueryResult.course.value?.circle_contact_type === "LEARNING_MENTOR") {
return {
contactDescription: "circlePage.contactLearningMentorDescription",
contactButton: "circlePage.contactLearningMentorButton",
showContact: true,
};
} else {
return {
contactDescription: "",
contactButton: "",
showContact: true,
};
}
});
const { data: mentors } = useCSRFFetch(
`/api/mentor/${courseSession.value.id}/mentors`
).json();
interface Expert {
id: string;
email: string;
avatar_url: string;
first_name: string;
last_name: string;
}
interface Mentor {
id: number;
mentor: Expert;
}
const experts = computed<Expert[] | null>(() => {
if (courseConfig.value.showContact) {
if (lpQueryResult.course.value?.circle_contact_type === "EXPERT") {
return circleExperts.value;
} else if (lpQueryResult.course.value?.circle_contact_type === "LEARNING_MENTOR") {
if (mentors.value?.length > 0) {
return mentors.value.map((m: Mentor) => m.mentor);
}
}
}
return null;
});
watch(
() => circle.value,
() => {
if (circle.value) {
log.debug("circle loaded", circle);
try {
if (route.hash.startsWith("#ls-") || route.hash.startsWith("#lu-")) {
const slugEnd = route.hash.replace("#", "");
if (circle.value) {
let wagtailPage = null;
if (slugEnd.startsWith("ls-")) {
wagtailPage = circle.value.learning_sequences.find((ls) => {
return ls.slug.endsWith(slugEnd);
});
} else if (slugEnd.startsWith("lu-")) {
const learningUnits = circle.value.learning_sequences.flatMap(
(ls) => ls.learning_units
);
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);
}
}
}
);
</script>
<template>
<div v-if="circle">
<Teleport to="body">
<CircleOverview
:circle="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">
{{ circle?.title }}
</h1>
<div v-if="showDuration" class="mt-2">
{{ $t("circlePage.duration") }}:
{{ duration }}
</div>
<div class="mt-8 w-full">
<CircleDiagram v-if="circle" :circle="circle"></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">
{{ circle?.description }}
</div>
<button
class="btn-primary mt-4 text-xl"
@click="circleStore.page = 'OVERVIEW'"
>
{{ $t("circlePage.learnMore") }}
</button>
</div>
<DocumentSection v-if="showDocumentSection" :circle="circle" />
<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(courseConfig.contactDescription, {
circleName: circle?.title,
})
}}
</div>
<div
v-for="expert in experts"
:key="expert.id"
class="mb-6 mt-6 flex flex-row items-center space-x-2 last:mb-0"
>
<img
class="h-[48px] rounded-full"
:alt="expert.last_name"
:src="
expert.avatar_url ||
'/static/avatars/myvbv-default-avatar.png'
"
/>
<div class="flex flex-col">
{{ expert.first_name }} {{ expert.last_name }}
<a class="text-gray-800" :href="`mailto:${expert.email}`">
{{ expert.email }}
</a>
</div>
</div>
</div>
</div>
</div>
<ol class="flex-auto bg-gray-200 px-4 py-8 lg:px-24">
<li
v-for="learningSequence in circle?.learning_sequences ?? []"
:key="learningSequence.id"
>
<LearningSequence
:course-slug="props.courseSlug"
:circle="circle"
:learning-sequence="learningSequence"
:readonly="learningContentReadonly"
></LearningSequence>
</li>
</ol>
</div>
</div>
</div>
</div>
</Transition>
</div>
</template>
<style lang="postcss" scoped>
.v-enter-active,
.v-leave-active {
transition: opacity 0.3s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>