309 lines
11 KiB
Vue
309 lines
11 KiB
Vue
<script setup lang="ts">
|
|
import LearningContentBadge from "@/pages/learningPath/LearningContentTypeBadge.vue";
|
|
import { showIcon } from "@/pages/learningPath/circlePage/learningSequenceUtils";
|
|
import { useCircleStore } from "@/stores/circle";
|
|
import type {
|
|
CircleType,
|
|
CourseCompletionStatus,
|
|
LearningContent,
|
|
LearningContentAssignment,
|
|
LearningContentEdoniqTest,
|
|
LearningContentWithCompletion,
|
|
LearningSequence,
|
|
} from "@/types";
|
|
import type { Ref } from "vue";
|
|
import { computed } from "vue";
|
|
import {
|
|
itCheckboxDefaultIconCheckedTailwindClass,
|
|
itCheckboxDefaultIconUncheckedTailwindClass,
|
|
} from "@/constants";
|
|
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
|
import {
|
|
allFinishedInLearningSequence,
|
|
calcSelfEvaluationStatus,
|
|
circleFlatLearningContents,
|
|
someFinishedInLearningSequence,
|
|
} from "@/services/circle";
|
|
import { useCourseDataWithCompletion } from "@/composables";
|
|
import { findLastIndex } from "lodash";
|
|
|
|
type Props = {
|
|
courseSlug: string;
|
|
learningSequence: LearningSequence;
|
|
circle: CircleType;
|
|
readonly?: boolean;
|
|
};
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
readonly: false,
|
|
});
|
|
|
|
const circleStore = useCircleStore();
|
|
|
|
const lpQueryResult = useCourseDataWithCompletion(props.courseSlug);
|
|
|
|
function toggleCompleted(learningContent: LearningContentWithCompletion) {
|
|
let completionStatus: CourseCompletionStatus = "SUCCESS";
|
|
if (learningContent.completion_status === "SUCCESS") {
|
|
completionStatus = "FAIL";
|
|
}
|
|
lpQueryResult.markCompletion(learningContent, completionStatus);
|
|
}
|
|
|
|
const someFinished = computed(() => {
|
|
if (props.learningSequence) {
|
|
return someFinishedInLearningSequence(props.learningSequence);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
const allFinished = computed(() => {
|
|
if (props.learningSequence) {
|
|
return allFinishedInLearningSequence(props.learningSequence);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
const continueTranslationKeyTuple: Ref<[string | undefined, boolean]> = computed(() => {
|
|
if (props.learningSequence) {
|
|
const flatLearningContents = circleFlatLearningContents(props.circle);
|
|
const lastFinishedIndex = findLastIndex(
|
|
circleFlatLearningContents(props.circle),
|
|
(learningContent) => {
|
|
return learningContent.completion_status === "SUCCESS";
|
|
}
|
|
);
|
|
|
|
if (lastFinishedIndex === -1) {
|
|
// must be the first
|
|
return [flatLearningContents[0].id, true];
|
|
}
|
|
|
|
if (flatLearningContents[lastFinishedIndex + 1]) {
|
|
return [flatLearningContents[lastFinishedIndex + 1].id, false];
|
|
}
|
|
}
|
|
|
|
return [undefined, false];
|
|
});
|
|
|
|
const learningSequenceBorderClass = computed(() => {
|
|
let result: string[] = [];
|
|
if (props.learningSequence) {
|
|
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", "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"
|
|
data-cy="lp-learning-sequence"
|
|
>
|
|
<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.learning_units"
|
|
:id="learningUnit.slug"
|
|
:key="learningUnit.id"
|
|
data-cy="lp-learning-unit"
|
|
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.learning_contents"
|
|
:key="learningContent.id"
|
|
data-cy="lp-learning-content"
|
|
>
|
|
<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="
|
|
(event: MouseEvent) => {
|
|
// when disabled open the learning content directly
|
|
if (!learningContent.can_user_self_toggle_course_completion) {
|
|
circleStore.openLearningContent(learningContent);
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
"
|
|
/>
|
|
<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.id === 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.performance_criteria.length"
|
|
:class="{ 'cursor-pointer': !props.readonly }"
|
|
:data-cy="`${learningUnit.slug}`"
|
|
@click="!props.readonly && circleStore.openSelfEvaluation(learningUnit)"
|
|
>
|
|
<div
|
|
v-if="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="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>
|