Merged in feature/new-lc-navigation (pull request #60)
Implement new learning content navigation/layout * Fix first part of cypress tests * Add event bus type to fix typecheck * Rework SelfEvaluation to support new layout * Fix layout * Hide lang switcher icon in lc footer Closes https://iterativ.atlassian.net/browse/VBV-319 * Fix cypress tests * Unregister event bus handler * Hide ItNavigationProgress on self evaluations with only a single step * Last fixes * Merged develop into feature/new-lc-navigation
This commit is contained in:
parent
766af5444a
commit
1d77da83da
|
|
@ -1,126 +1,108 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<LearningContentMultiLayout
|
||||||
<FeedbackIntro
|
:title="title"
|
||||||
v-if="stepNo === 0"
|
subtitle="Feedback"
|
||||||
:title="circleStore.circle?.title"
|
:learning-content-type="'feedback'"
|
||||||
:intro="
|
:show-start-button="stepNo === 0"
|
||||||
$t('feedback.intro', {
|
:show-next-button="stepNo > 0 && stepNo + 1 < numSteps"
|
||||||
name: `${courseSessionsStore.circleExperts[0].first_name} ${courseSessionsStore.circleExperts[0].last_name}`,
|
:show-previous-button="stepNo > 0"
|
||||||
})
|
:show-exit-button="stepNo + 1 === numSteps"
|
||||||
"
|
:current-step="stepNo"
|
||||||
@start="stepNo = 1"
|
:steps-count="numSteps"
|
||||||
></FeedbackIntro>
|
:start-badge-text="$t('feedback.introduction')"
|
||||||
<ItRadioGroup
|
:end-badge-text="$t('feedback.submission')"
|
||||||
v-if="stepNo === 1"
|
@previous="previousStep()"
|
||||||
v-model="wouldRecommend"
|
@next="nextStep()"
|
||||||
:label="$t('feedback.recommendLabel')"
|
>
|
||||||
class="mb-8"
|
<div class="container-medium">
|
||||||
:items="YES_NO"
|
<p v-if="stepNo === 0" class="mt-10">
|
||||||
/>
|
{{
|
||||||
<ItRadioGroup
|
$t("feedback.intro", {
|
||||||
v-if="stepNo === 2"
|
name: `${courseSessionsStore.circleExperts[0].first_name} ${courseSessionsStore.circleExperts[0].last_name}`,
|
||||||
v-model="satisfaction"
|
})
|
||||||
:label="$t('feedback.satisfactionLabel')"
|
}}
|
||||||
class="mb-8"
|
</p>
|
||||||
:items="RATINGS"
|
<p v-if="stepNo > 0 && stepNo + 1 < numSteps" class="pb-2">
|
||||||
/>
|
{{ stepLabels[stepNo] }}
|
||||||
<ItRadioGroup
|
</p>
|
||||||
v-if="stepNo === 3"
|
<ItRadioGroup
|
||||||
v-model="goalAttainment"
|
v-if="stepNo === 1"
|
||||||
:label="$t('feedback.goalAttainmentLabel')"
|
v-model="wouldRecommend"
|
||||||
class="mb-8"
|
class="mb-8"
|
||||||
:items="RATINGS"
|
:items="YES_NO"
|
||||||
/>
|
/>
|
||||||
<ItRadioGroup
|
<ItRadioGroup
|
||||||
v-if="stepNo === 4"
|
v-if="stepNo === 2"
|
||||||
v-model="proficiency"
|
v-model="satisfaction"
|
||||||
:label="$t('feedback.proficiencyLabel')"
|
class="mb-8"
|
||||||
class="mb-8"
|
:items="RATINGS"
|
||||||
:items="PERCENTAGES"
|
/>
|
||||||
/>
|
<ItRadioGroup
|
||||||
<ItRadioGroup
|
v-if="stepNo === 3"
|
||||||
v-if="stepNo === 5"
|
v-model="goalAttainment"
|
||||||
v-model="receivedMaterials"
|
class="mb-8"
|
||||||
:label="$t('feedback.receivedMaterialsLabel')"
|
:items="RATINGS"
|
||||||
class="mb-8"
|
/>
|
||||||
:items="YES_NO"
|
<ItRadioGroup
|
||||||
/>
|
v-if="stepNo === 4"
|
||||||
<ItRadioGroup
|
v-model="proficiency"
|
||||||
v-if="stepNo === 5 && receivedMaterials"
|
class="mb-8"
|
||||||
v-model="materialsRating"
|
:items="PERCENTAGES"
|
||||||
:label="$t('feedback.materialsRatingLabel')"
|
/>
|
||||||
class="mb-8"
|
<ItRadioGroup
|
||||||
:items="RATINGS"
|
v-if="stepNo === 5"
|
||||||
/>
|
v-model="receivedMaterials"
|
||||||
<ItRadioGroup
|
class="mb-8"
|
||||||
v-if="stepNo === 6"
|
:items="YES_NO"
|
||||||
v-model="instructorCompetence"
|
/>
|
||||||
:label="$t('feedback.instructorCompetenceLabel')"
|
<div v-if="stepNo === 5 && receivedMaterials">
|
||||||
class="mb-8"
|
<p class="pb-2">{{ t("feedback.materialsRatingLabel") }}</p>
|
||||||
:items="RATINGS"
|
<ItRadioGroup v-model="materialsRating" class="mb-8" :items="RATINGS" />
|
||||||
/>
|
</div>
|
||||||
<ItRadioGroup
|
<ItRadioGroup
|
||||||
v-if="stepNo === 7"
|
v-if="stepNo === 6"
|
||||||
v-model="instructorRespect"
|
v-model="instructorCompetence"
|
||||||
class="mb-8"
|
class="mb-8"
|
||||||
:label="$t('feedback.instructorRespectLabel')"
|
:items="RATINGS"
|
||||||
:items="RATINGS"
|
/>
|
||||||
/>
|
<ItRadioGroup
|
||||||
<ItTextarea
|
v-if="stepNo === 7"
|
||||||
v-if="stepNo === 8"
|
v-model="instructorRespect"
|
||||||
v-model="instructorOpenFeedback"
|
class="mb-8"
|
||||||
:label="$t('feedback.instructorOpenFeedbackLabel')"
|
:items="RATINGS"
|
||||||
class="mb-8"
|
/>
|
||||||
/>
|
<ItTextarea v-if="stepNo === 8" v-model="instructorOpenFeedback" class="mb-8" />
|
||||||
<ItTextarea
|
<ItTextarea v-if="stepNo === 9" v-model="courseNegativeFeedback" class="mb-8" />
|
||||||
v-if="stepNo === 9"
|
<ItTextarea v-if="stepNo === 10" v-model="coursePositiveFeedback" class="mb-8" />
|
||||||
v-model="courseNegativeFeedback"
|
<FeedbackCompletition
|
||||||
:label="$t('feedback.courseNegativeFeedbackLabel')"
|
v-if="stepNo === 11"
|
||||||
class="mb-8"
|
:avatar-url="courseSessionsStore.circleExperts[0].avatar_url"
|
||||||
/>
|
:title="
|
||||||
<ItTextarea
|
$t('feedback.completionTitle', {
|
||||||
v-if="stepNo === 10"
|
name: `${courseSessionsStore.circleExperts[0].first_name} ${courseSessionsStore.circleExperts[0].last_name}`,
|
||||||
v-model="coursePositiveFeedback"
|
})
|
||||||
:label="$t('feedback.coursePositiveFeedbackLabel')"
|
"
|
||||||
class="mb-8"
|
:description="$t('feedback.completionDescription')"
|
||||||
/>
|
:feedback-sent="mutationResult != null"
|
||||||
<FeedbackCompletition
|
@send-feedback="sendFeedback"
|
||||||
v-if="stepNo === 11"
|
/>
|
||||||
:avatar-url="courseSessionsStore.circleExperts[0].avatar_url"
|
</div>
|
||||||
:title="
|
</LearningContentMultiLayout>
|
||||||
$t('feedback.completionTitle', {
|
<!--
|
||||||
name: `${courseSessionsStore.circleExperts[0].first_name} ${courseSessionsStore.circleExperts[0].last_name}`,
|
<pre>
|
||||||
})
|
satisfaction {{ satisfaction }}
|
||||||
"
|
goalAttainment {{ goalAttainment }}
|
||||||
:description="$t('feedback.completionDescription')"
|
proficiency {{ proficiency }}
|
||||||
:feedback-sent="mutationResult != null"
|
receivedMaterials {{ receivedMaterials }}
|
||||||
@send-feedback="sendFeedback"
|
materialsRating {{ materialsRating }}
|
||||||
/>
|
instructorCompetence {{ instructorCompetence }}
|
||||||
|
instructorRespect {{ instructorRespect }}
|
||||||
<LearningContentNavigation
|
instructorOpenFeedback {{ instructorOpenFeedback }}
|
||||||
:show-back-button="stepNo > 0"
|
wouldRecommend {{ wouldRecommend }}
|
||||||
:show-next-button="stepNo > 0 && stepNo < MAX_STEPS - 1"
|
coursePositiveFeedback {{ coursePositiveFeedback }}
|
||||||
:question-index="stepNo"
|
courseNegativeFeedback {{ courseNegativeFeedback }}
|
||||||
:max-question-index="MAX_STEPS"
|
mutationResult: {{ mutationResult }}
|
||||||
@back="previousStep"
|
</pre> -->
|
||||||
@continue="nextStep"
|
|
||||||
></LearningContentNavigation>
|
|
||||||
<!-- <hr class="mb-10 mt-10" />
|
|
||||||
<pre>
|
|
||||||
satisfaction {{ satisfaction }}
|
|
||||||
goalAttainment {{ goalAttainment }}
|
|
||||||
proficiency {{ proficiency }}
|
|
||||||
receivedMaterials {{ receivedMaterials }}
|
|
||||||
materialsRating {{ materialsRating }}
|
|
||||||
instructorCompetence {{ instructorCompetence }}
|
|
||||||
instructorRespect {{ instructorRespect }}
|
|
||||||
instructorOpenFeedback {{ instructorOpenFeedback }}
|
|
||||||
wouldRecommend {{ wouldRecommend }}
|
|
||||||
coursePositiveFeedback {{ coursePositiveFeedback }}
|
|
||||||
courseNegativeFeedback {{ courseNegativeFeedback }}
|
|
||||||
mutationResult: {{ mutationResult }}
|
|
||||||
</pre> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -128,31 +110,52 @@ import ItRadioGroup from "@/components/ui/ItRadioGroup.vue";
|
||||||
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||||
import { graphql } from "@/gql/";
|
import { graphql } from "@/gql/";
|
||||||
import type { SendFeedbackInput } from "@/gql/graphql";
|
import type { SendFeedbackInput } from "@/gql/graphql";
|
||||||
import LearningContentNavigation from "@/pages/learningPath/learningContentPage/LearningContentNavigation.vue";
|
|
||||||
import FeedbackCompletition from "@/pages/learningPath/learningContentPage/feedback/FeedbackCompletition.vue";
|
import FeedbackCompletition from "@/pages/learningPath/learningContentPage/feedback/FeedbackCompletition.vue";
|
||||||
import FeedbackIntro from "@/pages/learningPath/learningContentPage/feedback/FeedbackIntro.vue";
|
|
||||||
import {
|
import {
|
||||||
PERCENTAGES,
|
PERCENTAGES,
|
||||||
RATINGS,
|
RATINGS,
|
||||||
YES_NO,
|
YES_NO,
|
||||||
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
||||||
|
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
||||||
import { useCircleStore } from "@/stores/circle";
|
import { useCircleStore } from "@/stores/circle";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type { LearningContent } from "@/types";
|
import type { LearningContent } from "@/types";
|
||||||
import { useMutation } from "@urql/vue";
|
import { useMutation } from "@urql/vue";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { onMounted, reactive, ref } from "vue";
|
import { computed, onMounted, reactive, ref } from "vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const props = defineProps<{ page: LearningContent }>();
|
const props = defineProps<{ page: LearningContent }>();
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
const circleStore = useCircleStore();
|
const circleStore = useCircleStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
log.debug("Feedback mounted");
|
log.debug("Feedback mounted");
|
||||||
});
|
});
|
||||||
|
|
||||||
const stepNo = ref(0);
|
const stepNo = ref(0);
|
||||||
const MAX_STEPS = 12;
|
|
||||||
|
const title = computed(
|
||||||
|
() => `«${circleStore.circle?.title}»: ${t("feedback.areYouSatisfied")}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const stepLabels = [
|
||||||
|
t("feedback.introduction"),
|
||||||
|
t("feedback.recommendLabel"),
|
||||||
|
t("feedback.satisfactionLabel"),
|
||||||
|
t("feedback.goalAttainmentLabel"),
|
||||||
|
t("feedback.proficiencyLabel"),
|
||||||
|
t("feedback.receivedMaterialsLabel"),
|
||||||
|
t("feedback.instructorCompetenceLabel"),
|
||||||
|
t("feedback.instructorRespectLabel"),
|
||||||
|
t("feedback.instructorOpenFeedbackLabel"),
|
||||||
|
t("feedback.courseNegativeFeedbackLabel"),
|
||||||
|
t("feedback.coursePositiveFeedbackLabel"),
|
||||||
|
t("feedback.submission"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const numSteps = stepLabels.length;
|
||||||
|
|
||||||
const sendFeedbackMutation = graphql(`
|
const sendFeedbackMutation = graphql(`
|
||||||
mutation SendFeedbackMutation($input: SendFeedbackInput!) {
|
mutation SendFeedbackMutation($input: SendFeedbackInput!) {
|
||||||
|
|
@ -189,9 +192,10 @@ const previousStep = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const nextStep = () => {
|
const nextStep = () => {
|
||||||
if (stepNo.value < MAX_STEPS - 1) {
|
if (stepNo.value < numSteps) {
|
||||||
stepNo.value += 1;
|
stepNo.value += 1;
|
||||||
}
|
}
|
||||||
|
log.info(`next step ${stepNo.value} of ${numSteps}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendFeedback = () => {
|
const sendFeedback = () => {
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,10 @@ export const NavigationProgressPartial: Story = {
|
||||||
endBadgeText: "Abgabe",
|
endBadgeText: "Abgabe",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NavigationProgressNoStartEndBadge: Story = {
|
||||||
|
args: {
|
||||||
|
steps: 5,
|
||||||
|
currentStep: 3,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,28 @@
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
// Number of steps including the start and end badge
|
||||||
steps: number;
|
steps: number;
|
||||||
|
// Current step, starting at 0 for the start badge
|
||||||
currentStep: number;
|
currentStep: number;
|
||||||
startBadgeText: string;
|
startBadgeText?: string;
|
||||||
endBadgeText: string;
|
endBadgeText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
startBadgeText: undefined,
|
||||||
|
endBadgeText: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasStartBadge = computed(() => typeof props.startBadgeText !== "undefined");
|
||||||
|
const hasEndBadge = computed(() => typeof props.endBadgeText !== "undefined");
|
||||||
|
|
||||||
|
const numNumberSteps = computed(
|
||||||
|
() => props.steps - Number(hasStartBadge.value) - Number(hasEndBadge.value)
|
||||||
|
);
|
||||||
|
|
||||||
function getPillClasses(step: number) {
|
function getPillClasses(step: number) {
|
||||||
if (step == props.currentStep) {
|
if (step + Number(hasStartBadge.value) == props.currentStep) {
|
||||||
return "bg-sky-500 text-bold";
|
return "bg-sky-500 text-bold";
|
||||||
} else if (step < props.currentStep) {
|
} else if (step < props.currentStep) {
|
||||||
return "bg-green-500";
|
return "bg-green-500";
|
||||||
|
|
@ -20,16 +32,16 @@ function getPillClasses(step: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const startBadgeClasses = computed(() => {
|
const startBadgeClasses = computed(() => {
|
||||||
if (0 == props.currentStep) {
|
if (0 === props.currentStep) {
|
||||||
return "bg-sky-500 text-bold";
|
return "bg-sky-500 text-bold";
|
||||||
}
|
}
|
||||||
return "bg-green-500";
|
return "bg-green-500";
|
||||||
});
|
});
|
||||||
|
|
||||||
const endBadgeClasses = computed(() => {
|
const endBadgeClasses = computed(() => {
|
||||||
if (props.steps + 1 == props.currentStep) {
|
if (props.steps === props.currentStep + 1) {
|
||||||
return "bg-sky-500 text-bold";
|
return "bg-sky-500 text-bold";
|
||||||
} else if (props.steps + 2 == props.currentStep) {
|
} else if (props.steps === props.currentStep) {
|
||||||
return "bg-green-500 text-bold";
|
return "bg-green-500 text-bold";
|
||||||
}
|
}
|
||||||
return "border";
|
return "border";
|
||||||
|
|
@ -39,23 +51,28 @@ const endBadgeClasses = computed(() => {
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-row text-sm">
|
<div class="flex flex-row text-sm">
|
||||||
<div
|
<div
|
||||||
class="inline-flex h-7 items-center justify-center rounded-3xl px-3"
|
v-if="props.startBadgeText"
|
||||||
|
class="inline-flex h-7 items-center justify-center whitespace-nowrap rounded-3xl px-3 text-sm"
|
||||||
:class="startBadgeClasses"
|
:class="startBadgeClasses"
|
||||||
>
|
>
|
||||||
{{ props.startBadgeText }}
|
{{ props.startBadgeText }}
|
||||||
</div>
|
</div>
|
||||||
<div v-for="step in props.steps" :key="step" class="flex flex-row">
|
<div v-for="(_, step) in numNumberSteps" :key="step" class="flex flex-row">
|
||||||
<hr class="w-16 self-center border border-[1px] border-gray-400" />
|
<hr
|
||||||
|
v-if="hasStartBadge || step !== 0"
|
||||||
|
class="w-8 self-center border border-gray-400"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
class="inline-flex h-7 w-7 items-center justify-center rounded-full px-3 py-1"
|
class="inline-flex h-7 w-7 items-center justify-center rounded-full px-3 py-1 text-sm"
|
||||||
:class="getPillClasses(step)"
|
:class="getPillClasses(step)"
|
||||||
>
|
>
|
||||||
{{ step }}
|
{{ step + 1 }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="w-16 self-center border border-gray-400" />
|
<hr v-if="hasEndBadge" class="w-8 self-center border border-gray-400" />
|
||||||
<div
|
<div
|
||||||
class="inline-flex h-7 items-center justify-center rounded-3xl px-3"
|
v-if="endBadgeText"
|
||||||
|
class="inline-flex h-7 items-center justify-center whitespace-nowrap rounded-3xl px-3 text-sm"
|
||||||
:class="endBadgeClasses"
|
:class="endBadgeClasses"
|
||||||
>
|
>
|
||||||
{{ props.endBadgeText }}
|
{{ props.endBadgeText }}
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,14 @@ import type { RadioItem } from "@/pages/learningPath/learningContentPage/feedbac
|
||||||
|
|
||||||
import { RadioGroup, RadioGroupLabel, RadioGroupOption } from "@headlessui/vue";
|
import { RadioGroup, RadioGroupLabel, RadioGroupOption } from "@headlessui/vue";
|
||||||
|
|
||||||
defineProps<{
|
interface Props {
|
||||||
modelValue: any;
|
modelValue: any;
|
||||||
items: RadioItem<any>[];
|
items: RadioItem<any>[];
|
||||||
label: string;
|
label?: string;
|
||||||
}>();
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
label: undefined,
|
||||||
|
});
|
||||||
defineEmits(["update:modelValue"]);
|
defineEmits(["update:modelValue"]);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="heading-1 mb-8 block">{{ label }}</h2>
|
<h2 v-if="label" class="heading-1 mb-8 block">{{ label }}</h2>
|
||||||
<textarea
|
<textarea
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
class="h-40 w-full border-gray-500"
|
class="h-40 w-full border-gray-500"
|
||||||
|
|
@ -10,10 +10,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
interface Props {
|
||||||
modelValue: string;
|
modelValue: string;
|
||||||
label: string;
|
label?: string;
|
||||||
}>();
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
label: undefined,
|
||||||
|
});
|
||||||
const emit = defineEmits(["update:modelValue"]);
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
const onInput = (event: Event) => {
|
const onInput = (event: Event) => {
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@
|
||||||
"instructorOpenFeedbackLabel": "Was ich dem Kursleiter sonst noch sagen wollte:",
|
"instructorOpenFeedbackLabel": "Was ich dem Kursleiter sonst noch sagen wollte:",
|
||||||
"instructorRespectLabel": "Fragen und Anregungen der Kursteilnehmenden wurden ernst genommen und aufgegriffen.",
|
"instructorRespectLabel": "Fragen und Anregungen der Kursteilnehmenden wurden ernst genommen und aufgegriffen.",
|
||||||
"intro": "{name}, dein/e Trainer/-in, bittet dich, ihm/ihr Feedback zu geben. Das ist freiwillig, würde aber ihm/ihr helfen, deine Lernerlebniss zu verbessern.",
|
"intro": "{name}, dein/e Trainer/-in, bittet dich, ihm/ihr Feedback zu geben. Das ist freiwillig, würde aber ihm/ihr helfen, deine Lernerlebniss zu verbessern.",
|
||||||
|
"introduction": "Einleitung",
|
||||||
"materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?",
|
"materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?",
|
||||||
"noFeedbacks": "Es wurden noch keine Feedbacks abgegeben",
|
"noFeedbacks": "Es wurden noch keine Feedbacks abgegeben",
|
||||||
"proficiencyLabel": "Wie beurteilen Sie Ihre Sicherheit bezüglichen den Themen nach dem Kurs?",
|
"proficiencyLabel": "Wie beurteilen Sie Ihre Sicherheit bezüglichen den Themen nach dem Kurs?",
|
||||||
|
|
@ -80,6 +81,7 @@
|
||||||
"sendFeedback": "Feedback abschicken",
|
"sendFeedback": "Feedback abschicken",
|
||||||
"sentByUsers": "Von {count} Teilnehmern ausgefüllt",
|
"sentByUsers": "Von {count} Teilnehmern ausgefüllt",
|
||||||
"showDetails": "Details anzeigen",
|
"showDetails": "Details anzeigen",
|
||||||
|
"submission": "Abgabe",
|
||||||
"unhappy": "Unzufrieden",
|
"unhappy": "Unzufrieden",
|
||||||
"veryHappy": "Sehr zufrieden",
|
"veryHappy": "Sehr zufrieden",
|
||||||
"veryUnhappy": "Sehr unzufrieden"
|
"veryUnhappy": "Sehr unzufrieden"
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,7 @@ function close() {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="singleCriteria" class="absolute bottom-0 top-0 w-full bg-white">
|
<div v-if="singleCriteria" class="absolute bottom-0 top-0 w-full bg-white">
|
||||||
<LearningContentContainer
|
<LearningContentContainer @exit="router.back()">
|
||||||
:title="''"
|
|
||||||
:next-button-text="$t('general.save')"
|
|
||||||
@exit="router.back()"
|
|
||||||
@next="router.back()"
|
|
||||||
>
|
|
||||||
<div v-if="singleCriteria" class="container-medium">
|
<div v-if="singleCriteria" class="container-medium">
|
||||||
<div class="mt-4 border p-6 lg:mt-8 lg:p-12">
|
<div class="mt-4 border p-6 lg:mt-8 lg:p-12">
|
||||||
<h2 class="heading-2">
|
<h2 class="heading-2">
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ import { useCircleStore } from "@/stores/circle";
|
||||||
import type { LearningContent, LearningContentType } from "@/types";
|
import type { LearningContent, LearningContentType } from "@/types";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import type { Component } from "vue";
|
import type { Component } from "vue";
|
||||||
import { computed } from "vue";
|
import { computed, onUnmounted } from "vue";
|
||||||
|
|
||||||
import AssignmentBlock from "@/pages/learningPath/learningContentPage/blocks/AssignmentBlock.vue";
|
import AssignmentBlock from "@/pages/learningPath/learningContentPage/blocks/AssignmentBlock.vue";
|
||||||
import AttendanceDayBlock from "@/pages/learningPath/learningContentPage/blocks/AttendanceDayBlock.vue";
|
import AttendanceDayBlock from "@/pages/learningPath/learningContentPage/blocks/AttendanceDayBlock.vue";
|
||||||
|
import eventBus from "@/utils/eventBus";
|
||||||
import DescriptionBlock from "./blocks/DescriptionBlock.vue";
|
import DescriptionBlock from "./blocks/DescriptionBlock.vue";
|
||||||
import DescriptionTextBlock from "./blocks/DescriptionTextBlock.vue";
|
import DescriptionTextBlock from "./blocks/DescriptionTextBlock.vue";
|
||||||
import FeedbackBlock from "./blocks/FeedbackBlock.vue";
|
import FeedbackBlock from "./blocks/FeedbackBlock.vue";
|
||||||
|
|
@ -57,20 +58,21 @@ const component = computed(() => {
|
||||||
return DEFAULT_BLOCK;
|
return DEFAULT_BLOCK;
|
||||||
});
|
});
|
||||||
|
|
||||||
const showNavigationBorder = computed(() => {
|
function handleFinishedLearningContent() {
|
||||||
return block.value?.type !== "feedback";
|
circleStore.continueFromLearningContent(props.learningContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.on("finishedLearningContent", handleFinishedLearningContent);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
eventBus.off("finishedLearningContent", handleFinishedLearningContent);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LearningContentContainer
|
<LearningContentContainer
|
||||||
v-if="block"
|
v-if="block"
|
||||||
:title="learningContent.title"
|
|
||||||
:next-button-text="$t('learningContent.completeAndContinue')"
|
|
||||||
:learning-content-block="learningContent.contents[0]"
|
|
||||||
:show-border="showNavigationBorder"
|
|
||||||
@exit="circleStore.closeLearningContent(props.learningContent)"
|
@exit="circleStore.closeLearningContent(props.learningContent)"
|
||||||
@next="circleStore.continueFromLearningContent(props.learningContent)"
|
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<component
|
<component
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,18 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LearningContentBadge from "@/pages/learningPath/LearningContentTypeBadge.vue";
|
|
||||||
import type { LearningContentBlock } from "@/types";
|
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
|
||||||
log.debug("LearningContentContainer.vue setup");
|
log.debug("LearningContentContainer.vue setup");
|
||||||
|
|
||||||
interface Props {
|
defineEmits(["exit"]);
|
||||||
title: string;
|
|
||||||
nextButtonText: string;
|
|
||||||
learningContentBlock: LearningContentBlock | null;
|
|
||||||
showBorder: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _props = withDefaults(defineProps<Props>(), {
|
|
||||||
title: "",
|
|
||||||
nextButtonText: "",
|
|
||||||
learningContentBlock: null,
|
|
||||||
showBorder: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
defineEmits(["next", "exit"]);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="h-full"></div>
|
<div class="h-full"></div>
|
||||||
<!-- just here to not make the footer jump during the transition -->
|
|
||||||
<div class="absolute bottom-0 top-0 w-full bg-white">
|
<div class="absolute bottom-0 top-0 w-full bg-white">
|
||||||
<div class="h-content overflow-y-scroll">
|
<div class="h-content overflow-y-auto">
|
||||||
<header
|
<header
|
||||||
class="relative flex h-12 w-full items-center justify-between bg-white px-4 py-4 lg:h-16 lg:px-8"
|
class="relative flex h-12 w-full items-center justify-between bg-white px-4 lg:h-16 lg:px-8"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -42,30 +25,6 @@ defineEmits(["next", "exit"]);
|
||||||
</header>
|
</header>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<nav
|
|
||||||
class="nav flex items-center justify-between bg-white px-4"
|
|
||||||
:class="{ 'border-t': showBorder }"
|
|
||||||
>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<LearningContentBadge
|
|
||||||
v-if="learningContentBlock && learningContentBlock.type"
|
|
||||||
:learning-content-type="learningContentBlock.type"
|
|
||||||
class="mr-2 hidden lg:flex"
|
|
||||||
/>
|
|
||||||
<h1 class="hidden text-base font-normal lg:inline-block" data-cy="ln-title">
|
|
||||||
{{ title }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn-blue btn-large-icon z-10"
|
|
||||||
data-cy="complete-and-continue"
|
|
||||||
@click="$emit('next')"
|
|
||||||
>
|
|
||||||
{{ nextButtonText }}
|
|
||||||
<it-icon-check class="ml-2"></it-icon-check>
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
interface Props {
|
|
||||||
showBackButton: boolean;
|
|
||||||
showNextButton: boolean;
|
|
||||||
questionIndex: number;
|
|
||||||
maxQuestionIndex: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _props = withDefaults(defineProps<Props>(), {
|
|
||||||
showBackButton: true,
|
|
||||||
showNextButton: true,
|
|
||||||
questionIndex: 0,
|
|
||||||
maxQuestionIndex: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
defineEmits(["back", "continue"]);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<nav class="mb-4 mt-16 flex">
|
|
||||||
<button
|
|
||||||
v-if="showBackButton"
|
|
||||||
class="btn-secondary mr-2 flex items-center"
|
|
||||||
data-cy="previous-step"
|
|
||||||
@click="$emit('back')"
|
|
||||||
>
|
|
||||||
<it-icon-arrow-left class="mr-2 h-6 w-6"></it-icon-arrow-left>
|
|
||||||
{{ $t("general.backCapitalized") }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="showNextButton"
|
|
||||||
class="btn-secondary flex items-center"
|
|
||||||
data-cy="next-step"
|
|
||||||
@click="$emit('continue')"
|
|
||||||
>
|
|
||||||
{{ $t("general.next") }}
|
|
||||||
<it-icon-arrow-right class="ml-2 h-6 w-6"></it-icon-arrow-right>
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
<div
|
|
||||||
role="progressbar"
|
|
||||||
:aria-valuenow="questionIndex + 1"
|
|
||||||
:aria-valuemax="maxQuestionIndex"
|
|
||||||
:aria-valuemin="1"
|
|
||||||
class="absolute bottom-[86px] left-0 right-0 inline-flex h-1 gap-1 lg:left-4 lg:right-4"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-for="i in maxQuestionIndex"
|
|
||||||
:key="i"
|
|
||||||
class="w-full"
|
|
||||||
:class="{
|
|
||||||
'bg-sky-500': i <= questionIndex + 1,
|
|
||||||
'bg-gray-400': i > questionIndex + 1,
|
|
||||||
}"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<AssignmentView
|
<AssignmentView
|
||||||
|
class="container-medium"
|
||||||
:assignment-id="props.value.assignment"
|
:assignment-id="props.value.assignment"
|
||||||
:learning-content="props.content"
|
:learning-content="props.content"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container-medium">
|
<FeedbackForm :page="content" />
|
||||||
<div class="lg:mt-12">
|
|
||||||
<FeedbackForm :page="content" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -14,7 +10,7 @@ interface Value {
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
value: Value;
|
value: Value;
|
||||||
content: LearningContent;
|
content: LearningContent;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="h-screen">
|
<LearningContentSimpleLayout
|
||||||
<iframe width="100%" height="100%" scrolling="no" :src="value.url" />
|
:subtitle="learningContentTypeData('resource').title"
|
||||||
</div>
|
:learning-content-type="'resource'"
|
||||||
|
>
|
||||||
|
<div class="h-screen">
|
||||||
|
<iframe width="100%" height="100%" scrolling="no" :src="value.url" />
|
||||||
|
</div>
|
||||||
|
</LearningContentSimpleLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
|
||||||
import type { LearningContent } from "@/types";
|
import type { LearningContent } from "@/types";
|
||||||
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
|
|
||||||
interface Value {
|
interface Value {
|
||||||
url: string;
|
url: string;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container-medium">
|
<LearningContentSimpleLayout
|
||||||
<div class="lg:mt-8">
|
:title="content.title"
|
||||||
<h1>{{ content.title }}</h1>
|
:subtitle="learningContentTypeData('media_library').title"
|
||||||
|
:learning-content-type="'media_library'"
|
||||||
|
>
|
||||||
|
<div class="container-medium">
|
||||||
<p class="text-large my-4 lg:my-8">{{ value.description }}</p>
|
<p class="text-large my-4 lg:my-8">{{ value.description }}</p>
|
||||||
<router-link :to="`${value.url}?back=${route.path}`" class="button btn-primary">
|
<router-link :to="`${value.url}?back=${route.path}`" class="button btn-primary">
|
||||||
Mediathek öffnen
|
Mediathek öffnen
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</LearningContentSimpleLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
|
||||||
import type { LearningContent } from "@/types";
|
import type { LearningContent } from "@/types";
|
||||||
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container-medium">
|
<LearningContentSimpleLayout
|
||||||
<div class="lg:mt-8">
|
:title="content.title"
|
||||||
<p class="text-large my-4">{{ value.description }}</p>
|
:subtitle="learningContentTypeData('placeholder').title"
|
||||||
<h1>{{ content.title }}</h1>
|
:learning-content-type="'placeholder'"
|
||||||
</div>
|
></LearningContentSimpleLayout>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
|
||||||
import type { LearningContent } from "@/types";
|
import type { LearningContent } from "@/types";
|
||||||
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
|
|
||||||
interface Value {
|
interface Value {
|
||||||
description: string;
|
description: string;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container-medium">
|
<LearningContentSimpleLayout
|
||||||
<iframe
|
:title="content.title"
|
||||||
class="mt-8 aspect-video w-full"
|
:subtitle="learningContentTypeData('video').title"
|
||||||
:src="value.url"
|
:learning-content-type="'video'"
|
||||||
:title="content.title"
|
>
|
||||||
frameborder="0"
|
<div class="container-medium">
|
||||||
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
|
<iframe
|
||||||
allowfullscreen
|
class="mt-8 aspect-video w-full"
|
||||||
></iframe>
|
:src="value.url"
|
||||||
</div>
|
:title="content.title"
|
||||||
|
frameborder="0"
|
||||||
|
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowfullscreen
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</LearningContentSimpleLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
|
||||||
import type { LearningContent } from "@/types";
|
import type { LearningContent } from "@/types";
|
||||||
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
|
|
||||||
interface Value {
|
interface Value {
|
||||||
url: string;
|
url: string;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h1 class="mb-8">«{{ title }}»: {{ $t("feedback.areYouSatisfied") }}</h1>
|
|
||||||
<p class="mb-8">{{ intro }}</p>
|
|
||||||
<button class="btn-primary" @click="$emit('start')">
|
|
||||||
{{ $t("general.start") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
interface Props {
|
|
||||||
title: string;
|
|
||||||
intro: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _props = withDefaults(defineProps<Props>(), {
|
|
||||||
title: "",
|
|
||||||
intro: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
defineEmits(["start"]);
|
|
||||||
</script>
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import eventBus from "@/utils/eventBus";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
showStartButton?: boolean;
|
||||||
|
showPreviousButton?: boolean;
|
||||||
|
showNextButton?: boolean;
|
||||||
|
showExitButton?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
showStartButton: false,
|
||||||
|
showPreviousButton: false,
|
||||||
|
showNextButton: false,
|
||||||
|
showExitButton: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits(["start", "previous", "next"]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<nav class="nav absolute bottom-0 w-full border-t px-6 py-4">
|
||||||
|
<div class="relative z-10 flex flex-row place-content-end bg-white">
|
||||||
|
<button
|
||||||
|
v-if="props.showPreviousButton"
|
||||||
|
class="btn-secondary mr-2 flex items-center"
|
||||||
|
data-cy="previous-step"
|
||||||
|
@click="$emit('previous')"
|
||||||
|
>
|
||||||
|
<it-icon-arrow-left class="mr-2 h-6 w-6"></it-icon-arrow-left>
|
||||||
|
{{ $t("general.backCapitalized") }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="props.showNextButton"
|
||||||
|
class="btn-blue z-10 flex items-center"
|
||||||
|
data-cy="next-step"
|
||||||
|
@click="$emit('next')"
|
||||||
|
>
|
||||||
|
{{ $t("general.next") }}
|
||||||
|
<it-icon-arrow-right class="ml-2 h-6 w-6"></it-icon-arrow-right>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="props.showStartButton"
|
||||||
|
type="button"
|
||||||
|
class="btn-blue z-10 flex items-center"
|
||||||
|
data-cy="start"
|
||||||
|
@click="$emit('start')"
|
||||||
|
>
|
||||||
|
{{ $t("general.start") }}
|
||||||
|
<it-icon-arrow-right class="ml-2 h-6 w-6"></it-icon-arrow-right>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="props.showExitButton"
|
||||||
|
type="button"
|
||||||
|
class="btn-blue z-10 flex items-center"
|
||||||
|
data-cy="complete-and-continue"
|
||||||
|
@click="eventBus.emit('finishedLearningContent', true)"
|
||||||
|
>
|
||||||
|
{{ "Als erledigt markieren" }}
|
||||||
|
<it-icon-check class="ml-2"></it-icon-check>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
// Layout for learning contents with multiple steps
|
||||||
|
import ItNavigationProgress from "@/components/ui/ItNavigationProgress.vue";
|
||||||
|
import LearningContentFooter from "@/pages/learningPath/learningContentPage/layouts/LearningContentFooter.vue";
|
||||||
|
import type { LearningContentType } from "@/types";
|
||||||
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string | undefined;
|
||||||
|
subtitle: string;
|
||||||
|
learningContentType: LearningContentType;
|
||||||
|
showStartButton: boolean;
|
||||||
|
showPreviousButton: boolean;
|
||||||
|
showNextButton: boolean;
|
||||||
|
showExitButton: boolean;
|
||||||
|
currentStep: number;
|
||||||
|
stepsCount: number;
|
||||||
|
startBadgeText?: string;
|
||||||
|
endBadgeText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
title: undefined,
|
||||||
|
startBadgeText: undefined,
|
||||||
|
endBadgeText: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["previous", "next", "exit"]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container-medium">
|
||||||
|
<div
|
||||||
|
v-if="props.learningContentType !== 'placeholder'"
|
||||||
|
class="flex h-min w-min items-center gap-2 rounded-full pb-10"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="learningContentTypeData(props.learningContentType).icon"
|
||||||
|
class="h-6 w-6 text-gray-900"
|
||||||
|
></component>
|
||||||
|
<p class="whitespace-nowrap text-gray-900">
|
||||||
|
{{ props.subtitle }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 v-if="props.title" class="pb-6 text-3xl" data-cy="ln-title">
|
||||||
|
{{ props.title }}
|
||||||
|
</h2>
|
||||||
|
<ItNavigationProgress
|
||||||
|
v-if="props.stepsCount > 1"
|
||||||
|
:current-step="props.currentStep"
|
||||||
|
:start-badge-text="props.startBadgeText"
|
||||||
|
:steps="stepsCount"
|
||||||
|
:end-badge-text="props.endBadgeText"
|
||||||
|
class="overflow-hidden pb-12"
|
||||||
|
></ItNavigationProgress>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<LearningContentFooter
|
||||||
|
:show-start-button="props.showStartButton"
|
||||||
|
:show-next-button="props.showNextButton"
|
||||||
|
:show-previous-button="props.showPreviousButton"
|
||||||
|
:show-exit-button="props.showExitButton"
|
||||||
|
@previous="emit('previous')"
|
||||||
|
@next="emit('next')"
|
||||||
|
@start="emit('next')"
|
||||||
|
></LearningContentFooter>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
// Basic layout for a learning content that only has a single step
|
||||||
|
import LearningContentFooter from "@/pages/learningPath/learningContentPage/layouts/LearningContentFooter.vue";
|
||||||
|
import type { LearningContentType } from "@/types";
|
||||||
|
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string | undefined;
|
||||||
|
subtitle: string;
|
||||||
|
learningContentType: LearningContentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
title: undefined,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container-medium">
|
||||||
|
<div
|
||||||
|
v-if="props.learningContentType !== 'placeholder'"
|
||||||
|
class="flex h-min w-full items-center gap-2 pb-8"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="learningContentTypeData(props.learningContentType).icon"
|
||||||
|
class="h-6 w-6 text-gray-900"
|
||||||
|
></component>
|
||||||
|
<p class="whitespace-nowrap text-gray-900">
|
||||||
|
{{ props.subtitle }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 v-if="props.title" data-cy="ln-title">{{ props.title }}</h2>
|
||||||
|
</div>
|
||||||
|
<slot></slot>
|
||||||
|
<LearningContentFooter></LearningContentFooter>
|
||||||
|
</template>
|
||||||
|
|
@ -5,9 +5,10 @@ import * as log from "loglevel";
|
||||||
|
|
||||||
import { COMPLETION_FAILURE, COMPLETION_SUCCESS } from "@/constants";
|
import { COMPLETION_FAILURE, COMPLETION_SUCCESS } from "@/constants";
|
||||||
import LearningContentContainer from "@/pages/learningPath/learningContentPage/LearningContentContainer.vue";
|
import LearningContentContainer from "@/pages/learningPath/learningContentPage/LearningContentContainer.vue";
|
||||||
|
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
import eventBus from "@/utils/eventBus";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
import LearningContentNavigation from "../learningContentPage/LearningContentNavigation.vue";
|
|
||||||
|
|
||||||
log.debug("LearningContent.vue setup");
|
log.debug("LearningContent.vue setup");
|
||||||
|
|
||||||
|
|
@ -24,7 +25,14 @@ const props = defineProps<{
|
||||||
|
|
||||||
const questions = computed(() => props.learningUnit?.children);
|
const questions = computed(() => props.learningUnit?.children);
|
||||||
const currentQuestion = computed(() => questions.value[state.questionIndex]);
|
const currentQuestion = computed(() => questions.value[state.questionIndex]);
|
||||||
const showBackButton = computed(() => state.questionIndex != 0);
|
const showPreviousButton = computed(() => state.questionIndex != 0);
|
||||||
|
const showNextButton = computed(
|
||||||
|
() => state.questionIndex + 1 < questions.value?.length && questions.value?.length > 1
|
||||||
|
);
|
||||||
|
const showExitButton = computed(
|
||||||
|
() =>
|
||||||
|
questions.value?.length === 1 || questions.value?.length === state.questionIndex + 1
|
||||||
|
);
|
||||||
|
|
||||||
function handleContinue() {
|
function handleContinue() {
|
||||||
log.debug("handleContinue");
|
log.debug("handleContinue");
|
||||||
|
|
@ -43,74 +51,82 @@ function handleBack() {
|
||||||
state.questionIndex -= 1;
|
state.questionIndex -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventBus.on("finishedLearningContent", () => {
|
||||||
|
circleStore.closeSelfEvaluation(props.learningUnit);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="learningUnit">
|
<div v-if="learningUnit">
|
||||||
<LearningContentContainer
|
<LearningContentContainer
|
||||||
:title="$t('selfEvaluation.title', { title: learningUnit.title })"
|
|
||||||
:next-button-text="$t('learningContent.completeAndContinue')"
|
|
||||||
:show-border="false"
|
|
||||||
@exit="circleStore.closeSelfEvaluation(props.learningUnit)"
|
@exit="circleStore.closeSelfEvaluation(props.learningUnit)"
|
||||||
@next="circleStore.closeSelfEvaluation(props.learningUnit)"
|
|
||||||
>
|
>
|
||||||
<div class="container-medium h-full">
|
<LearningContentMultiLayout
|
||||||
<div class="mt-8 lg:mt-40">
|
:current-step="state.questionIndex"
|
||||||
<h2 class="heading-2">
|
:subtitle="$t('selfEvaluation.title')"
|
||||||
{{ currentQuestion.competence_id }} {{ currentQuestion.title }}
|
:title="$t('selfEvaluation.title', { title: learningUnit.title })"
|
||||||
</h2>
|
learning-content-type="learningmodule"
|
||||||
|
:steps-count="questions.length"
|
||||||
|
:show-next-button="showNextButton"
|
||||||
|
:show-exit-button="showExitButton"
|
||||||
|
:show-start-button="false"
|
||||||
|
:show-previous-button="showPreviousButton"
|
||||||
|
@previous="handleBack()"
|
||||||
|
@next="handleContinue()"
|
||||||
|
>
|
||||||
|
<div class="h-full">
|
||||||
|
<div class="mt-8">
|
||||||
|
<h3 class="heading-3">
|
||||||
|
{{ currentQuestion.competence_id }} {{ currentQuestion.title }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="mt-4 flex flex-col justify-between gap-8 lg:mt-8 lg:flex-row lg:gap-12"
|
class="mt-4 flex flex-col justify-between gap-8 lg:mt-8 lg:flex-row lg:gap-12"
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="inline-flex flex-1 items-center border p-4 text-left"
|
|
||||||
:class="{
|
|
||||||
'border-green-500': currentQuestion.completion_status === 'success',
|
|
||||||
'border-2': currentQuestion.completion_status === 'success',
|
|
||||||
}"
|
|
||||||
data-cy="success"
|
|
||||||
@click="circleStore.markCompletion(currentQuestion, COMPLETION_SUCCESS)"
|
|
||||||
>
|
>
|
||||||
<it-icon-smiley-happy class="mr-4 h-16 w-16"></it-icon-smiley-happy>
|
<button
|
||||||
<span class="text-large font-bold">{{ $t("selfEvaluation.yes") }}.</span>
|
class="inline-flex flex-1 items-center border p-4 text-left"
|
||||||
</button>
|
:class="{
|
||||||
<button
|
'border-green-500': currentQuestion.completion_status === 'success',
|
||||||
class="inline-flex flex-1 items-center border p-4 text-left"
|
'border-2': currentQuestion.completion_status === 'success',
|
||||||
:class="{
|
}"
|
||||||
'border-orange-500':
|
data-cy="success"
|
||||||
currentQuestion.completion_status === COMPLETION_FAILURE,
|
@click="circleStore.markCompletion(currentQuestion, COMPLETION_SUCCESS)"
|
||||||
'border-2': currentQuestion.completion_status === COMPLETION_FAILURE,
|
>
|
||||||
}"
|
<it-icon-smiley-happy class="mr-4 h-16 w-16"></it-icon-smiley-happy>
|
||||||
data-cy="fail"
|
<span class="text-large font-bold">
|
||||||
@click="circleStore.markCompletion(currentQuestion, 'fail')"
|
{{ $t("selfEvaluation.yes") }}.
|
||||||
>
|
</span>
|
||||||
<it-icon-smiley-thinking class="mr-4 h-16 w-16"></it-icon-smiley-thinking>
|
</button>
|
||||||
<span class="text-xl font-bold">{{ $t("selfEvaluation.no") }}.</span>
|
<button
|
||||||
</button>
|
class="inline-flex flex-1 items-center border p-4 text-left"
|
||||||
</div>
|
:class="{
|
||||||
|
'border-orange-500':
|
||||||
|
currentQuestion.completion_status === COMPLETION_FAILURE,
|
||||||
|
'border-2': currentQuestion.completion_status === COMPLETION_FAILURE,
|
||||||
|
}"
|
||||||
|
data-cy="fail"
|
||||||
|
@click="circleStore.markCompletion(currentQuestion, 'fail')"
|
||||||
|
>
|
||||||
|
<it-icon-smiley-thinking
|
||||||
|
class="mr-4 h-16 w-16"
|
||||||
|
></it-icon-smiley-thinking>
|
||||||
|
<span class="text-xl font-bold">{{ $t("selfEvaluation.no") }}.</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 lg:mt-12">
|
<div class="mt-6 lg:mt-12">
|
||||||
{{ $t("selfEvaluation.progressText") }}
|
{{ $t("selfEvaluation.progressText") }}
|
||||||
<router-link
|
<router-link
|
||||||
:to="courseSession.currentCourseSession?.competence_url || '/'"
|
:to="courseSession.currentCourseSession?.competence_url || '/'"
|
||||||
class="text-primary-500 underline"
|
class="text-primary-500 underline"
|
||||||
>
|
>
|
||||||
{{ $t("selfEvaluation.progressLink") }}
|
{{ $t("selfEvaluation.progressLink") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<LearningContentNavigation
|
</LearningContentMultiLayout>
|
||||||
:show-next-button="
|
|
||||||
questions.length > 1 && state.questionIndex + 1 < questions.length
|
|
||||||
"
|
|
||||||
:show-back-button="showBackButton"
|
|
||||||
:question-index="state.questionIndex"
|
|
||||||
:max-question-index="questions.length"
|
|
||||||
@back="handleBack"
|
|
||||||
@continue="handleContinue"
|
|
||||||
></LearningContentNavigation>
|
|
||||||
</div>
|
|
||||||
</LearningContentContainer>
|
</LearningContentContainer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import mitt from "mitt";
|
||||||
export type MittEvents = {
|
export type MittEvents = {
|
||||||
// FIXME: clean up with VBV-305
|
// FIXME: clean up with VBV-305
|
||||||
switchedCourseSession: number;
|
switchedCourseSession: number;
|
||||||
|
finishedLearningContent: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const eventBus = mitt<MittEvents>();
|
const eventBus = mitt<MittEvents>();
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,15 @@ describe("circle page", () => {
|
||||||
"contain",
|
"contain",
|
||||||
"Verschaffe dir einen Überblick"
|
"Verschaffe dir einen Überblick"
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="complete-and-continue"]').click();
|
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||||
|
|
||||||
cy.get('[data-cy="ls-continue-button"]').click();
|
cy.get('[data-cy="ls-continue-button"]').click();
|
||||||
cy.get('[data-cy="ln-title"]').should("contain", "Mediathek Fahrzeug");
|
cy.get('[data-cy="ln-title"]').should("contain", "Mediathek Fahrzeug");
|
||||||
cy.get('[data-cy="complete-and-continue"]').click();
|
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||||
|
|
||||||
cy.get('[data-cy="ls-continue-button"]').click();
|
cy.get('[data-cy="ls-continue-button"]').click();
|
||||||
cy.get('[data-cy="ln-title"]').should("contain", "Vorbereitungsauftrag");
|
cy.get('[data-cy="ln-title"]').should("contain", "Vorbereitungsauftrag");
|
||||||
cy.get('[data-cy="complete-and-continue"]').click();
|
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||||
|
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-cy="test-lehrgang-lp-circle-fahrzeug-lc-verschaffe-dir-einen-überblick-checkbox"] > .cy-checkbox-checked'
|
'[data-cy="test-lehrgang-lp-circle-fahrzeug-lc-verschaffe-dir-einen-überblick-checkbox"] > .cy-checkbox-checked'
|
||||||
|
|
@ -68,7 +68,7 @@ describe("circle page", () => {
|
||||||
"contain",
|
"contain",
|
||||||
"Verschaffe dir einen Überblick"
|
"Verschaffe dir einen Überblick"
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="complete-and-continue"]').click();
|
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||||
|
|
||||||
cy.get('[data-cy="ls-continue-button"]').should("contain", "Weiter geht's");
|
cy.get('[data-cy="ls-continue-button"]').should("contain", "Weiter geht's");
|
||||||
cy.get('[data-cy="ls-continue-button"]').click();
|
cy.get('[data-cy="ls-continue-button"]').click();
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,9 @@ Cypress.Commands.add('makeSelfEvaluation', (answers) => {
|
||||||
cy.get('[data-cy="fail"]').click();
|
cy.get('[data-cy="fail"]').click();
|
||||||
}
|
}
|
||||||
if (i < answers.length - 1) {
|
if (i < answers.length - 1) {
|
||||||
cy.get('[data-cy="next-step"]').click();
|
cy.get('[data-cy="next-step"]').click({ force: true });
|
||||||
} else {
|
} else {
|
||||||
cy.get('[data-cy="complete-and-continue"]').click();
|
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue