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

291 lines
10 KiB
Vue

<script setup lang="ts">
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
import LearningContentBadge from "@/pages/learningPath/LearningContentTypeBadge.vue";
import { showIcon } from "@/pages/learningPath/circlePage/learningSequenceUtils";
import { useCircleStore } from "@/stores/circle";
import type {
CourseCompletionStatus,
LearningContent,
LearningContentAssignment,
LearningContentEdoniqTest,
LearningContentInterface,
LearningSequence,
} from "@/types";
import findLast from "lodash/findLast";
import { computed } from "vue";
import { humanizeDuration } from "../../../utils/humanizeDuration";
import {
itCheckboxDefaultIconCheckedTailwindClass,
itCheckboxDefaultIconUncheckedTailwindClass,
} from "@/constants";
type Props = {
learningSequence: LearningSequence;
readonly?: boolean;
};
const props = withDefaults(defineProps<Props>(), {
readonly: false,
});
const circleStore = useCircleStore();
function toggleCompleted(learningContent: LearningContentInterface) {
let completionStatus: CourseCompletionStatus = "SUCCESS";
if (learningContent.completion_status === "SUCCESS") {
completionStatus = "FAIL";
}
circleStore.markCompletion(learningContent, completionStatus);
}
const someFinished = computed(() => {
if (props.learningSequence && circleStore.circle) {
return circleStore.circle.someFinishedInLearningSequence(
props.learningSequence.translation_key
);
}
return false;
});
const allFinished = computed(() => {
if (props.learningSequence && circleStore.circle) {
return circleStore.circle.allFinishedInLearningSequence(
props.learningSequence.translation_key
);
}
return false;
});
const continueTranslationKeyTuple = computed(() => {
if (props.learningSequence && circleStore.circle) {
const lastFinished = findLast(
circleStore.circle.flatLearningContents,
(learningContent) => {
return learningContent.completion_status === "SUCCESS";
}
);
if (!lastFinished) {
// must be the first
return [circleStore.circle.flatLearningContents[0].translation_key, true];
}
if (lastFinished && lastFinished.nextLearningContent) {
return [lastFinished.nextLearningContent.translation_key, false];
}
}
return "";
});
const learningSequenceBorderClass = computed(() => {
let result: string[] = [];
if (props.learningSequence && circleStore.circle) {
if (allFinished.value) {
result = ["border-l-4", "border-l-green-500"];
} else if (someFinished.value) {
result = ["border-l-4", "border-l-sky-500"];
} else {
result = ["border-l-gray-500"];
}
}
return result;
});
function belongsToCompetenceCertificate(lc: LearningContent) {
return (
(lc.content_type === "learnpath.LearningContentAssignment" ||
lc.content_type === "learnpath.LearningContentEdoniqTest") &&
lc.competence_certificate?.frontend_url
);
}
type LearninContentWithCompetenceCertificate =
| LearningContentAssignment
| LearningContentEdoniqTest;
function checkboxIconCheckedTailwindClass(lc: LearningContent) {
if (belongsToCompetenceCertificate(lc)) {
return "bg-[url(/static/icons/icon-lc-competence-certificate-checked.svg)]";
}
return itCheckboxDefaultIconCheckedTailwindClass;
}
function checkboxIconUncheckedTailwindClass(lc: LearningContent) {
if (belongsToCompetenceCertificate(lc)) {
return "bg-[url(/static/icons/icon-lc-competence-certificate.svg)]";
}
return itCheckboxDefaultIconUncheckedTailwindClass;
}
</script>
<template>
<div :id="learningSequence.slug" class="learning-sequence mb-8">
<div class="mb-2 flex items-center gap-4 text-blue-900">
<component :is="learningSequence.icon" v-if="showIcon(learningSequence.icon)" />
<h3 class="text-large font-semibold">
{{ learningSequence.title }}
</h3>
<div v-if="learningSequence.minutes > 0">
{{ humanizeDuration(learningSequence.minutes) }}
</div>
</div>
<ol class="border bg-white px-4 lg:px-6" :class="learningSequenceBorderClass">
<li
v-for="learningUnit in learningSequence.learningUnits"
:id="learningUnit.slug"
:key="learningUnit.id"
class="pt-3 lg:pt-6"
>
<div
v-if="learningUnit.title && !learningUnit.title_hidden"
class="lg:pg-6 flex gap-4 pb-3 text-blue-900"
>
<div class="font-semibold">
{{ learningUnit.title }}
</div>
<div v-if="learningUnit.minutes > 0" class="whitespace-nowrap">
{{ humanizeDuration(learningUnit.minutes) }}
</div>
</div>
<ol>
<li
v-for="learningContent in learningUnit.learningContents"
:key="learningContent.id"
>
<div class="pb-6">
<div class="flex items-center gap-4">
<div v-if="props.readonly">
<it-icon-check
v-if="learningContent.completion_status === 'SUCCESS'"
class="block h-8 w-8"
></it-icon-check>
<div v-else class="h-8 w-8"></div>
</div>
<ItCheckbox
v-else
:checkbox-item="{
value: learningContent.completion_status,
checked: learningContent.completion_status === 'SUCCESS',
}"
:data-cy="`${learningContent.slug}-checkbox`"
:icon-checked-tailwind-class="
checkboxIconCheckedTailwindClass(learningContent)
"
:icon-unchecked-tailwind-class="
checkboxIconUncheckedTailwindClass(learningContent)
"
@toggle="toggleCompleted(learningContent)"
@click.stop="
() => {
// when disabled open the learning content directly
if (!learningContent.can_user_self_toggle_course_completion)
circleStore.openLearningContent(learningContent);
}
"
/>
<div
class="flex flex-auto flex-col gap-4 xl:flex-row xl:justify-between"
>
<div class="xl:order-last">
<LearningContentBadge :learning-content="learningContent" />
</div>
<div>
<div v-if="props.readonly" class="w-full text-left sm:w-auto">
{{ learningContent.title }}
</div>
<button
v-else
class="w-full cursor-pointer text-left sm:w-auto"
:data-cy="`${learningContent.slug}`"
@click.stop="circleStore.openLearningContent(learningContent)"
>
{{ learningContent.title }}
</button>
</div>
</div>
</div>
<div
v-if="belongsToCompetenceCertificate(learningContent)"
class="ml-16 text-sm text-gray-800"
>
{{
$t("circlePage.Dieser Inhalt gehört zu x", {
x: (learningContent as LearninContentWithCompetenceCertificate)
?.competence_certificate?.title,
})
}}
<br />
<router-link
v-if="(learningContent as LearninContentWithCompetenceCertificate).competence_certificate?.frontend_url"
:to="(learningContent as LearninContentWithCompetenceCertificate).competence_certificate?.frontend_url ?? ''"
class="link"
data-cy="show-results"
>
{{ $t("circlePage.Im KompetenzNavi anschauen") }}
</router-link>
</div>
<div
v-if="
learningContent.translation_key === continueTranslationKeyTuple[0] &&
!props.readonly
"
class="my-4"
>
<button
class="btn-blue order-1 sm:order-none"
data-cy="ls-continue-button"
@click.stop="circleStore.openLearningContent(learningContent)"
>
<span v-if="continueTranslationKeyTuple[1]" class="whitespace-nowrap">
{{ $t("general.start") }}
</span>
<span v-else class="whitespace-nowrap">
{{ $t("general.nextStep") }}
</span>
</button>
</div>
</div>
</li>
</ol>
<div
v-if="learningUnit.children.length"
:class="{ 'cursor-pointer': !props.readonly }"
:data-cy="`${learningUnit.slug}`"
@click="!props.readonly && circleStore.openSelfEvaluation(learningUnit)"
>
<div
v-if="circleStore.calcSelfEvaluationStatus(learningUnit) === 'SUCCESS'"
class="self-evaluation-success flex items-center gap-4 pb-6"
>
<it-icon-smiley-happy class="mr-4 h-8 w-8 flex-none" data-cy="success" />
<div>{{ $t("selfEvaluation.selfEvaluationYes") }}</div>
</div>
<div
v-else-if="circleStore.calcSelfEvaluationStatus(learningUnit) === 'FAIL'"
class="self-evaluation-fail flex items-center gap-4 pb-6"
>
<it-icon-smiley-thinking class="mr-4 h-8 w-8 flex-none" data-cy="fail" />
<div>{{ $t("selfEvaluation.selfEvaluationNo") }}</div>
</div>
<div v-else class="self-evaluation-unknown flex items-center gap-4 pb-6">
<it-icon-smiley-neutral class="mr-4 h-8 w-8 flex-none" data-cy="unknown" />
<div>{{ $t("a.Selbsteinschätzung") }}</div>
</div>
</div>
<hr v-if="!learningUnit.last" class="-mx-4 text-gray-500" />
</li>
</ol>
</div>
</template>
<style scoped></style>