Compare commits
114 Commits
feature/ne
...
wip/vbv-74
| Author | SHA1 | Date |
|---|---|---|
|
|
34efe68842 | |
|
|
cc2ae195c0 | |
|
|
2c4512ba91 | |
|
|
f9d952a5e8 | |
|
|
9692c71fbc | |
|
|
1a909431cf | |
|
|
cf13030f24 | |
|
|
bc5055324c | |
|
|
a620803413 | |
|
|
aa7137b764 | |
|
|
e87ab1da57 | |
|
|
b785e327c6 | |
|
|
c68057448e | |
|
|
68938ba44b | |
|
|
6ca8469455 | |
|
|
5e84490703 | |
|
|
82f086023c | |
|
|
3b8bc42cbe | |
|
|
71e2b7cef3 | |
|
|
0536b7a537 | |
|
|
4d367ff40d | |
|
|
1d5bd24571 | |
|
|
31db223661 | |
|
|
8631fca09d | |
|
|
72f7a7f68c | |
|
|
2386b5207e | |
|
|
4bf47efa2d | |
|
|
3d742fb03a | |
|
|
995dbb5c78 | |
|
|
2fc37d0d0b | |
|
|
f8b8347bb9 | |
|
|
abe60e7468 | |
|
|
eb1d814680 | |
|
|
1c669474e9 | |
|
|
d6c182b5ab | |
|
|
93660cf421 | |
|
|
55515dd10f | |
|
|
db399e2aa2 | |
|
|
3f5d837db9 | |
|
|
bc2dd2ba6e | |
|
|
20e2a2b172 | |
|
|
d361dabd16 | |
|
|
028ff05020 | |
|
|
3faf20ac07 | |
|
|
9b89177db5 | |
|
|
f560531df2 | |
|
|
31bbf046fa | |
|
|
922bb9da32 | |
|
|
644a107377 | |
|
|
44e618bf9d | |
|
|
72c9fe8c9e | |
|
|
228bfd9c27 | |
|
|
e5cbdc1620 | |
|
|
0c1a2b3151 | |
|
|
6c6bbb9e22 | |
|
|
28027eb96f | |
|
|
69efd0de2d | |
|
|
f2a422157f | |
|
|
7a782ed911 | |
|
|
485ce827b4 | |
|
|
c9052b9691 | |
|
|
bc55b1b660 | |
|
|
78ba5cc37f | |
|
|
7b1db672ab | |
|
|
1e65c557c1 | |
|
|
d3f1c2adfa | |
|
|
bf810b3aaa | |
|
|
f6e3d1c69a | |
|
|
62a7476d8a | |
|
|
09196007e6 | |
|
|
a91a0f096c | |
|
|
302d154f41 | |
|
|
c99fd7b17a | |
|
|
94cafb9b73 | |
|
|
9160084f98 | |
|
|
1d0ee7b906 | |
|
|
014ecc31c4 | |
|
|
5d40bce806 | |
|
|
8c8b11b354 | |
|
|
908f3cade9 | |
|
|
67a19b8513 | |
|
|
345e935655 | |
|
|
e55a5e9e42 | |
|
|
d4680157ac | |
|
|
e277268426 | |
|
|
564d45341c | |
|
|
2e6c305cea | |
|
|
f24d2ca01e | |
|
|
ea1d140fc1 | |
|
|
1d2aa38896 | |
|
|
5850321776 | |
|
|
5d933cd0ec | |
|
|
944dae9a07 | |
|
|
fb26dda971 | |
|
|
39133d2d28 | |
|
|
5320694172 | |
|
|
aca3e2a1c4 | |
|
|
c11e4d1105 | |
|
|
925d737ee4 | |
|
|
1f69deaa7f | |
|
|
09750abbc2 | |
|
|
82f9d66903 | |
|
|
50a0927a23 | |
|
|
ffe1709e7f | |
|
|
ac07a3e253 | |
|
|
dc572ad1a3 | |
|
|
4e3ece6f72 | |
|
|
37bac32999 | |
|
|
9e53b8814b | |
|
|
6902521736 | |
|
|
d09b6cce04 | |
|
|
fd763df546 | |
|
|
be1df5ce1a | |
|
|
9be46400b0 |
|
|
@ -285,3 +285,6 @@ git-crypt-encrypted-files-check.txt
|
|||
/client/src/gql/dist/minifiedSchema.json
|
||||
|
||||
/sftptest/
|
||||
|
||||
# BabelEdit translation software
|
||||
/client/bable_edit.babel
|
||||
|
|
|
|||
40
README.md
40
README.md
|
|
@ -181,15 +181,23 @@ Deployment happens manually via Bitbucket Pipelines
|
|||
|
||||
Bitbucket Pipelines name: myvbv
|
||||
https://myvbv.control.iterativ.ch/
|
||||
https://myvbv.iterativ.ch/
|
||||
|
||||
https://myvbv.iterativ.ch/v
|
||||
Deployment happens manually via Bitbucket Pipelines
|
||||
|
||||
|
||||
### CapRover feature branch deployment
|
||||
|
||||
You can deploy every feature branch to CapRover directly from Bitbucket Pipelines
|
||||
with a manual step.
|
||||
with a manual step. Either in the "normal" pipeline by running "deploy feature"
|
||||
as last manual step.
|
||||
|
||||
You can also select deploy-step direclty under
|
||||
|
||||
`Branches > Actions > Run pipeline` for a branch
|
||||
and then `custom: deploy-feature-branch -> Run` in the modal.
|
||||
|
||||

|
||||

|
||||
|
||||
When you run caprover_deploy.sh without arguments, it will deploy the current branch
|
||||
|
||||
|
|
@ -199,6 +207,9 @@ When you run caprover_deploy.sh without arguments, it will deploy the current br
|
|||
|
||||
### Cleanup caprover feature branch deployments
|
||||
|
||||
You can delete the CapRover App and DB directly in the CapRover interface
|
||||
or use the `caprover_cleanup.py` script.
|
||||
|
||||
```bash
|
||||
# by default it will delete all vbv-feature-* apps
|
||||
python caprover_cleanup.py
|
||||
|
|
@ -295,7 +306,25 @@ graphql schema.
|
|||
- What about the generated types from `codegen`? Hand written types seem to be better.
|
||||
- The functions in `cacheExchange` should be nearer the concrete implementation...
|
||||
|
||||
## Load prod data for testing
|
||||
## Test users
|
||||
### Local development
|
||||
If `prepare_server.sh` was run, the following users are available (password: `test` and found in `create_default_users.py`):
|
||||
|
||||
- Trainer
|
||||
- test-trainer1@example.com
|
||||
- test-trainer2@example.com
|
||||
- Teilnehmer
|
||||
- test-student1@example.com
|
||||
- test-student2@example.com
|
||||
- test-student3@example.com
|
||||
- Regionenleiter
|
||||
- test-supervisor1@example.com
|
||||
- Berufsbildner
|
||||
- test-berufsbildner1@example.com
|
||||
- Ausbildungsverantwortlicher
|
||||
- test-ausbildungsverantwortlicher1@example.com
|
||||
|
||||
### Load prod data for local testing
|
||||
|
||||
1. Checkout the [vbv-devops](https://bitbucket.org/iterativ/iterativ-devops/src/master/) repository
|
||||
2. Change into the `backups` directory
|
||||
|
|
@ -306,3 +335,6 @@ graphql schema.
|
|||
for csu in CourseSessionUser.objects.all():
|
||||
csu.user.set_password("test")
|
||||
```
|
||||
### Test users on production
|
||||
On production there are CourseSessions in the VV and üK courses with test users. The data can be rested via the admin
|
||||
interface ("Iterativ Testdurchführungen zurücksetzen"). All users use local login and can be found in 1password.
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ def main(app_name, image_name, environment_file):
|
|||
|
||||
default_allowed_hosts = f"{app_name}.iterativ.ch,{app_name}.control.iterativ.ch"
|
||||
|
||||
app_environment = env.str("IT_APP_ENVIRONMENT", "dev-feature")
|
||||
if app_environment == "local":
|
||||
# local is never the correct value...
|
||||
app_environment = "dev-feature"
|
||||
|
||||
cap.create_and_update_app(
|
||||
app_name=app_name,
|
||||
enable_ssl=True,
|
||||
|
|
@ -72,7 +77,7 @@ def main(app_name, image_name, environment_file):
|
|||
image_name=image_name,
|
||||
container_http_port=7555,
|
||||
environment_variables={
|
||||
"IT_APP_ENVIRONMENT": env.str("IT_APP_ENVIRONMENT", "dev-feature"),
|
||||
"IT_APP_ENVIRONMENT": app_environment,
|
||||
"IT_DEFAULT_ADMIN_PASSWORD": env.str(
|
||||
"IT_DEFAULT_ADMIN_PASSWORD", "ACEEs0DCmNaPxdoNV8vhccuCTRl9b"
|
||||
),
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ async function changeLocale(language: AvailableLanguages) {
|
|||
</a>
|
||||
</div>
|
||||
<router-link
|
||||
class="lg:ml-8"
|
||||
v-if="isVVLearningMentor(courseSessionsStore.currentCourseSession)"
|
||||
class="lg:ml-8"
|
||||
:to="vvBuyLink.href.value"
|
||||
data-cy="buy-vv-link"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import { i18nextInit, loadI18nextLocaleMessages } from "@/i18nextWrapper";
|
||||
import AttendanceStatus from "@/pages/cockpit/cockpitPage/AttendanceStatus.vue";
|
||||
import { config, mount } from "@vue/test-utils";
|
||||
import i18next from "i18next";
|
||||
import I18NextVue from "i18next-vue";
|
||||
import { expect, vi } from "vitest";
|
||||
|
||||
describe("AttendanceStatus.vue", async () => {
|
||||
vi.useFakeTimers();
|
||||
const date = new Date(1999, 2, 31);
|
||||
vi.setSystemTime(date);
|
||||
await i18nextInit();
|
||||
await loadI18nextLocaleMessages("de");
|
||||
config.global.plugins = [[I18NextVue, { i18next }]];
|
||||
|
||||
test("Attendance check complete", () => {
|
||||
const wrapper = mount(AttendanceStatus, {
|
||||
props: {
|
||||
done: true,
|
||||
date: "",
|
||||
},
|
||||
});
|
||||
expect(wrapper.text()).toContain("Du hast die Anwesenheit bestätigt.");
|
||||
});
|
||||
|
||||
test("Attendance check future", () => {
|
||||
const future = "1999-04-02T06:30:00+00:00";
|
||||
const wrapper = mount(AttendanceStatus, {
|
||||
props: {
|
||||
done: false,
|
||||
date: future,
|
||||
},
|
||||
});
|
||||
expect(wrapper.text()).toContain("Der Präsenzkurs findet in 2 Tagen statt.");
|
||||
});
|
||||
|
||||
test("Attendance check future", () => {
|
||||
const future = "1999-04-01T06:30:00+00:00";
|
||||
const wrapper = mount(AttendanceStatus, {
|
||||
props: {
|
||||
done: false,
|
||||
date: future,
|
||||
},
|
||||
});
|
||||
expect(wrapper.text()).toContain("Der Präsenzkurs findet in einem Tag statt.");
|
||||
});
|
||||
|
||||
test("Attendance check now", () => {
|
||||
const yesterday = "1999-03-30T06:30:00+00:00";
|
||||
const wrapper = mount(AttendanceStatus, {
|
||||
props: {
|
||||
done: false,
|
||||
date: yesterday,
|
||||
},
|
||||
});
|
||||
expect(wrapper.text()).toContain("Überprüfe jetzt die Anwesenheit.");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { expect, vi } from "vitest";
|
||||
import { isInFuture } from "../dueDates/dueDatesUtils";
|
||||
|
||||
test("Date Utils", () => {
|
||||
vi.useFakeTimers();
|
||||
const date = new Date(1999, 2, 31);
|
||||
vi.setSystemTime(date);
|
||||
|
||||
const today = "1999-03-31T06:30:00+00:00";
|
||||
const yesterday = "1999-03-30T06:30:00+00:00";
|
||||
const tomorrow = "1999-04-01T06:30:00+00:00";
|
||||
|
||||
expect(isInFuture(yesterday)).toBeFalsy();
|
||||
expect(isInFuture(today)).toBeFalsy();
|
||||
expect(isInFuture(tomorrow)).toBeTruthy();
|
||||
});
|
||||
|
|
@ -55,6 +55,7 @@ const submittables = computed(() => {
|
|||
lc.content_type === "learnpath.LearningContentAssignment" ||
|
||||
lc.content_type === "learnpath.LearningContentFeedbackUK" ||
|
||||
lc.content_type === "learnpath.LearningContentFeedbackVV" ||
|
||||
lc.content_type === "learnpath.LearningContentFeedbackAutomobilGewerbe" ||
|
||||
lc.content_type === "learnpath.LearningContentEdoniqTest"
|
||||
);
|
||||
|
||||
|
|
@ -76,7 +77,8 @@ const submittables = computed(() => {
|
|||
const isFeedback = (lc: LearningContent) => {
|
||||
return (
|
||||
lc.content_type === "learnpath.LearningContentFeedbackUK" ||
|
||||
lc.content_type === "learnpath.LearningContentFeedbackVV"
|
||||
lc.content_type === "learnpath.LearningContentFeedbackVV" ||
|
||||
lc.content_type === "learnpath.LearningContentFeedbackAutomobilGewerbe"
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-[325px]">
|
||||
<div class="w-60 sm:w-[325px]">
|
||||
<BaseBox
|
||||
:details-link="`/dashboard/persons?course=${props.courseId}`"
|
||||
data-cy="dashboard.mentor.menteeCount"
|
||||
|
|
@ -37,14 +37,16 @@ onMounted(async () => {
|
|||
>
|
||||
<template #title>{{ $t("a.Personen") }}</template>
|
||||
<template #content>
|
||||
<div :class="['flex flex-row space-x-3 bg-white', slim ? '' : 'pb-6']">
|
||||
<div
|
||||
:class="['flex flex-row flex-wrap items-center bg-white', slim ? '' : 'pb-6']"
|
||||
>
|
||||
<div
|
||||
class="flex h-[74px] items-center justify-center py-1 pr-3 text-3xl font-bold"
|
||||
data-cy="dashboard.mentor.menteeCountValue"
|
||||
>
|
||||
<span>{{ menteeCount }}</span>
|
||||
</div>
|
||||
<p class="ml-3 mt-0 leading-[74px]">
|
||||
<p class="mt-0">
|
||||
{{ $t("a.Personen, die du begleitest") }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ onMounted(async () => {
|
|||
|
||||
<template>
|
||||
<div v-if="assignment">
|
||||
<div class="w-[395px]">
|
||||
<div class="sm:w-[395px]">
|
||||
<AssignmentProgressSummaryBox
|
||||
:total-assignments="assignment.total_count"
|
||||
:achieved-points-count="assignment.points_achieved_count"
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const data = computed(() => ({
|
|||
</script>
|
||||
w
|
||||
<template>
|
||||
<div class="flex flex-row items-center space-x-8">
|
||||
<div class="flex flex-row flex-wrap items-center gap-y-4 space-x-8 sm:gap-y-0">
|
||||
<div class="size-32">
|
||||
<Pie
|
||||
:data="data"
|
||||
|
|
@ -39,7 +39,7 @@ w
|
|||
}"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="flex flex-col items-start">
|
||||
<div
|
||||
v-for="(value, key) in props.data"
|
||||
:key="key"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="competence" class="w-[395px]">
|
||||
<div v-if="competence" class="sm:w-[395px]">
|
||||
<CompetenceSummaryBox
|
||||
:fail-count="competence.fail_count"
|
||||
:success-count="competence.success_count"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import UkStatistics from "@/components/dashboard/UkStatistics.vue";
|
|||
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||
import type { DashboardCourseConfigType, WidgetType } from "@/services/dashboard";
|
||||
import { getCockpitUrl, getLearningMentorUrl, getLearningPathUrl } from "@/utils/utils";
|
||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
||||
import { computed } from "vue";
|
||||
import TrainingResponsibleStatistics from "./TrainingResponsibleStatistics.vue";
|
||||
|
||||
|
|
@ -23,6 +24,9 @@ const props = defineProps<{
|
|||
courseConfig: DashboardCourseConfigType | undefined;
|
||||
}>();
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind);
|
||||
const smAndLarger = breakpoints.greaterOrEqual("sm");
|
||||
|
||||
const courseSlug = computed(() => props.courseConfig?.course_slug ?? "");
|
||||
const courseName = computed(() => props.courseConfig?.course_title ?? "");
|
||||
const numberOfMentorWidgets = computed(() => {
|
||||
|
|
@ -65,6 +69,13 @@ const actionButtonProps = computed<{ href: string; text: string; cyKey: string }
|
|||
cyKey: "lm-dashboard-link",
|
||||
};
|
||||
}
|
||||
if (props.courseConfig?.role_key === "MentorUK") {
|
||||
return {
|
||||
href: getLearningPathUrl(props.courseConfig?.course_slug),
|
||||
text: "a.Teilnehmer Vorschau",
|
||||
cyKey: "attendance-dashboard-link",
|
||||
};
|
||||
}
|
||||
return {
|
||||
href: getLearningPathUrl(props.courseConfig?.course_slug),
|
||||
text: "Weiter lernen",
|
||||
|
|
@ -75,7 +86,6 @@ const actionButtonProps = computed<{ href: string; text: string; cyKey: string }
|
|||
|
||||
function hasActionButton(): boolean {
|
||||
return (
|
||||
props.courseConfig?.role_key !== "MentorUK" &&
|
||||
props.courseConfig?.role_key !== "Ausbildungsverantwortlicher" &&
|
||||
props.courseConfig?.role_key !== "Berufsbildner"
|
||||
);
|
||||
|
|
@ -90,8 +100,10 @@ function hasActionButton(): boolean {
|
|||
>
|
||||
<div class="flex flex-col space-y-8 bg-white p-6">
|
||||
<div class="border-b border-gray-300 pb-8">
|
||||
<div class="flex flex-row items-start justify-between">
|
||||
<h3 class="mb-4 text-3xl" data-cy="db-course-title">{{ courseName }}</h3>
|
||||
<div class="flex flex-row flex-wrap items-start justify-between pb-3 sm:pb-0">
|
||||
<h3 class="mb-4 text-xl sm:text-3xl" data-cy="db-course-title">
|
||||
{{ courseName }}
|
||||
</h3>
|
||||
<a
|
||||
v-if="hasActionButton()"
|
||||
:href="actionButtonProps.href"
|
||||
|
|
@ -108,7 +120,7 @@ function hasActionButton(): boolean {
|
|||
<router-link
|
||||
v-if="courseConfig.has_preview"
|
||||
:to="getLearningPathUrl(courseConfig.course_slug)"
|
||||
class="inline-block pl-6"
|
||||
class="inline-block pt-3 sm:pl-6 sm:pt-0"
|
||||
target="_blank"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
|
|
@ -131,7 +143,7 @@ function hasActionButton(): boolean {
|
|||
:key="courseSlug"
|
||||
:course-slug="courseSlug"
|
||||
:course-session-id="courseConfig.session_to_continue_id"
|
||||
diagram-type="horizontal"
|
||||
:diagram-type="smAndLarger ? 'horizontal' : 'horizontalSmall'"
|
||||
></LearningPathDiagram>
|
||||
</div>
|
||||
|
||||
|
|
@ -173,7 +185,7 @@ function hasActionButton(): boolean {
|
|||
|
||||
<div
|
||||
v-if="numberOfMentorWidgets > 0"
|
||||
class="flex flex-col flex-wrap items-stretch md:flex-row"
|
||||
class="flex flex-col flex-wrap items-stretch space-y-4 sm:space-y-0 md:flex-row"
|
||||
>
|
||||
<AgentConnectionCount
|
||||
v-if="hasWidget('MentorPersonWidget')"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ const summary = computed(() => {
|
|||
return mentorAssignmentData.value?.assignments ?? null;
|
||||
});
|
||||
|
||||
const courseSlug = computed(() => mentorAssignmentData.value?.course_slug);
|
||||
|
||||
onMounted(async () => {
|
||||
mentorAssignmentData.value = await fetchMentorCompetenceSummary(
|
||||
props.courseId,
|
||||
|
|
@ -27,9 +29,9 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="summary" class="w-[325px]">
|
||||
<div v-if="summary" class="w-60 sm:w-[325px]">
|
||||
<BaseBox
|
||||
:details-link="`/dashboard/persons-competence?course=${props.courseId}`"
|
||||
:details-link="`/statistic/${props.agentRole}/${courseSlug}/assignment`"
|
||||
data-cy="dashboard.mentor.competenceSummary"
|
||||
>
|
||||
<template #title>{{ $t("Kompetenznachweise") }}</template>
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-[325px]">
|
||||
<div class="w-60 sm:w-[325px]">
|
||||
<BaseBox
|
||||
:details-link="`/course/${props.courseSlug}/learning-mentor/tasks`"
|
||||
data-cy="dashboard.mentor.openTasksCount"
|
||||
>
|
||||
<template #title>{{ $t("a.Zu erledigen") }}</template>
|
||||
<template #content>
|
||||
<div class="flex flex-row space-x-3 bg-white pb-6">
|
||||
<div class="flex flex-row flex-wrap space-x-3 bg-white pb-6">
|
||||
<div
|
||||
class="flex h-[74px] w-[74px] items-center justify-center rounded-full border-2 border-green-500 px-3 py-1 text-3xl font-bold"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ const attendanceCountPerChosenProfile = computed(() => {
|
|||
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col flex-wrap items-stretch md:flex-row">
|
||||
<div class="flex flex-col flex-wrap items-stretch gap-y-4 sm:gap-y-0 md:flex-row">
|
||||
<BaseBox
|
||||
:details-link="`/dashboard/cost/${courseSessionId}`"
|
||||
data-cy="dashboard.stats.trainingResponsible.cost"
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ onMounted(async () => {
|
|||
<template>
|
||||
<div v-if="statistics" class="space-y-8">
|
||||
<div
|
||||
class="flex flex-col flex-wrap justify-between gap-x-5 border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
||||
class="flex flex-col flex-wrap justify-between gap-x-5 gap-y-5 border-b border-gray-300 pb-8 last:border-0 md:flex-row"
|
||||
>
|
||||
<AttendanceSummaryBox
|
||||
class="flex-grow"
|
||||
|
|
@ -80,7 +80,7 @@ onMounted(async () => {
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col flex-wrap gap-x-5 border-b border-gray-300 align-top last:border-0 md:flex-row"
|
||||
class="flex flex-col flex-wrap gap-x-5 gap-y-5 border-b border-gray-300 align-top last:border-0 md:flex-row"
|
||||
>
|
||||
<FeedbackSummaryBox
|
||||
:feedback-count="feebackSummary.total_responses"
|
||||
|
|
|
|||
|
|
@ -59,3 +59,12 @@ export const getWeekday = (date: Dayjs) => {
|
|||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
export const isInFuture = (date: string) => {
|
||||
// is today before the prop date?
|
||||
return dayjs().isBefore(date, "day");
|
||||
};
|
||||
|
||||
export const howManyDaysInFuture = (date: string) => {
|
||||
return dayjs(date).diff(dayjs().startOf("day"), "day");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,29 +1,16 @@
|
|||
<template>
|
||||
<AccountMenuContent
|
||||
:course-sessions="courseSessionsStore.allCurrentCourseSessions"
|
||||
:selected-course-session="courseSessionsStore.currentCourseSession?.id"
|
||||
:user="userStore"
|
||||
@logout="logout"
|
||||
@select-course-session="selectCourseSession"
|
||||
@close="emit('close')"
|
||||
/>
|
||||
<AccountMenuContent :user="userStore" @logout="logout" @close="emit('close')" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AccountMenuContent from "@/components/header/AccountMenuContent.vue";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { CourseSession } from "@/types";
|
||||
|
||||
const emit = defineEmits(["close"]);
|
||||
|
||||
const logout = () => {
|
||||
userStore.handleLogout();
|
||||
};
|
||||
const selectCourseSession = (courseSession: CourseSession) => {
|
||||
courseSessionsStore.switchCourseSessionById(courseSession.id);
|
||||
};
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const userStore = useUserStore();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import CourseSessionsMenu from "@/components/header/CourseSessionsMenu.vue";
|
||||
import { SETTINGS_ROUTE } from "@/router/names";
|
||||
import type { User } from "@/stores/user";
|
||||
import type { CourseSession } from "@/types";
|
||||
import { useRouteLookups } from "@/utils/route";
|
||||
import { computed } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSessions: CourseSession[];
|
||||
defineProps<{
|
||||
user: User;
|
||||
selectedCourseSession?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["selectCourseSession", "logout", "close"]);
|
||||
const emit = defineEmits(["logout", "close"]);
|
||||
|
||||
const router = useRouter();
|
||||
const { inCourse } = useRouteLookups();
|
||||
|
||||
const showCourseSessionMenu = computed(() => inCourse() && props.courseSessions.length);
|
||||
|
||||
async function navigate(routeName: string) {
|
||||
await router.push({ name: routeName });
|
||||
emit("close");
|
||||
}
|
||||
|
||||
const settingsRoute = {
|
||||
name: SETTINGS_ROUTE,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-black">
|
||||
<div class="flex flex-col gap-4 text-black">
|
||||
<div class="border-b py-4">
|
||||
<div class="flex justify-start">
|
||||
<div v-if="user.avatar_url">
|
||||
|
|
@ -46,22 +42,19 @@ async function navigate(routeName: string) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showCourseSessionMenu" class="border-b py-4">
|
||||
<CourseSessionsMenu
|
||||
:items="courseSessions"
|
||||
:selected="selectedCourseSession"
|
||||
@select="emit('selectCourseSession', $event)"
|
||||
/>
|
||||
</div>
|
||||
<router-link class="flex items-center gap-2" :to="settingsRoute">
|
||||
<it-icon-settings />
|
||||
{{ $t("a.Einstellungen") }}
|
||||
</router-link>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="mt-6 flex items-center"
|
||||
class="flex items-center gap-2"
|
||||
data-cy="logout-button"
|
||||
@click="emit('logout')"
|
||||
>
|
||||
<it-icon-logout class="inline-block" />
|
||||
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
||||
<span>{{ $t("mainNavigation.logout") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const { t } = useTranslation();
|
|||
<nav class="bg-yellow-500">
|
||||
<div class="mx-auto px-4 lg:px-8">
|
||||
<div
|
||||
class="relative flex h-16 w-full flex-col items-center justify-end space-x-8 lg:flex-row lg:items-stretch lg:justify-center"
|
||||
class="relative flex h-auto min-h-16 w-full flex-col items-center justify-end space-x-8 lg:flex-row lg:items-stretch lg:justify-center"
|
||||
>
|
||||
<span class="flex items-center px-1 pt-1 font-bold text-black">
|
||||
{{ t("a.Vorschau Teilnehmer") }} ({{ courseSession.title }})
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ import {
|
|||
} from "@/utils/utils";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { computed } from "vue";
|
||||
import SelectedCourseSession from "./SelectedCourseSession.vue";
|
||||
|
||||
const { t } = useTranslation();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
|
||||
const { inCockpit, inCompetenceProfile, inLearningMentor, inLearningPath } =
|
||||
const { isInCockpit, inCompetenceProfile, inLearningMentor, inLearningPath } =
|
||||
useRouteLookups();
|
||||
const {
|
||||
hasCompetenceNaviMenu,
|
||||
|
|
@ -30,13 +31,18 @@ const mentorTabTitle = computed(() =>
|
|||
);
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="courseSessionsStore.currentCourseSession" class="hidden space-x-8 lg:flex">
|
||||
<div
|
||||
v-if="courseSessionsStore.currentCourseSession"
|
||||
class="flex space-x-8 px-2 lg:px-10"
|
||||
>
|
||||
<SelectedCourseSession />
|
||||
|
||||
<router-link
|
||||
v-if="hasCockpitMenu"
|
||||
data-cy="navigation-cockpit-link"
|
||||
:to="getCockpitUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCockpit() }"
|
||||
class="nav-item-no-mobile"
|
||||
:class="{ 'nav-item--active': isInCockpit }"
|
||||
>
|
||||
{{ t("cockpit.title") }}
|
||||
</router-link>
|
||||
|
|
@ -46,7 +52,7 @@ const mentorTabTitle = computed(() =>
|
|||
data-cy="navigation-preview-link"
|
||||
:to="getLearningPathUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||
target="_blank"
|
||||
class="nav-item"
|
||||
class="nav-item-no-mobile"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<span>{{ t("a.Vorschau Teilnehmer") }}</span>
|
||||
|
|
@ -57,7 +63,7 @@ const mentorTabTitle = computed(() =>
|
|||
v-if="hasLearningPathMenu"
|
||||
data-cy="navigation-learning-path-link"
|
||||
:to="getLearningPathUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||
class="nav-item"
|
||||
class="nav-item-no-mobile"
|
||||
:class="{ 'nav-item--active': inLearningPath() }"
|
||||
>
|
||||
{{ t("general.learningPath") }}
|
||||
|
|
@ -67,7 +73,7 @@ const mentorTabTitle = computed(() =>
|
|||
v-if="hasCompetenceNaviMenu"
|
||||
data-cy="navigation-competence-profile-link"
|
||||
:to="getCompetenceNaviUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||
class="nav-item"
|
||||
class="nav-item-no-mobile"
|
||||
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
||||
>
|
||||
{{ t("competences.title") }}
|
||||
|
|
@ -77,7 +83,7 @@ const mentorTabTitle = computed(() =>
|
|||
v-if="hasLearningMentor"
|
||||
data-cy="navigation-learning-mentor-link"
|
||||
:to="getLearningMentorUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||
class="nav-item"
|
||||
class="nav-item-no-mobile"
|
||||
:class="{ 'nav-item--active': inLearningMentor() }"
|
||||
>
|
||||
{{ t(mentorTabTitle) }}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import CoursePreviewBar from "./CoursePreviewBar.vue";
|
||||
import MainNavigationBar from "./MainNavigationBar.vue";
|
||||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import { useRouteLookups } from "@/utils/route";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
const { isInCourse } = useRouteLookups();
|
||||
|
||||
const { t } = useTranslation();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hidden flex-shrink-0 items-center lg:flex">
|
||||
<div class="flex items-center">
|
||||
<div class="flex flex-shrink-0 items-center">
|
||||
<template v-if="isInCourse">
|
||||
<div class="flex h-full items-center border-r border-slate-500">
|
||||
<router-link to="/" class="flex items-center pr-3">
|
||||
<it-icon-arrow-left class="fill-current text-slate-500" />
|
||||
<span class="hidden text-slate-500 lg:inline">
|
||||
{{ t("a.Dashboard") }}
|
||||
</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link to="/" class="flex">
|
||||
<it-icon-vbv class="-ml-3 -mt-6 mr-3 h-8 w-16" />
|
||||
</router-link>
|
||||
|
|
@ -15,6 +27,6 @@ const { t } = useTranslation();
|
|||
{{ t("general.title") }}
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -15,12 +15,7 @@ log.debug("MainNavigationBar created");
|
|||
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const { inMediaLibrary, inAppointments } = useRouteLookups();
|
||||
const { hasMediaLibraryMenu, hasAppointmentsMenu, hasSessionTitle } =
|
||||
useNavigationAttributes();
|
||||
|
||||
const selectedCourseSessionTitle = computed(() => {
|
||||
return courseSessionsStore.currentCourseSession?.title;
|
||||
});
|
||||
const { hasMediaLibraryMenu, hasAppointmentsMenu } = useNavigationAttributes();
|
||||
|
||||
const appointmentsUrl = computed(() => {
|
||||
const currentCourseSession = courseSessionsStore.currentCourseSession;
|
||||
|
|
@ -40,13 +35,12 @@ onMounted(() => {
|
|||
<nav class="bg-blue-900 text-white">
|
||||
<div class="mx-auto px-4 lg:px-8">
|
||||
<div class="relative flex h-16 justify-between">
|
||||
<MobileMenuButton />
|
||||
<div class="flex flex-1 items-stretch justify-start">
|
||||
<HomeNavigation />
|
||||
<CourseSessionNavigation />
|
||||
</div>
|
||||
|
||||
<div class="flex items-stretch justify-start space-x-8">
|
||||
<div class="flex items-stretch justify-start gap-2 lg:gap-4">
|
||||
<router-link
|
||||
v-if="hasMediaLibraryMenu"
|
||||
:to="
|
||||
|
|
@ -62,7 +56,7 @@ onMounted(() => {
|
|||
v-if="hasAppointmentsMenu"
|
||||
:to="appointmentsUrl"
|
||||
data-cy="all-duedates-link"
|
||||
class="nav-item"
|
||||
class="nav-item-no-mobile"
|
||||
:class="{ 'nav-item--active': inAppointments() }"
|
||||
>
|
||||
<it-icon-calendar-light class="h-8 w-8" />
|
||||
|
|
@ -71,22 +65,12 @@ onMounted(() => {
|
|||
<!-- Notification Bell & Menu -->
|
||||
<NotificationButton />
|
||||
|
||||
<div
|
||||
v-if="hasSessionTitle"
|
||||
class="nav-item hidden items-center lg:inline-flex"
|
||||
>
|
||||
<div class="" data-cy="current-course-session-title">
|
||||
{{ selectedCourseSessionTitle }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav-item">
|
||||
<ProfileMenuButton />
|
||||
</div>
|
||||
</div>
|
||||
<MobileMenuButton />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style lang="postcss"></style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||
import { useVVByLink } from "@/composables";
|
||||
import { PERSONAL_PROFILE_ROUTE, SETTINGS_ROUTE } from "@/router/names";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import type { User } from "@/stores/user";
|
||||
import type { CourseSession } from "@/types";
|
||||
|
|
@ -49,13 +50,20 @@ const mentorTabTitle = computed(() =>
|
|||
? "a.Praxisbildner"
|
||||
: "a.Lernbegleitung"
|
||||
);
|
||||
|
||||
const settingsRoute = {
|
||||
name: SETTINGS_ROUTE,
|
||||
};
|
||||
const profileRoute = {
|
||||
name: PERSONAL_PROFILE_ROUTE,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ItFullScreenModal :show="show" @closemodal="emit('closemodal')">
|
||||
<div>
|
||||
<div class="-mx-4">
|
||||
<div>
|
||||
<div v-if="user?.loggedIn" class="-mx-4 border-b px-8 pb-4">
|
||||
<div v-if="user?.loggedIn" class="border-b px-8 pb-4">
|
||||
<div class="-ml-4 flex">
|
||||
<div v-if="user?.avatar_url">
|
||||
<img
|
||||
|
|
@ -66,62 +74,80 @@ const mentorTabTitle = computed(() =>
|
|||
</div>
|
||||
<div class="ml-6">
|
||||
<h3>{{ user?.first_name }} {{ user?.last_name }}</h3>
|
||||
|
||||
<div class="mb-3 text-sm text-gray-800">{{ user.email }}</div>
|
||||
<router-link
|
||||
:to="profileRoute"
|
||||
class="underline"
|
||||
@click="emit('closemodal')"
|
||||
>
|
||||
{{ $t("a.Profil anzeigen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="courseSession" class="mt-6 border-b">
|
||||
<h4 class="text-sm text-gray-900">{{ courseSession.course.title }}</h4>
|
||||
<ul class="mt-6">
|
||||
<li v-if="hasCockpitMenu" class="mb-6">
|
||||
<button
|
||||
<div v-if="courseSession" class="border-b px-4 py-6">
|
||||
<h4 class="mb-4 px-4 text-sm text-gray-900">
|
||||
{{ courseSession.course.title }}
|
||||
</h4>
|
||||
<ul class="flex flex-col gap-2">
|
||||
<li v-if="hasCockpitMenu">
|
||||
<router-link
|
||||
class="block w-full px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
data-cy="navigation-mobile-cockpit-link"
|
||||
@click="clickLink(getCockpitUrl(courseSession.course.slug))"
|
||||
:to="getCockpitUrl(courseSession.course.slug)"
|
||||
@click="$emit('closemodal')"
|
||||
>
|
||||
{{ $t("cockpit.title") }}
|
||||
</button>
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="hasPreviewMenu" class="mb-6">
|
||||
<button
|
||||
<li v-if="hasPreviewMenu">
|
||||
<router-link
|
||||
class="block w-full px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
data-cy="navigation-mobile-preview-link"
|
||||
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||
:to="getLearningPathUrl(courseSession.course.slug)"
|
||||
@click="$emit('closemodal')"
|
||||
>
|
||||
{{ $t("a.Vorschau Teilnehmer") }}
|
||||
</button>
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="hasLearningPathMenu" class="mb-6">
|
||||
<button
|
||||
<li v-if="hasLearningPathMenu">
|
||||
<router-link
|
||||
class="block w-full px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
data-cy="navigation-mobile-learning-path-link"
|
||||
@click="clickLink(getLearningPathUrl(courseSession.course.slug))"
|
||||
:to="getLearningPathUrl(courseSession.course.slug)"
|
||||
@click="$emit('closemodal')"
|
||||
>
|
||||
{{ $t("general.learningPath") }}
|
||||
</button>
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="hasCompetenceNaviMenu" class="mb-6">
|
||||
<button
|
||||
<li v-if="hasCompetenceNaviMenu">
|
||||
<router-link
|
||||
class="block w-full px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
data-cy="navigation-mobile-competence-profile-link"
|
||||
@click="clickLink(getCompetenceNaviUrl(courseSession.course.slug))"
|
||||
:to="getCompetenceNaviUrl(courseSession.course.slug)"
|
||||
@click="$emit('closemodal')"
|
||||
>
|
||||
{{ $t("competences.title") }}
|
||||
</button>
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="hasLearningMentor" class="mb-6">
|
||||
<button
|
||||
<li v-if="hasLearningMentor">
|
||||
<router-link
|
||||
class="block w-full px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
data-cy="navigation-mobile-mentor-link"
|
||||
@click="clickLink(getLearningMentorUrl(courseSession.course.slug))"
|
||||
:to="getLearningMentorUrl(courseSession.course.slug)"
|
||||
@click="$emit('closemodal')"
|
||||
>
|
||||
{{ $t(mentorTabTitle) }}
|
||||
</button>
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
<li v-if="hasMediaLibraryMenu" class="mb-6">
|
||||
<button
|
||||
data-cy="medialibrary-link"
|
||||
@click="clickLink(getMediaCenterUrl(courseSession.course.slug))"
|
||||
>
|
||||
{{ $t("a.Mediathek") }}
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
v-if="isVVLearningMentor(courseSessionsStore.currentCourseSession)"
|
||||
class="mb-6"
|
||||
|
|
@ -135,22 +161,57 @@ const mentorTabTitle = computed(() =>
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-6 border-b">
|
||||
<ul>
|
||||
<li class="mb-6">
|
||||
<button data-cy="dashboard-link" @click="clickLink('/')">myVBV</button>
|
||||
<div v-if="courseSession" class="border-b px-4">
|
||||
<ul class="flex flex-col gap-2 py-6">
|
||||
<li v-if="courseSession && hasMediaLibraryMenu" class="flex">
|
||||
<router-link
|
||||
data-cy="medialibrary-link"
|
||||
class="flex w-full items-center gap-2 px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
:to="getMediaCenterUrl(courseSession.course.slug)"
|
||||
@click="$emit('closemodal')"
|
||||
>
|
||||
<it-icon-media-library />
|
||||
{{ $t("a.Mediathek") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="courseSession && hasMediaLibraryMenu" class="flex">
|
||||
<router-link
|
||||
data-cy="calendar-link"
|
||||
class="flex w-full items-center gap-2 px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
:to="'/'"
|
||||
@click="$emit('closemodal')"
|
||||
>
|
||||
<!-- todo: correct route -->
|
||||
<it-icon-calendar-light />
|
||||
{{ $t("a.Termine") }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button
|
||||
v-if="user?.loggedIn"
|
||||
type="button"
|
||||
class="mt-6 flex items-center"
|
||||
@click="$emit('logout')"
|
||||
>
|
||||
<it-icon-logout class="inline-block" />
|
||||
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
||||
</button>
|
||||
|
||||
<div class="flex flex-col gap-2 px-4 py-6">
|
||||
<router-link
|
||||
v-if="user?.loggedIn"
|
||||
:to="settingsRoute"
|
||||
class="flex w-full items-center gap-2 px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
type="button"
|
||||
>
|
||||
<it-icon-settings />
|
||||
{{ $t("a.Einstellungen") }}
|
||||
</router-link>
|
||||
<button
|
||||
v-if="user?.loggedIn"
|
||||
type="button"
|
||||
class="flex items-center px-4 py-2"
|
||||
@click="$emit('logout')"
|
||||
>
|
||||
<it-icon-logout class="inline-block" />
|
||||
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const showMenu = ref(false);
|
|||
@logout="userStore.handleLogout()"
|
||||
/>
|
||||
</Teleport>
|
||||
<div class="absolute inset-y-0 left-0 flex items-center lg:hidden">
|
||||
<div class="inset-y-0 flex items-center lg:hidden">
|
||||
<!-- Mobile menu button -->
|
||||
<div data-cy="navigation-mobile-menu-button" class="flex" @click="showMenu = true">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -29,7 +29,11 @@ function popoverClick(event: Event) {
|
|||
<AccountMenu @close="showMenu = false" />
|
||||
</ItFullScreenModal>
|
||||
</Teleport>
|
||||
<div v-if="userStore.loggedIn" class="flex items-center" data-cy="header-profile">
|
||||
<div
|
||||
v-if="userStore.loggedIn"
|
||||
class="hidden items-center lg:flex"
|
||||
data-cy="header-profile"
|
||||
>
|
||||
<Popover class="relative">
|
||||
<PopoverButton @click="popoverClick($event)">
|
||||
<div v-if="userStore.avatar_url">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<script setup lang="ts">
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import type { CourseSession } from "@/types";
|
||||
import { useNavigationAttributes } from "@/utils/navigation";
|
||||
import { useRouteLookups } from "@/utils/route";
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
|
||||
import { computed } from "vue";
|
||||
import CourseSessionsMenu from "./CourseSessionsMenu.vue";
|
||||
|
||||
const { isInCourse } = useRouteLookups();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const { hasSessionTitle } = useNavigationAttributes();
|
||||
|
||||
const selectedCourseSessionTitle = computed(() => {
|
||||
return courseSessionsStore.currentCourseSession?.title;
|
||||
});
|
||||
const selectedCourseSession = computed(() => {
|
||||
return courseSessionsStore.currentCourseSession;
|
||||
});
|
||||
|
||||
const selectCourseSession = (courseSession: CourseSession) => {
|
||||
courseSessionsStore.switchCourseSessionById(courseSession.id);
|
||||
};
|
||||
|
||||
const courseSessions = computed(() => {
|
||||
return courseSessionsStore.allCourseSessions;
|
||||
});
|
||||
|
||||
const showCourseSessionMenu = computed(
|
||||
() => isInCourse.value && courseSessions.value.length
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="hasSessionTitle"
|
||||
class="nav-item-base inline-flex items-center lg:inline-flex"
|
||||
>
|
||||
<Popover v-if="showCourseSessionMenu" class="relative">
|
||||
<PopoverButton
|
||||
class="group flex items-center gap-1 rounded-md bg-transparent px-3 text-base focus:outline-none"
|
||||
>
|
||||
<span data-cy="current-course-session-title" class="text-bold">
|
||||
{{ selectedCourseSessionTitle }}
|
||||
</span>
|
||||
<it-icon-arrow-down class="h-6 w-6" />
|
||||
</PopoverButton>
|
||||
<PopoverPanel class="absolute left-0 z-10 mt-3 w-64 px-1 sm:px-0 lg:max-w-3xl">
|
||||
<div
|
||||
class="flex flex-col rounded-lg bg-white p-4 shadow-lg ring-1 ring-black/5"
|
||||
>
|
||||
<h3 class="fond-bold mb-2 text-base text-black">Durchführung</h3>
|
||||
<CourseSessionsMenu
|
||||
:items="courseSessions"
|
||||
:selected="selectedCourseSession?.id"
|
||||
@select="selectCourseSession"
|
||||
/>
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
|
||||
<div v-else data-cy="current-course-session-title" class="text-bold">
|
||||
{{ selectedCourseSessionTitle }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { RouterLink } from "vue-router";
|
||||
// https://router.vuejs.org/guide/advanced/extending-router-link
|
||||
import { isExternalLink as isExternalLinkFn } from "@/utils/navigation";
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
// @ts-expect-error the example above mentions needing @ts-ignore
|
||||
...RouterLink.props,
|
||||
});
|
||||
|
||||
const isExternalLink = computed(() => {
|
||||
return isExternalLinkFn(props.to);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
v-if="isExternalLink"
|
||||
class="flex items-center gap-2"
|
||||
v-bind="$attrs"
|
||||
:href="to"
|
||||
target="_blank"
|
||||
>
|
||||
<slot />
|
||||
<it-icon-external-link class="h-6 w-6" />
|
||||
</a>
|
||||
<!-- make `:to` explicit -->
|
||||
<router-link
|
||||
v-else
|
||||
v-slot="{ isActive, href, navigate }"
|
||||
v-bind="$props"
|
||||
:to="$props.to"
|
||||
custom
|
||||
>
|
||||
<a
|
||||
v-bind="$attrs"
|
||||
:class="isActive ? activeClass : ''"
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
>
|
||||
<slot />
|
||||
</a>
|
||||
</router-link>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<script setup lang="ts">
|
||||
import SubNavItem from "@/components/header/SubNavItem.vue";
|
||||
import { isExternalLink } from "@/utils/navigation";
|
||||
import { Listbox, ListboxOption, ListboxOptions } from "@headlessui/vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
|
||||
export interface EntryRoute {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type EntryOrExternalRoute = EntryRoute | string;
|
||||
|
||||
export interface SubNavEntry {
|
||||
id: number;
|
||||
name: string;
|
||||
route: EntryOrExternalRoute;
|
||||
dataCy?: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
items: SubNavEntry[];
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const isCurrentRoute = (route: { name: string } | string) => {
|
||||
return typeof route !== "string" && route?.name === router.currentRoute.value.name;
|
||||
};
|
||||
const currentRouteName = computed(() => {
|
||||
return props.items.find((item) => isCurrentRoute(item.route))?.name || "";
|
||||
});
|
||||
const open = ref<boolean>(false);
|
||||
const currentRoute = ref(props.items.find((item) => isCurrentRoute(item.route)));
|
||||
const selectRoute = (current: SubNavEntry) => {
|
||||
// we use this to mimic VueRouter's active flag
|
||||
open.value = false;
|
||||
currentRoute.value = current;
|
||||
};
|
||||
|
||||
const internalLinks = computed(() => {
|
||||
return props.items.filter((i) => !isExternalLink(i.route));
|
||||
});
|
||||
const externalLinks = computed(() => {
|
||||
return props.items.filter((i) => isExternalLink(i.route));
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<nav class="border-b bg-white px-4 lg:px-8">
|
||||
<Listbox as="div" :model-value="currentRoute" by="id">
|
||||
<div class="relative w-full py-2 lg:hidden">
|
||||
<button
|
||||
class="relative flex w-full cursor-default flex-row items-center border bg-white py-3 pl-5 pr-10 text-left"
|
||||
@click="open = !open"
|
||||
>
|
||||
{{ currentRouteName }}
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||
>
|
||||
<it-icon-arrow-down class="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
</button>
|
||||
<ListboxOptions
|
||||
v-if="open"
|
||||
class="absolute top-14 z-50 flex w-full cursor-default flex-col rounded-xl border-0 bg-white text-left shadow-lg"
|
||||
static
|
||||
>
|
||||
<ListboxOption
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
v-slot="{ selected }"
|
||||
:value="item"
|
||||
class="relative w-full border-b py-3 pl-10 pr-10 last:border-b-0"
|
||||
>
|
||||
<SubNavItem
|
||||
:to="item.route"
|
||||
class="flex items-center gap-2"
|
||||
@click="selectRoute(item)"
|
||||
>
|
||||
<it-icon-check
|
||||
v-if="selected"
|
||||
class="absolute left-2 top-1/2 w-8 -translate-y-1/2"
|
||||
/>
|
||||
{{ item.name }}
|
||||
</SubNavItem>
|
||||
</ListboxOption>
|
||||
</ListboxOptions>
|
||||
</div>
|
||||
</Listbox>
|
||||
<div class="center hidden items-end justify-between lg:flex">
|
||||
<ul class="flex flex-row gap-10">
|
||||
<li
|
||||
v-for="item in internalLinks"
|
||||
:key="item.id"
|
||||
class="border-t-2 border-t-transparent"
|
||||
:class="{ 'border-b-2 border-b-blue-900': isCurrentRoute(item.route) }"
|
||||
>
|
||||
<SubNavItem :data-cy="item.dataCy" :to="item.route" class="block py-3">
|
||||
{{ item.name }}
|
||||
</SubNavItem>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="flex flex-row gap-10">
|
||||
<li
|
||||
v-for="item in externalLinks"
|
||||
:key="item.id"
|
||||
class="border-b-2 border-t-2 border-b-transparent border-t-transparent"
|
||||
>
|
||||
<SubNavItem :data-cy="item.dataCy" :to="item.route" class="block py-3">
|
||||
{{ item.name }}
|
||||
</SubNavItem>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
|
@ -26,14 +26,18 @@ const hasPendingTasks = computed(() => props.pendingTasks > 0);
|
|||
<div
|
||||
class="flex flex-col items-start justify-between gap-4 border-b py-2 pl-5 pr-5 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
||||
>
|
||||
<div class="flex flex-grow flex-row items-center justify-start">
|
||||
<div
|
||||
class="flex flex-grow flex-row flex-wrap items-center justify-start space-y-4 sm:space-y-0"
|
||||
>
|
||||
<div class="w-80">
|
||||
<div class="font-bold">{{ taskTitle }}</div>
|
||||
<div class="text-small text-gray-900">
|
||||
{{ $t("a.Circle") }} «{{ circleTitle }}»
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-grow flex-row items-center justify-start space-x-2 pl-20">
|
||||
<div
|
||||
class="flex flex-grow flex-row items-center justify-start space-x-2 sm:pl-20"
|
||||
>
|
||||
<template v-if="hasPendingTasks">
|
||||
<div
|
||||
class="flex h-7 w-7 items-center justify-center rounded-full border-2 border-green-500 px-3 py-1 text-sm font-bold"
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ const circles = computed(() => {
|
|||
(c) => props.showCircleSlugs?.includes(c.slug) ?? true
|
||||
);
|
||||
}
|
||||
|
||||
return lpQueryResult.circles.value ?? [];
|
||||
});
|
||||
|
||||
|
|
@ -45,7 +46,10 @@ const wrapperClasses = computed(() => {
|
|||
if (props.diagramType === "horizontal") {
|
||||
classes += " flex-row h-8 space-x-2";
|
||||
} else if (props.diagramType === "horizontalSmall") {
|
||||
classes += " flex-row h-5 space-x-1";
|
||||
classes +=
|
||||
filteredCircles.value.length > 15
|
||||
? " flex-row h-3 space-x-[1.1px]"
|
||||
: " flex-row h-4 space-x-[1.8px]";
|
||||
} else if (props.diagramType === "singleSmall") {
|
||||
classes += " h-8";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
|
|||
|
||||
<PopoverPanel>
|
||||
<div
|
||||
class="absolute right-0 z-10 mt-2 bg-white px-4 py-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none lg:right-2"
|
||||
class="absolute right-0 z-30 mt-2 bg-white px-4 py-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none lg:right-2"
|
||||
>
|
||||
<!-- To close the popover withing your content, use the 'PopoverButton'
|
||||
https://headlessui.com/vue/popover#closing-popovers-manually
|
||||
|
|
|
|||
|
|
@ -32,5 +32,23 @@ const paymentMethods = [
|
|||
<p class="mt-4">
|
||||
{{ $t("shop.paymentCembraByjunoMessage") }}
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
<i18next
|
||||
:translation="
|
||||
$t('a.Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.')
|
||||
"
|
||||
>
|
||||
<template #cembraTos>
|
||||
<a :href="t('cembraTosLink')" target="_blank" class="underline">
|
||||
{{ $t("a.AGB") }}
|
||||
</a>
|
||||
</template>
|
||||
<template #cembraPrivacy>
|
||||
<a :href="t('cembraPrivacyLink')" target="_blank" class="underline">
|
||||
{{ $t("a.Datenschutzerklärung") }}
|
||||
</a>
|
||||
</template>
|
||||
</i18next>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ interface Props {
|
|||
items?: DropdownSelectable[];
|
||||
borderless?: boolean;
|
||||
placeholderText?: string | null;
|
||||
asHeading?: boolean; // style the dropdown to be used as a page heading
|
||||
typeName?: string; // to display the type of the selected item, e.g. `Circle: Fahrzeug` instead of `Fahrzeug`
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -24,6 +26,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
},
|
||||
items: () => [],
|
||||
placeholderText: null,
|
||||
asHeading: false,
|
||||
typeName: "",
|
||||
});
|
||||
|
||||
const dropdownSelected = computed<DropdownSelectable>({
|
||||
|
|
@ -36,26 +40,34 @@ const dropdownSelected = computed<DropdownSelectable>({
|
|||
<Listbox v-model="dropdownSelected" as="div">
|
||||
<div class="relative w-full">
|
||||
<ListboxButton
|
||||
class="relative flex w-full cursor-default flex-row items-center bg-white py-3 pl-5 pr-10 text-left"
|
||||
:class="{
|
||||
border: !props.borderless,
|
||||
'font-bold': !props.borderless,
|
||||
}"
|
||||
:class="[
|
||||
{
|
||||
border: !borderless && !asHeading,
|
||||
'font-bold': !borderless,
|
||||
},
|
||||
asHeading
|
||||
? 'group flex w-full items-center gap-1 rounded-md bg-transparent text-base focus:outline-none'
|
||||
: 'relative flex w-full cursor-default flex-row items-center bg-white py-3 pl-5 pr-10 text-left',
|
||||
]"
|
||||
data-cy="dropdown-select"
|
||||
>
|
||||
<span v-if="dropdownSelected.iconName" class="mr-4">
|
||||
<component :is="dropdownSelected.iconName"></component>
|
||||
</span>
|
||||
<span class="block truncate">
|
||||
{{ dropdownSelected.name }}
|
||||
<span :class="[asHeading ? 'h-11 text-4xl' : '']" class="block truncate">
|
||||
{{ typeName }} {{ dropdownSelected.name }}
|
||||
<span v-if="placeholderText && !dropdownSelected.name" class="text-gray-900">
|
||||
{{ placeholderText }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||
class="pointer-events-none flex items-center pr-2"
|
||||
:class="asHeading ? '' : 'absolute inset-y-0 right-0'"
|
||||
>
|
||||
<it-icon-arrow-down class="h-5 w-5" aria-hidden="true" />
|
||||
<it-icon-arrow-down
|
||||
:class="asHeading ? 'h-12 w-12' : 'h-5 w-5'"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
|
|
@ -79,7 +91,7 @@ const dropdownSelected = computed<DropdownSelectable>({
|
|||
active ? 'bg-blue-900 text-white' : 'text-black',
|
||||
'relative cursor-default select-none py-2 pl-3 pr-9',
|
||||
]"
|
||||
class="flex flex-row items-center"
|
||||
class="group flex flex-row items-center"
|
||||
:data-cy="`dropdown-select-option-${item.name}`"
|
||||
>
|
||||
<span v-if="item.iconName" class="mr-4">
|
||||
|
|
@ -98,7 +110,11 @@ const dropdownSelected = computed<DropdownSelectable>({
|
|||
v-if="dropdownSelected"
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-4 text-blue-900"
|
||||
>
|
||||
<it-icon-check v-if="selected" class="h-5 w-5" aria-hidden="true" />
|
||||
<it-icon-check
|
||||
v-if="selected"
|
||||
class="h-5 w-5 fill-current group-hover:text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const removeNoScroll = () => {
|
|||
<div
|
||||
v-if="show"
|
||||
data-cy="full-screen-modal"
|
||||
class="fixed top-0 h-full w-full overflow-y-scroll bg-white px-4 py-16 lg:px-16 lg:py-24"
|
||||
class="fixed top-0 z-20 h-full w-full overflow-y-scroll bg-white px-4 py-16 lg:px-16 lg:py-24"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="flex flex-col sm:flex-row">
|
||||
<div class="flex-none border-r bg-white p-4 lg:p-8">
|
||||
<slot name="side"></slot>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -722,13 +722,25 @@ export function useVVByLink() {
|
|||
return { href };
|
||||
}
|
||||
|
||||
export function useAllCompetenceCertificates(userId: string, courseSlug: string) {
|
||||
export function useAllCompetenceCertificates(
|
||||
userId: string,
|
||||
courseSlug: string,
|
||||
currentCourseSessionOnly: boolean = false
|
||||
) {
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const certificateQueries = courseSessionsStore.allCourseSessions.map(
|
||||
(courseSession) => {
|
||||
let certificateQueries;
|
||||
if (currentCourseSessionOnly) {
|
||||
const courseSession = useCurrentCourseSession();
|
||||
certificateQueries = [
|
||||
useCertificateQuery([userId], courseSlug, courseSession.value).certificatesQuery,
|
||||
];
|
||||
} else {
|
||||
// wtf
|
||||
certificateQueries = courseSessionsStore.allCourseSessions.map((courseSession) => {
|
||||
// todo: use a single query, instead of one for every courseSession
|
||||
return useCertificateQuery([userId], courseSlug, courseSession).certificatesQuery;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const competenceCertificatesPerCs = computed(() =>
|
||||
certificateQueries.map((query) => {
|
||||
|
|
|
|||
|
|
@ -662,6 +662,23 @@ export type LearningContentEdoniqTestObjectType = CoursePageInterface & Learning
|
|||
translation_key: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type LearningContentFeedbackAutomobilGewerbeObjectType = CoursePageInterface & LearningContentInterface & {
|
||||
__typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType';
|
||||
can_user_self_toggle_course_completion: Scalars['Boolean']['output'];
|
||||
circle?: Maybe<CircleLightObjectType>;
|
||||
content_type: Scalars['String']['output'];
|
||||
content_url: Scalars['String']['output'];
|
||||
course?: Maybe<CourseObjectType>;
|
||||
description: Scalars['String']['output'];
|
||||
frontend_url: Scalars['String']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
live: Scalars['Boolean']['output'];
|
||||
minutes?: Maybe<Scalars['Int']['output']>;
|
||||
slug: Scalars['String']['output'];
|
||||
title: Scalars['String']['output'];
|
||||
translation_key: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type LearningContentFeedbackUkObjectType = CoursePageInterface & LearningContentInterface & {
|
||||
__typename?: 'LearningContentFeedbackUKObjectType';
|
||||
can_user_self_toggle_course_completion: Scalars['Boolean']['output'];
|
||||
|
|
@ -973,6 +990,7 @@ export type Query = {
|
|||
learning_content_assignment?: Maybe<LearningContentAssignmentObjectType>;
|
||||
learning_content_attendance_course?: Maybe<LearningContentAttendanceCourseObjectType>;
|
||||
learning_content_document_list?: Maybe<LearningContentDocumentListObjectType>;
|
||||
learning_content_feedback_automobil_gewerbe?: Maybe<LearningContentFeedbackAutomobilGewerbeObjectType>;
|
||||
learning_content_feedback_uk?: Maybe<LearningContentFeedbackUkObjectType>;
|
||||
learning_content_feedback_vv?: Maybe<LearningContentFeedbackVvObjectType>;
|
||||
learning_content_knowledge_assessment?: Maybe<LearningContentKnowledgeAssessmentObjectType>;
|
||||
|
|
@ -1206,6 +1224,8 @@ type CoursePageFieldsLearningContentDocumentListObjectTypeFragment = { __typenam
|
|||
|
||||
type CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment = { __typename?: 'LearningContentEdoniqTestObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment' };
|
||||
|
||||
type CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment = { __typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment' };
|
||||
|
||||
type CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment = { __typename?: 'LearningContentFeedbackUKObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment' };
|
||||
|
||||
type CoursePageFieldsLearningContentFeedbackVvObjectTypeFragment = { __typename?: 'LearningContentFeedbackVVObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsLearningContentFeedbackVvObjectTypeFragment' };
|
||||
|
|
@ -1232,7 +1252,7 @@ type CoursePageFieldsPerformanceCriteriaObjectTypeFragment = { __typename?: 'Per
|
|||
|
||||
type CoursePageFieldsTopicObjectTypeFragment = { __typename?: 'TopicObjectType', title: string, id: string, slug: string, content_type: string, frontend_url: string } & { ' $fragmentName'?: 'CoursePageFieldsTopicObjectTypeFragment' };
|
||||
|
||||
export type CoursePageFieldsFragment = CoursePageFieldsActionCompetenceObjectTypeFragment | CoursePageFieldsAssignmentObjectTypeFragment | CoursePageFieldsCircleObjectTypeFragment | CoursePageFieldsCompetenceCertificateListObjectTypeFragment | CoursePageFieldsCompetenceCertificateObjectTypeFragment | CoursePageFieldsLearningContentAssignmentObjectTypeFragment | CoursePageFieldsLearningContentAttendanceCourseObjectTypeFragment | CoursePageFieldsLearningContentDocumentListObjectTypeFragment | CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment | CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment | CoursePageFieldsLearningContentFeedbackVvObjectTypeFragment | CoursePageFieldsLearningContentKnowledgeAssessmentObjectTypeFragment | CoursePageFieldsLearningContentLearningModuleObjectTypeFragment | CoursePageFieldsLearningContentMediaLibraryObjectTypeFragment | CoursePageFieldsLearningContentPlaceholderObjectTypeFragment | CoursePageFieldsLearningContentRichTextObjectTypeFragment | CoursePageFieldsLearningContentVideoObjectTypeFragment | CoursePageFieldsLearningPathObjectTypeFragment | CoursePageFieldsLearningSequenceObjectTypeFragment | CoursePageFieldsLearningUnitObjectTypeFragment | CoursePageFieldsPerformanceCriteriaObjectTypeFragment | CoursePageFieldsTopicObjectTypeFragment;
|
||||
export type CoursePageFieldsFragment = CoursePageFieldsActionCompetenceObjectTypeFragment | CoursePageFieldsAssignmentObjectTypeFragment | CoursePageFieldsCircleObjectTypeFragment | CoursePageFieldsCompetenceCertificateListObjectTypeFragment | CoursePageFieldsCompetenceCertificateObjectTypeFragment | CoursePageFieldsLearningContentAssignmentObjectTypeFragment | CoursePageFieldsLearningContentAttendanceCourseObjectTypeFragment | CoursePageFieldsLearningContentDocumentListObjectTypeFragment | CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment | CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment | CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment | CoursePageFieldsLearningContentFeedbackVvObjectTypeFragment | CoursePageFieldsLearningContentKnowledgeAssessmentObjectTypeFragment | CoursePageFieldsLearningContentLearningModuleObjectTypeFragment | CoursePageFieldsLearningContentMediaLibraryObjectTypeFragment | CoursePageFieldsLearningContentPlaceholderObjectTypeFragment | CoursePageFieldsLearningContentRichTextObjectTypeFragment | CoursePageFieldsLearningContentVideoObjectTypeFragment | CoursePageFieldsLearningPathObjectTypeFragment | CoursePageFieldsLearningSequenceObjectTypeFragment | CoursePageFieldsLearningUnitObjectTypeFragment | CoursePageFieldsPerformanceCriteriaObjectTypeFragment | CoursePageFieldsTopicObjectTypeFragment;
|
||||
|
||||
export type AttendanceCheckQueryQueryVariables = Exact<{
|
||||
courseSessionId: Scalars['ID']['input'];
|
||||
|
|
@ -1276,6 +1296,9 @@ export type CompetenceCertificateQueryQuery = { __typename?: 'Query', competence
|
|||
) | (
|
||||
{ __typename?: 'LearningContentEdoniqTestObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
|
||||
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment': CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment } }
|
||||
) | (
|
||||
{ __typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
|
||||
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment': CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment } }
|
||||
) | (
|
||||
{ __typename?: 'LearningContentFeedbackUKObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
|
||||
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment': CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment } }
|
||||
|
|
@ -1330,6 +1353,9 @@ export type CompetenceCertificateForUserQueryQuery = { __typename?: 'Query', com
|
|||
) | (
|
||||
{ __typename?: 'LearningContentEdoniqTestObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
|
||||
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment': CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment } }
|
||||
) | (
|
||||
{ __typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
|
||||
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment': CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment } }
|
||||
) | (
|
||||
{ __typename?: 'LearningContentFeedbackUKObjectType', circle?: { __typename?: 'CircleLightObjectType', id: string, title: string, slug: string } | null }
|
||||
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment': CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment } }
|
||||
|
|
@ -1407,6 +1433,9 @@ export type CourseQueryQuery = { __typename?: 'Query', course?: { __typename?: '
|
|||
& { ' $fragmentRefs'?: { 'CoursePageFieldsCompetenceCertificateObjectTypeFragment': CoursePageFieldsCompetenceCertificateObjectTypeFragment } }
|
||||
) | null }
|
||||
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment': CoursePageFieldsLearningContentEdoniqTestObjectTypeFragment } }
|
||||
) | (
|
||||
{ __typename?: 'LearningContentFeedbackAutomobilGewerbeObjectType', can_user_self_toggle_course_completion: boolean, content_url: string, minutes?: number | null, description: string }
|
||||
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment': CoursePageFieldsLearningContentFeedbackAutomobilGewerbeObjectTypeFragment } }
|
||||
) | (
|
||||
{ __typename?: 'LearningContentFeedbackUKObjectType', can_user_self_toggle_course_completion: boolean, content_url: string, minutes?: number | null, description: string }
|
||||
& { ' $fragmentRefs'?: { 'CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment': CoursePageFieldsLearningContentFeedbackUkObjectTypeFragment } }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ type Query {
|
|||
learning_content_attendance_course: LearningContentAttendanceCourseObjectType
|
||||
learning_content_feedback_uk: LearningContentFeedbackUKObjectType
|
||||
learning_content_feedback_vv: LearningContentFeedbackVVObjectType
|
||||
learning_content_feedback_automobil_gewerbe: LearningContentFeedbackAutomobilGewerbeObjectType
|
||||
learning_content_learning_module: LearningContentLearningModuleObjectType
|
||||
learning_content_knowledge_assessment: LearningContentKnowledgeAssessmentObjectType
|
||||
learning_content_placeholder: LearningContentPlaceholderObjectType
|
||||
|
|
@ -819,6 +820,22 @@ type LearningContentFeedbackVVObjectType implements CoursePageInterface & Learni
|
|||
circle: CircleLightObjectType
|
||||
}
|
||||
|
||||
type LearningContentFeedbackAutomobilGewerbeObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
id: ID!
|
||||
title: String!
|
||||
slug: String!
|
||||
content_type: String!
|
||||
live: Boolean!
|
||||
translation_key: String!
|
||||
frontend_url: String!
|
||||
course: CourseObjectType
|
||||
minutes: Int
|
||||
description: String!
|
||||
content_url: String!
|
||||
can_user_self_toggle_course_completion: Boolean!
|
||||
circle: CircleLightObjectType
|
||||
}
|
||||
|
||||
type LearningContentLearningModuleObjectType implements CoursePageInterface & LearningContentInterface {
|
||||
id: ID!
|
||||
title: String!
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export const LearningContentAssignmentObjectType = "LearningContentAssignmentObj
|
|||
export const LearningContentAttendanceCourseObjectType = "LearningContentAttendanceCourseObjectType";
|
||||
export const LearningContentDocumentListObjectType = "LearningContentDocumentListObjectType";
|
||||
export const LearningContentEdoniqTestObjectType = "LearningContentEdoniqTestObjectType";
|
||||
export const LearningContentFeedbackAutomobilGewerbeObjectType = "LearningContentFeedbackAutomobilGewerbeObjectType";
|
||||
export const LearningContentFeedbackUKObjectType = "LearningContentFeedbackUKObjectType";
|
||||
export const LearningContentFeedbackVVObjectType = "LearningContentFeedbackVVObjectType";
|
||||
export const LearningContentInterface = "LearningContentInterface";
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"a.Abgabetermin": "Abgabetermin",
|
||||
"a.Abgezogene Punkte": "Abgezogene Punkte",
|
||||
"a.Adresse": "Adresse",
|
||||
"a.AGB": "AGB",
|
||||
"a.Aktuell begleitest du niemanden als Lernbegleitung.": "Aktuell begleitest du niemanden als Lernbegleitung.",
|
||||
"a.Aktuell begleitest du niemanden als Praxisbildner.": "Aktuell begleitest du niemanden als Praxisbildner.",
|
||||
"a.Aktuell bist du leider keiner Durchführung zugewiesen.": "Aktuell bist du leider keiner Durchführung zugewiesen.",
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
"a.An Durchführung teilnehmen": "An Durchführung teilnehmen",
|
||||
"a.Anmelden": "Anmelden",
|
||||
"a.Anwesenheit": "Anwesenheit",
|
||||
"a.Anwesenheit anschauen": "Anwesenheit anschauen",
|
||||
"a.Anwesenheit Präsenzkurse": "Anwesenheit Präsenzkurse",
|
||||
"a.Anwesenheitskontrolle Präsenzkurse": "Anwesenheitskontrolle Präsenzkurse",
|
||||
"a.Arbeiten": "Arbeiten",
|
||||
|
|
@ -69,9 +71,11 @@
|
|||
"a.Damit du myVBV nutzen kannst, brauchst du ein Konto.": "Damit du myVBV nutzen kannst, brauchst du ein Konto.",
|
||||
"a.Das muss ich nochmals anschauen": "Das muss ich nochmals anschauen",
|
||||
"a.Das wurde mit dir geteilt": "Das wurde mit dir geteilt",
|
||||
"a.Dashboard": "Dashboard",
|
||||
"a.Datei auswählen": "Datei auswählen",
|
||||
"a.Datei hochladen": "Datei hochladen",
|
||||
"a.Datei kann nicht gespeichert werden.": "Datei kann nicht gespeichert werden.",
|
||||
"a.Datenschutzerklärung": "Datenschutzerklärung",
|
||||
"a.Datum": "Datum",
|
||||
"a.Debit-/Kreditkarte/Twint": "Debit-/Kreditkarte/Twint",
|
||||
"a.Dein Feedback für x y wurde freigegeben.": "Dein Feedback für {{x}} {{y}} wurde freigegeben.",
|
||||
|
|
@ -81,6 +85,8 @@
|
|||
"a.Deine Änderungen wurden gespeichert": "Deine Änderungen wurden gespeichert",
|
||||
"a.Der Lehrgang und die Prüfung zum Erwerb des Verbandszertifikats als Versicherungsvermittler/-in.": "Der Lehrgang und die Prüfung zum Erwerb des Verbandszertifikats als Versicherungsvermittler/-in.",
|
||||
"a.Der Preis für den Lehrgang {course} beträgt {price}.": "Der Preis für den Lehrgang {course} beträgt {price} exkl. MWSt.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._one": "Der Präsenzkurs findet in einem Tag statt.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._other": "Der Präsenzkurs findet in {{count}} Tagen statt.",
|
||||
"a.Details anschauen": "Details anschauen",
|
||||
"a.Details anzeigen": "Details anzeigen",
|
||||
"a.Deutsch": "Deutsch",
|
||||
|
|
@ -92,6 +98,7 @@
|
|||
"a.Du hast alles erledigt.": "Du hast alles erledigt.",
|
||||
"a.Du hast deine Fremdeinschätzung freigegeben": "Du hast deine Fremdeinschätzung freigegeben.",
|
||||
"a.Du hast deine Selbsteinschätzung erfolgreich mit FULL_NAME geteilt.": "Du hast deine Selbsteinschätzung erfolgreich mit {{FULL_NAME}} geteilt.",
|
||||
"a.Du hast die Anwesenheit bestätigt.": "Du hast die Anwesenheit bestätigt.",
|
||||
"a.Du hast die Einladung von {name} erfolgreich akzeptiert.": "Du hast die Einladung von {name} erfolgreich akzeptiert.",
|
||||
"a.Du hast erfolgreich ein Konto für EMAIL erstellt.": "Du hast erfolgreich ein Konto für {{email}} erstellt.",
|
||||
"a.Du kannst deine Selbsteinschätzung mit deiner Lernbegleitung teilen, damit sie eine Fremdeinschätzung vornimmt.": "Du kannst deine Selbsteinschätzung mit deiner Lernbegleitung teilen, damit sie eine Fremdeinschätzung vornimmt.",
|
||||
|
|
@ -102,6 +109,7 @@
|
|||
"a.E-Mail Adresse": "E-Mail Adresse",
|
||||
"a.Einladung": "Einladung",
|
||||
"a.Einladung abschicken": "Einladung abschicken",
|
||||
"a.Einstellungen": "Einstellungen",
|
||||
"a.Elemente zu erledigen": "Elemente zu erledigen",
|
||||
"a.Email": "Email",
|
||||
"a.Entfernen": "Entfernen",
|
||||
|
|
@ -113,6 +121,7 @@
|
|||
"a.Ergebnisse bewerten": "Ergebnisse bewerten",
|
||||
"a.Ergebnisse teilen": "Ergebnisse teilen",
|
||||
"a.Erneut bearbeiten": "Erneut bearbeiten",
|
||||
"a.Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.": "Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.",
|
||||
"a.Experte": "Experte",
|
||||
"a.Feedback abschliessen": "Feedback abschliessen",
|
||||
"a.Feedback ansehen": "Feedback ansehen",
|
||||
|
|
@ -208,6 +217,7 @@
|
|||
"a.Nicht bestanden": "Nicht bestanden",
|
||||
"a.Nicht bewertet": "Nicht bewertet",
|
||||
"a.Nichtleben": "Nichtleben",
|
||||
"a.Noch nicht bestätigt": "Noch nicht bestätigt",
|
||||
"a.Note": "Note",
|
||||
"a.NUMBER Elemente abgeschlossen": "{NUMBER} Elemente abgeschlossen",
|
||||
"a.NUMBER Präsenztage abgeschlossen": "{NUMBER} Präsenztage abgeschlossen",
|
||||
|
|
@ -219,6 +229,7 @@
|
|||
"a.Personen, die du begleitest": "Personen, die du begleitest ",
|
||||
"a.Persönliche Informationen": "Persönliche Informationen",
|
||||
"a.PLZ": "PLZ",
|
||||
"a.Postleizahl hat das falsche Format": "Postleizahl hat das falsche Format",
|
||||
"a.Praxisauftrag": "Praxisauftrag",
|
||||
"a.Praxisaufträge anschauen": "Praxisaufträge anschauen",
|
||||
"a.Praxisbildner": "Praxisbildner",
|
||||
|
|
@ -263,6 +274,7 @@
|
|||
"a.Teilnehmer": "Teilnehmer",
|
||||
"a.Teilnehmer im": "Teilnehmer im",
|
||||
"a.Teilnehmer nach Zulassungsprofilen im": "Teilnehmer nach Zulassungsprofilen im",
|
||||
"a.Teilnehmer Vorschau": "Teilnehmer Vorschau",
|
||||
"a.Telefonnummer": "Telefonnummer",
|
||||
"a.Telefonnummer hat das falsche Format": "Telefonnummer hat das falsche Format",
|
||||
"a.Termin": "Termin",
|
||||
|
|
@ -302,6 +314,7 @@
|
|||
"a.Überbetriebliche Kurse": "Überbetriebliche Kurse",
|
||||
"a.Übergangslösung Innendienst-Mitarbeitende": "Übergangslösung Innendienst-Mitarbeitende",
|
||||
"a.Überprüfe deine Eingaben unten und gib anschliessend deine Fremdeinschätzung für FEEDBACK_REQUESTER frei": "Überprüfe deine Eingaben unten und gib anschliessend deine Fremdeinschätzung für {{FEEDBACK_REQUESTER}} frei.",
|
||||
"a.Überprüfe jetzt die Anwesenheit.": "Überprüfe jetzt die Anwesenheit.",
|
||||
"a.Übersicht": "Übersicht",
|
||||
"a.Übersicht anschauen": "Übersicht anschauen",
|
||||
"Abgabe": "Abgabe",
|
||||
|
|
@ -349,6 +362,8 @@
|
|||
"Berufsbildner": "Berufsbildner",
|
||||
"Bestanden": "Bestanden",
|
||||
"Bewertung von x y": "Bewertung von {{x}} {{y}}",
|
||||
"cembraPrivacyLink": "https://cembrapay.ch/de/privacy",
|
||||
"cembraTosLink": "https://cembrapay.ch/de/terms/CP",
|
||||
"Circle": "Circle",
|
||||
"circlePage.circleContentBoxTitle": "Das lernst du in diesem Circle",
|
||||
"circlePage.contactExpertButton": "Trainer/-in kontaktieren",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"a.Abgabetermin": "Date de remise",
|
||||
"a.Abgezogene Punkte": "Points déduits",
|
||||
"a.Adresse": "Adresse",
|
||||
"a.AGB": "CGV",
|
||||
"a.Aktuell begleitest du niemanden als Lernbegleitung.": "Actuellement, vous n'accompagnez personne en tant que mentor d'apprentissage.",
|
||||
"a.Aktuell begleitest du niemanden als Praxisbildner.": "Actuellement, vous n'accompagnez personne en tant que formateur/-trice pratique.",
|
||||
"a.Aktuell bist du leider keiner Durchführung zugewiesen.": "Actuellement, vous n'êtes malheureusement affecté à aucune session.",
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
"a.An Durchführung teilnehmen": "Participer à la session",
|
||||
"a.Anmelden": "Connexion",
|
||||
"a.Anwesenheit": "Présence",
|
||||
"a.Anwesenheit anschauen": "Voir le contrôle de présence",
|
||||
"a.Anwesenheit Präsenzkurse": "Présence aux cours",
|
||||
"a.Anwesenheitskontrolle Präsenzkurse": "Contrôle de présence aux cours",
|
||||
"a.Arbeiten": "Travaux",
|
||||
|
|
@ -69,9 +71,11 @@
|
|||
"a.Damit du myVBV nutzen kannst, brauchst du ein Konto.": "Pour utiliser myVBV, vous devez créer un compte.",
|
||||
"a.Das muss ich nochmals anschauen": "Il faut que je regarde cela encore une fois de plus près",
|
||||
"a.Das wurde mit dir geteilt": "Cela a été partagé avec toi",
|
||||
"a.Dashboard": "Dashboard",
|
||||
"a.Datei auswählen": "Sélectionner le fichier",
|
||||
"a.Datei hochladen": "Télécharger le fichier",
|
||||
"a.Datei kann nicht gespeichert werden.": "Impossible d'enregistrer le fichier.",
|
||||
"a.Datenschutzerklärung": "protection des données",
|
||||
"a.Datum": "Date",
|
||||
"a.Debit-/Kreditkarte/Twint": "Carte de débit/crédit / Twint",
|
||||
"a.Dein Feedback für x y wurde freigegeben.": "Ton feedback pour {{x}} {{y}} a été validé.",
|
||||
|
|
@ -81,6 +85,8 @@
|
|||
"a.Deine Änderungen wurden gespeichert": "Tes modifications ont été enregistrées",
|
||||
"a.Der Lehrgang und die Prüfung zum Erwerb des Verbandszertifikats als Versicherungsvermittler/-in.": "Le cours et l'examen pour obtenir le certificat d'association comme courtier/agent d'assurance.",
|
||||
"a.Der Preis für den Lehrgang {course} beträgt {price}.": "Le prix de la formation {course} est de {price} hors TVA.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._one": "Le cours de présence se déroule en une jour.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._other": "Le cours de présence se déroule en {{count}} jours.",
|
||||
"a.Details anschauen": "Voir les détails",
|
||||
"a.Details anzeigen": "Afficher les détails",
|
||||
"a.Deutsch": "Allemand",
|
||||
|
|
@ -92,6 +98,7 @@
|
|||
"a.Du hast alles erledigt.": "Tu as tout fini.",
|
||||
"a.Du hast deine Fremdeinschätzung freigegeben": "Tu as autorisé ton évaluation externe.",
|
||||
"a.Du hast deine Selbsteinschätzung erfolgreich mit FULL_NAME geteilt.": "Tu as partagé avec succès ton auto-évaluation avec {{FULL_NAME}}.",
|
||||
"a.Du hast die Anwesenheit bestätigt.": "Tu as confirmé la présence.",
|
||||
"a.Du hast die Einladung von {name} erfolgreich akzeptiert.": "Tu as accepté avec succès l'invitation de {name}.",
|
||||
"a.Du hast erfolgreich ein Konto für EMAIL erstellt.": "Vous avez créé un compte avec succès pour {{email}}.",
|
||||
"a.Du kannst deine Selbsteinschätzung mit deiner Lernbegleitung teilen, damit sie eine Fremdeinschätzung vornimmt.": "Tu peux partager ton auto-évaluation avec ton accompagnateur d'apprentissage afin qu'il puisse effectuer une évaluation externe.",
|
||||
|
|
@ -102,6 +109,7 @@
|
|||
"a.E-Mail Adresse": "Adresse e-mail",
|
||||
"a.Einladung": "Invitation",
|
||||
"a.Einladung abschicken": "Envoyer l'invitation",
|
||||
"a.Einstellungen": "Paramètres",
|
||||
"a.Elemente zu erledigen": "Eléments à faire",
|
||||
"a.Email": "Email",
|
||||
"a.Entfernen": "Supprimer",
|
||||
|
|
@ -113,6 +121,7 @@
|
|||
"a.Ergebnisse bewerten": "Évaluer les résultats",
|
||||
"a.Ergebnisse teilen": "Partager les résultats",
|
||||
"a.Erneut bearbeiten": "Modifier à nouveau",
|
||||
"a.Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.": "Les {cembraTos} et la déclaration de {cembraPrivacy} de CembraPay AG s'appliquent.",
|
||||
"a.Experte": "Expert",
|
||||
"a.Feedback abschliessen": "Terminer le feedback",
|
||||
"a.Feedback ansehen": "Voir le feedback",
|
||||
|
|
@ -208,6 +217,7 @@
|
|||
"a.Nicht bestanden": "Échoué",
|
||||
"a.Nicht bewertet": "Non évalué",
|
||||
"a.Nichtleben": "Non-vie",
|
||||
"a.Noch nicht bestätigt": "Pas encore confirmé",
|
||||
"a.Note": "Note",
|
||||
"a.NUMBER Elemente abgeschlossen": "{NUMBER} éléments terminés",
|
||||
"a.NUMBER Präsenztage abgeschlossen": "{NUMBER} jours de présence complétés",
|
||||
|
|
@ -219,6 +229,7 @@
|
|||
"a.Personen, die du begleitest": "Personnes que tu accompagnes",
|
||||
"a.Persönliche Informationen": "Informations personnelles",
|
||||
"a.PLZ": "Code postal",
|
||||
"a.Postleizahl hat das falsche Format": "Le code postal n'a pas le bon format",
|
||||
"a.Praxisauftrag": "Exercice pratique",
|
||||
"a.Praxisaufträge anschauen": "Voir les missions pratiques",
|
||||
"a.Praxisbildner": "Formateur pratique",
|
||||
|
|
@ -263,6 +274,7 @@
|
|||
"a.Teilnehmer": "Participants",
|
||||
"a.Teilnehmer im": "Participants en",
|
||||
"a.Teilnehmer nach Zulassungsprofilen im": "Participants par profil d'admission en",
|
||||
"a.Teilnehmer Vorschau": "Aperçu des participants",
|
||||
"a.Telefonnummer": "Numéro de téléphone",
|
||||
"a.Telefonnummer hat das falsche Format": "Le numéro de téléphone n'est pas au bon format",
|
||||
"a.Termin": "Date",
|
||||
|
|
@ -302,6 +314,7 @@
|
|||
"a.Überbetriebliche Kurse": "Cours interentreprises",
|
||||
"a.Übergangslösung Innendienst-Mitarbeitende": "Solution transitoire pour le service interne",
|
||||
"a.Überprüfe deine Eingaben unten und gib anschliessend deine Fremdeinschätzung für FEEDBACK_REQUESTER frei": "Vérifie tes saisies ci-dessous et libère ensuite ton évaluation externe pour {{FEEDBACK_REQUESTER}}.",
|
||||
"a.Überprüfe jetzt die Anwesenheit.": "Vérifie maintenant la présence.",
|
||||
"a.Übersicht": "Aperçu",
|
||||
"a.Übersicht anschauen": "Consulter l'aperçu",
|
||||
"Abgabe": "Remise",
|
||||
|
|
@ -349,6 +362,8 @@
|
|||
"Berufsbildner": "Formateur professionnel",
|
||||
"Bestanden": "Réussi",
|
||||
"Bewertung von x y": "Évaluation de {{x}} {{y}}",
|
||||
"cembraPrivacyLink": "https://cembrapay.ch/fr/privacy",
|
||||
"cembraTosLink": "https://cembrapay.ch/fr/terms/CP",
|
||||
"Circle": "Cercle",
|
||||
"circlePage.circleContentBoxTitle": "Ce que tu vas apprendre dans ce Circle",
|
||||
"circlePage.contactExpertButton": "Contacter le formateur / la formatrice",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"a.An Durchführung teilnehmen": "Partecipare alla sessione",
|
||||
"a.Anmelden": "Login",
|
||||
"a.Anwesenheit": "Presenza",
|
||||
"a.Anwesenheit anschauen": "Visualizza il controllo di presenza",
|
||||
"a.Anwesenheit Präsenzkurse": "Presenza ai corsi",
|
||||
"a.Anwesenheitskontrolle Präsenzkurse": "Controllo di presenza ai corsi",
|
||||
"a.Arbeiten": "Lavori",
|
||||
|
|
@ -69,6 +70,7 @@
|
|||
"a.Damit du myVBV nutzen kannst, brauchst du ein Konto.": "Per utilizzare myVBV, hai bisogno di un account.",
|
||||
"a.Das muss ich nochmals anschauen": "Devo riguardarlo ancora una volta",
|
||||
"a.Das wurde mit dir geteilt": "Questo è stato condiviso con te",
|
||||
"a.Dashboard": "Dashboard",
|
||||
"a.Datei auswählen": "Selezionare il file",
|
||||
"a.Datei hochladen": "Carica il file",
|
||||
"a.Datei kann nicht gespeichert werden.": "Impossibile salvare il file.",
|
||||
|
|
@ -81,6 +83,8 @@
|
|||
"a.Deine Änderungen wurden gespeichert": "Le tue modifiche sono state salvate",
|
||||
"a.Der Lehrgang und die Prüfung zum Erwerb des Verbandszertifikats als Versicherungsvermittler/-in.": "Il corso e l'esame per ottenere il certificato di associazione come intermediario/agente di assicurazione.",
|
||||
"a.Der Preis für den Lehrgang {course} beträgt {price}.": "Il prezzo del {corso} è {prezzo} IVA esclusa.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._one": "Il corso di presenza si svolge in un giorno.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._other": "Il corso di presenza si svolge in {{count}} giorni.",
|
||||
"a.Details anschauen": "Visualizza dettagli",
|
||||
"a.Details anzeigen": "Mostrare i dettagli",
|
||||
"a.Deutsch": "Tedesco",
|
||||
|
|
@ -92,6 +96,7 @@
|
|||
"a.Du hast alles erledigt.": "Hai fatto tutto.",
|
||||
"a.Du hast deine Fremdeinschätzung freigegeben": "Hai rilasciato la tua valutazione esterna.",
|
||||
"a.Du hast deine Selbsteinschätzung erfolgreich mit FULL_NAME geteilt.": "Hai condiviso con successo la tua autovalutazione con {{FULL_NAME}}.",
|
||||
"a.Du hast die Anwesenheit bestätigt.": "Hai confermato la presenza.",
|
||||
"a.Du hast die Einladung von {name} erfolgreich akzeptiert.": "Hai accettato con successo l'invito di {name}.",
|
||||
"a.Du hast erfolgreich ein Konto für EMAIL erstellt.": "Hai creato con successo un account per {{email}}.",
|
||||
"a.Du kannst deine Selbsteinschätzung mit deiner Lernbegleitung teilen, damit sie eine Fremdeinschätzung vornimmt.": "Puoi condividere la tua autovalutazione con il tuo tutor didattico affinché possa effettuare una valutazione esterna.",
|
||||
|
|
@ -102,6 +107,7 @@
|
|||
"a.E-Mail Adresse": "Indirizzo e-mail",
|
||||
"a.Einladung": "Invito",
|
||||
"a.Einladung abschicken": "Inviare l'invito",
|
||||
"a.Einstellungen": "Impostazioni",
|
||||
"a.Elemente zu erledigen": "Elementi da completare",
|
||||
"a.Email": "E-mail",
|
||||
"a.Entfernen": "Rimuovere",
|
||||
|
|
@ -302,6 +308,7 @@
|
|||
"a.Überbetriebliche Kurse": "Corsi interaziendali",
|
||||
"a.Übergangslösung Innendienst-Mitarbeitende": "Soluzione transitoria per il servizio interno",
|
||||
"a.Überprüfe deine Eingaben unten und gib anschliessend deine Fremdeinschätzung für FEEDBACK_REQUESTER frei": "Controlla le tue voci qui sotto e poi rilascia la tua valutazione esterna per {{FEEDBACK_REQUESTER}}.",
|
||||
"a.Überprüfe jetzt die Anwesenheit.": "Controllare la presenza ora.",
|
||||
"a.Übersicht": "Panoramica",
|
||||
"a.Übersicht anschauen": "Vedere la panoramica",
|
||||
"Abgabe": "Consegna",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@
|
|||
v-else-if="feedbackType === 'uk'"
|
||||
:feedback-data="feedbackData"
|
||||
/>
|
||||
<FeedbackPageAutomobilgewerbe
|
||||
v-else-if="feedbackType === 'automobilgewerbe'"
|
||||
:feedback-data="feedbackData"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -42,6 +46,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { itGet } from "@/fetchHelpers";
|
||||
import FeedbackPageAutomobilgewerbe from "@/pages/cockpit/FeedbackPageAutomobilgewerbe.vue";
|
||||
import FeedbackPageUK from "@/pages/cockpit/FeedbackPageUK.vue";
|
||||
import FeedbackPageVV from "@/pages/cockpit/FeedbackPageVV.vue";
|
||||
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||
|
|
@ -84,7 +89,7 @@ onMounted(async () => {
|
|||
log.debug("FeedbackPage feedbackData", feedbackData.value);
|
||||
if (
|
||||
feedbackData.value &&
|
||||
["uk", "vv"].includes(feedbackData.value?.feedbackType ?? "")
|
||||
["uk", "vv", "automobilgewerbe"].includes(feedbackData.value?.feedbackType ?? "")
|
||||
) {
|
||||
feedbackType.value = feedbackData.value.feedbackType;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<FeedbackResults
|
||||
:ordered-questions="orderedQuestions"
|
||||
:feedback-data="feedbackData"
|
||||
:rating-keys="ratingKeys"
|
||||
:vertical-chart-keys="verticalChartKeys"
|
||||
:horizontal-chart-keys="horizontalChartKeys"
|
||||
:open-keys="openKeys"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FeedbackResults from "@/pages/cockpit/FeedbackResults.vue";
|
||||
import type { FeedbackData } from "@/types";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import * as log from "loglevel";
|
||||
|
||||
defineProps<{
|
||||
feedbackData: FeedbackData;
|
||||
}>();
|
||||
|
||||
log.debug("FeedbackPageVV created");
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const orderedQuestions = [
|
||||
{
|
||||
key: "satisfaction",
|
||||
question: t("feedback.satisfactionLabel"),
|
||||
},
|
||||
{
|
||||
key: "goal_attainment",
|
||||
question: t("feedback.goalAttainmentLabel"),
|
||||
},
|
||||
{
|
||||
key: "proficiency",
|
||||
question: t("feedback.proficiencyLabelVV"),
|
||||
},
|
||||
{
|
||||
key: "would_recommend",
|
||||
question: t("feedback.recommendLabelVV"),
|
||||
},
|
||||
{
|
||||
key: "course_negative_feedback",
|
||||
question: t("feedback.courseNegativeFeedbackLabel"),
|
||||
},
|
||||
{
|
||||
key: "course_positive_feedback",
|
||||
question: t("feedback.coursePositiveFeedbackLabel"),
|
||||
},
|
||||
];
|
||||
|
||||
const ratingKeys = ["satisfaction", "goal_attainment"];
|
||||
const verticalChartKeys = ["preparation_task_clarity", "would_recommend"];
|
||||
const horizontalChartKeys = ["proficiency"];
|
||||
const openKeys = ["course_negative_feedback", "course_positive_feedback"];
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -1,27 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import type { AttendanceUserStatus } from "@/gql/graphql";
|
||||
import { graphqlClient } from "@/graphql/client";
|
||||
import type {
|
||||
AttendanceUserStatus,
|
||||
CourseSessionAttendanceCourseObjectType,
|
||||
} from "@/gql/graphql";
|
||||
import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations";
|
||||
import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
|
||||
import { exportAttendance } from "@/services/dashboard";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { DropdownSelectable } from "@/types";
|
||||
import { openDataAsXls } from "@/utils/export";
|
||||
import { useMutation } from "@urql/vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useMutation, useQuery } from "@urql/vue";
|
||||
import { useDateFormat } from "@vueuse/core";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted, reactive, watch } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import AttendanceCheck from "../cockpitPage/AttendanceCheck.vue";
|
||||
import AttendanceStatus from "../cockpitPage/AttendanceStatus.vue";
|
||||
|
||||
const { t } = useTranslation();
|
||||
const attendanceMutation = useMutation(ATTENDANCE_CHECK_MUTATION);
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
const userStore = useUserStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const expertCockpitStore = useExpertCockpitStore();
|
||||
|
||||
const attendanceCourses = computed(() => {
|
||||
return courseSessionDetailResult.courseSessionDetail.value?.attendance_courses ?? [];
|
||||
|
|
@ -31,46 +34,18 @@ const courseSessionDetail = computed(() => {
|
|||
return courseSessionDetailResult.courseSessionDetail.value;
|
||||
});
|
||||
|
||||
const attendanceCourseCircleId = computed(() => {
|
||||
const selectedAttendandeCourse = attendanceCourses.value.find(
|
||||
(course) => course.id === state.attendanceCourseSelected.id
|
||||
);
|
||||
return selectedAttendandeCourse?.learning_content?.circle?.id;
|
||||
});
|
||||
const currentCourse = computed(() => expertCockpitStore.currentCourse);
|
||||
|
||||
const presenceCoursesDropdownOptions = computed(() => {
|
||||
return attendanceCourses.value.map(
|
||||
(attendanceCourse) =>
|
||||
({
|
||||
id: attendanceCourse.id,
|
||||
name: `${t("a.Präsenzkurs")} ${
|
||||
attendanceCourse.learning_content.circle?.title
|
||||
} ${dayjs(attendanceCourse.due_date?.start).format("DD.MM.YYYY")}`,
|
||||
}) as DropdownSelectable
|
||||
);
|
||||
});
|
||||
const userPresence = ref(new Map<string, boolean>());
|
||||
const disclaimerConfirmed = ref(false);
|
||||
const attendanceSaved = ref(false);
|
||||
|
||||
const state = reactive({
|
||||
userPresence: new Map<string, boolean>(),
|
||||
attendanceCourseSelected: presenceCoursesDropdownOptions.value[0],
|
||||
disclaimerConfirmed: false,
|
||||
attendanceSaved: false,
|
||||
});
|
||||
|
||||
watch(
|
||||
attendanceCourses,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.length > 0) {
|
||||
state.attendanceCourseSelected = presenceCoursesDropdownOptions.value[0];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
function resetState() {
|
||||
state.userPresence = new Map<string, boolean>();
|
||||
state.disclaimerConfirmed = false;
|
||||
state.attendanceSaved = false;
|
||||
userPresence.value = new Map<string, boolean>();
|
||||
disclaimerConfirmed.value = false;
|
||||
attendanceSaved.value = false;
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
|
|
@ -78,78 +53,89 @@ const onSubmit = async () => {
|
|||
user_id: string;
|
||||
status: AttendanceUserStatus;
|
||||
};
|
||||
const attendanceUserList: UserPresence[] = Array.from(state.userPresence.keys()).map(
|
||||
const attendanceUserList: UserPresence[] = Array.from(userPresence.value.keys()).map(
|
||||
(key) => ({
|
||||
user_id: key,
|
||||
status: state.userPresence.get(key) ? "PRESENT" : "ABSENT",
|
||||
status: userPresence.value.get(key) ? "PRESENT" : "ABSENT",
|
||||
})
|
||||
);
|
||||
const res = await attendanceMutation.executeMutation({
|
||||
attendanceCourseId: state.attendanceCourseSelected.id.toString(),
|
||||
attendanceCourseId: (
|
||||
currentCourse.value as CourseSessionAttendanceCourseObjectType
|
||||
).id.toString(),
|
||||
attendanceUserList: attendanceUserList,
|
||||
});
|
||||
if (res.error) {
|
||||
log.error("Could not submit attendance check: ", res.error);
|
||||
return;
|
||||
}
|
||||
state.disclaimerConfirmed = false;
|
||||
state.attendanceSaved = true;
|
||||
disclaimerConfirmed.value = false;
|
||||
attendanceSaved.value = true;
|
||||
log.info("Attendance check submitted: ", res);
|
||||
};
|
||||
|
||||
const loadAttendanceData = async () => {
|
||||
resetState();
|
||||
// with changing variables `useQuery` does not seem to work correctly
|
||||
if (state.attendanceCourseSelected) {
|
||||
const res = await graphqlClient.query(
|
||||
ATTENDANCE_CHECK_QUERY,
|
||||
{
|
||||
courseSessionId: state.attendanceCourseSelected.id.toString(),
|
||||
if (currentCourse.value) {
|
||||
const result = await useQuery({
|
||||
query: ATTENDANCE_CHECK_QUERY,
|
||||
variables: {
|
||||
courseSessionId: currentCourse.value.id.toString(),
|
||||
},
|
||||
{
|
||||
requestPolicy: "network-only",
|
||||
}
|
||||
);
|
||||
requestPolicy: "network-only",
|
||||
});
|
||||
const attendanceUserList =
|
||||
res.data?.course_session_attendance_course?.attendance_user_list ?? [];
|
||||
result.data?.value?.course_session_attendance_course?.attendance_user_list ?? [];
|
||||
for (const user of attendanceUserList) {
|
||||
if (!user) continue;
|
||||
state.userPresence.set(user.user_id, user.status === "PRESENT");
|
||||
userPresence.value.set(user.user_id, user.status === "PRESENT");
|
||||
}
|
||||
if (attendanceUserList.length !== 0) {
|
||||
state.attendanceSaved = true;
|
||||
attendanceSaved.value = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function editAgain() {
|
||||
state.attendanceSaved = false;
|
||||
attendanceSaved.value = false;
|
||||
}
|
||||
|
||||
const toggleDisclaimer = (newValue: boolean) => {
|
||||
disclaimerConfirmed.value = newValue;
|
||||
};
|
||||
|
||||
async function exportData() {
|
||||
const data = await exportAttendance(
|
||||
{
|
||||
courseSessionIds: [Number(courseSession.value.id)],
|
||||
circleIds: [Number(attendanceCourseCircleId.value)],
|
||||
circleIds: [Number(currentCourse.value?.learning_content.circle?.id)],
|
||||
},
|
||||
userStore.language
|
||||
);
|
||||
openDataAsXls(data.encoded_data, data.file_name);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
log.debug("AttendanceCheckPage mounted");
|
||||
loadAttendanceData();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => state.attendanceCourseSelected,
|
||||
() => {
|
||||
log.debug("attendanceCourseSelected changed", state.attendanceCourseSelected);
|
||||
loadAttendanceData();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const courseDueDate = computed(() => {
|
||||
if (currentCourse.value && currentCourse.value.due_date?.start) {
|
||||
return currentCourse.value.due_date.start;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
const formattedCourseDueDate = computed(() => {
|
||||
if (courseDueDate.value) {
|
||||
return useDateFormat(courseDueDate.value, "D. MMMM YYYY", {
|
||||
locales: "de-CH",
|
||||
});
|
||||
}
|
||||
return "";
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -164,63 +150,50 @@ watch(
|
|||
<span>{{ $t("general.back") }}</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="pb-4 text-xl font-bold">{{ $t("a.Anwesenheit Präsenzkurse") }}</h3>
|
||||
<button
|
||||
v-if="state.attendanceSaved"
|
||||
class="flex"
|
||||
data-cy="export-button"
|
||||
@click="exportData"
|
||||
>
|
||||
<it-icon-export></it-icon-export>
|
||||
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<section v-if="attendanceCourses.length && state.attendanceCourseSelected">
|
||||
<div class="flex flex-row justify-between bg-white p-6">
|
||||
<ItDropdownSelect
|
||||
v-model="state.attendanceCourseSelected"
|
||||
:items="presenceCoursesDropdownOptions ?? []"
|
||||
></ItDropdownSelect>
|
||||
<div v-if="!state.attendanceSaved" class="flex flex-row items-center">
|
||||
<ItCheckbox
|
||||
:checkbox-item="{
|
||||
value: true,
|
||||
checked: state.disclaimerConfirmed,
|
||||
}"
|
||||
@toggle="state.disclaimerConfirmed = !state.disclaimerConfirmed"
|
||||
></ItCheckbox>
|
||||
<p class="w-64 pr-4 text-sm">
|
||||
{{
|
||||
$t(
|
||||
"Ich will die Anwesenheit der untenstehenden Personen definitiv bestätigen."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<button
|
||||
class="btn-primary"
|
||||
:disabled="!state.disclaimerConfirmed"
|
||||
@click="onSubmit"
|
||||
>
|
||||
{{ $t("Anwesenheit bestätigen") }}
|
||||
</button>
|
||||
<div class="flex items-center justify-between"></div>
|
||||
<section v-if="attendanceCourses.length && currentCourse">
|
||||
<div class="grid grid-cols-[2fr_1fr] justify-between gap-8 bg-white py-6">
|
||||
<div class="col-span-1 flex flex-col gap-2 px-6">
|
||||
<h3 class="pb-1 text-4xl font-bold">{{ $t("a.Präsenzkurs") }}</h3>
|
||||
<h5>
|
||||
{{ t("a.Circle") }} «{{ currentCourse?.learning_content.circle?.title }}»
|
||||
</h5>
|
||||
<h5>{{ formattedCourseDueDate }}</h5>
|
||||
</div>
|
||||
<div v-else class="self-center">
|
||||
<p class="text-base">
|
||||
{{ $t("a.Die Anwesenheit wurde definitiv bestätigt") }}
|
||||
</p>
|
||||
<button class="btn-link link" @click="editAgain()">
|
||||
{{ $t("a.Erneut bearbeiten") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-col bg-white p-6">
|
||||
<div
|
||||
v-for="(csu, index) in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
<button
|
||||
v-if="attendanceSaved"
|
||||
class="col-span-1 mr-4 hidden justify-self-end lg:flex"
|
||||
data-cy="export-button"
|
||||
@click="exportData"
|
||||
>
|
||||
<it-icon-export class="fill-current text-blue-900"></it-icon-export>
|
||||
<span class="ml inline-block text-blue-900">
|
||||
{{ $t("a.Als Excel exportieren") }}
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="col-span-2 flex flex-col items-start gap-4 px-6 lg:gap-6"
|
||||
:class="attendanceSaved ? 'lg:flex-row lg:items-center' : 'gap-8 lg:gap-8'"
|
||||
>
|
||||
<AttendanceStatus
|
||||
class="inline-flex px-6"
|
||||
:done="attendanceSaved"
|
||||
:date="courseDueDate"
|
||||
/>
|
||||
|
||||
<AttendanceCheck
|
||||
:attendance-saved="attendanceSaved"
|
||||
:disclaimer-confirmed="disclaimerConfirmed"
|
||||
@reopen="editAgain"
|
||||
@toggle="toggleDisclaimer"
|
||||
@confirm="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 border-t border-gray-500 px-6">
|
||||
<ItPersonRow
|
||||
v-for="(csu, index) in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
:class="0 === index ? 'border-none' : ''"
|
||||
|
|
@ -230,16 +203,13 @@ watch(
|
|||
>
|
||||
<template #leading>
|
||||
<ItCheckbox
|
||||
:disabled="state.attendanceSaved"
|
||||
:disabled="attendanceSaved"
|
||||
:checkbox-item="{
|
||||
value: true,
|
||||
checked: state.userPresence.get(csu.user_id) as boolean,
|
||||
checked: userPresence.get(csu.user_id) as boolean,
|
||||
}"
|
||||
@toggle="
|
||||
state.userPresence.set(
|
||||
csu.user_id,
|
||||
!state.userPresence.get(csu.user_id)
|
||||
)
|
||||
userPresence.set(csu.user_id, !userPresence.get(csu.user_id))
|
||||
"
|
||||
></ItCheckbox>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<script setup lang="ts">
|
||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||
export interface Props {
|
||||
attendanceSaved: boolean;
|
||||
disclaimerConfirmed: boolean;
|
||||
}
|
||||
defineProps<Props>();
|
||||
defineEmits(["toggle", "reopen", "confirm"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!attendanceSaved" class="flex flex-col gap-4">
|
||||
<div class="flex flex-row content-center items-center">
|
||||
<ItCheckbox
|
||||
:checkbox-item="{
|
||||
value: true,
|
||||
checked: disclaimerConfirmed,
|
||||
}"
|
||||
@toggle="$emit('toggle', !disclaimerConfirmed)"
|
||||
></ItCheckbox>
|
||||
<p class="text-sm">
|
||||
{{
|
||||
$t(
|
||||
"Ich will die Anwesenheit der untenstehenden Personen definitiv bestätigen."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn-primary w-64"
|
||||
:disabled="!disclaimerConfirmed"
|
||||
@click="$emit('confirm')"
|
||||
>
|
||||
{{ $t("Anwesenheit bestätigen") }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="flex-inline">
|
||||
<button class="btn-link link" @click="$emit('reopen')">
|
||||
{{ $t("a.Erneut bearbeiten") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<script lang="ts" setup>
|
||||
import type { CourseSessionAttendanceCourseObjectType } from "@/gql/graphql";
|
||||
import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
|
||||
import { ATTENDANCE_ROUTE } from "@/router/names";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
import { getStatus } from "@/utils/attendance";
|
||||
import { useQuery } from "@urql/vue";
|
||||
import { useDateFormat } from "@vueuse/core";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { computed } from "vue";
|
||||
import AttendanceStatus from "./AttendanceStatus.vue";
|
||||
|
||||
const attendanceRoute = {
|
||||
name: ATTENDANCE_ROUTE,
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const expertCockpitStore = useExpertCockpitStore();
|
||||
const currentCourse = computed(() => expertCockpitStore.currentCourse);
|
||||
|
||||
const shouldPause = computed(() => !currentCourse.value);
|
||||
const result = useQuery({
|
||||
query: ATTENDANCE_CHECK_QUERY,
|
||||
variables: () => ({
|
||||
courseSessionId: (
|
||||
currentCourse.value as CourseSessionAttendanceCourseObjectType
|
||||
).id.toString(),
|
||||
}),
|
||||
pause: shouldPause,
|
||||
});
|
||||
|
||||
// todo: maybe we can move these next 3 computed values somewhere else, as they are also used in the AttendanceCheckPage component
|
||||
const courseDueDate = computed(() => {
|
||||
if (currentCourse.value && currentCourse.value.due_date?.start) {
|
||||
return currentCourse.value.due_date.start;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
const attendanceSaved = computed(() => {
|
||||
const attendanceUserList =
|
||||
result.data?.value?.course_session_attendance_course?.attendance_user_list ?? [];
|
||||
return attendanceUserList.length !== 0;
|
||||
});
|
||||
|
||||
const formattedCourseDueDate = computed(() => {
|
||||
if (courseDueDate.value) {
|
||||
return useDateFormat(courseDueDate.value, "D. MMMM YYYY", {
|
||||
locales: "de-CH",
|
||||
});
|
||||
}
|
||||
return "";
|
||||
});
|
||||
const status = computed(() => {
|
||||
return getStatus(attendanceSaved.value, courseDueDate.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="my-4 flex flex-col items-start justify-between gap-4 bg-white p-6 lg:my-0 lg:flex-row lg:items-center lg:gap-0"
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-base font-bold">{{ t("a.Präsenzkurs") }}</h2>
|
||||
<p class="text-sm text-gray-800">{{ formattedCourseDueDate }}</p>
|
||||
</div>
|
||||
<AttendanceStatus :date="courseDueDate" :done="attendanceSaved" />
|
||||
<router-link
|
||||
:to="attendanceRoute"
|
||||
:class="
|
||||
status === 'now' ? 'bg-blue-900 px-4 py-2 font-bold text-white' : 'underline'
|
||||
"
|
||||
>
|
||||
<template v-if="status === 'now'">
|
||||
{{ $t("Anwesenheit prüfen") }}
|
||||
</template>
|
||||
<template v-else>{{ $t("a.Anwesenheit anschauen") }}</template>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<script setup lang="ts">
|
||||
import { howManyDaysInFuture } from "@/components/dueDates/dueDatesUtils";
|
||||
import { getStatus } from "@/utils/attendance";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
export interface Props {
|
||||
done: boolean;
|
||||
date: string;
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const status = computed(() => {
|
||||
return getStatus(props.done, props.date);
|
||||
});
|
||||
|
||||
const style = computed(() => {
|
||||
switch (status.value) {
|
||||
case "done":
|
||||
return "bg-green-200";
|
||||
case "soon":
|
||||
return "bg-gray-200";
|
||||
case "now":
|
||||
default:
|
||||
return "bg-sky-200";
|
||||
}
|
||||
});
|
||||
|
||||
const icon = computed(() => {
|
||||
switch (status.value) {
|
||||
case "done":
|
||||
return "it-icon-check";
|
||||
case "soon":
|
||||
case "now":
|
||||
default:
|
||||
return "it-icon-info";
|
||||
}
|
||||
});
|
||||
|
||||
const days = computed(() => {
|
||||
return howManyDaysInFuture(props.date);
|
||||
});
|
||||
|
||||
const text = computed(() => {
|
||||
switch (status.value) {
|
||||
case "done":
|
||||
return t("a.Du hast die Anwesenheit bestätigt.");
|
||||
case "soon":
|
||||
return t("a.Der Präsenzkurs findet in {{days}} Tagen statt.", {
|
||||
count: days.value,
|
||||
});
|
||||
case "now":
|
||||
default:
|
||||
return t("a.Überprüfe jetzt die Anwesenheit.");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="space-between inline-flex flex-row items-center gap-1 rounded py-1 pl-2 pr-4"
|
||||
:class="style"
|
||||
>
|
||||
<component :is="icon" class="h-7 w-7" />
|
||||
<p>{{ text }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
<script setup lang="ts">
|
||||
import SubmissionsOverview from "@/components/cockpit/SubmissionsOverview.vue";
|
||||
import UserStatusCount from "@/components/cockpit/UserStatusCount.vue";
|
||||
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
||||
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
import AttendanceOverview from "./AttendanceOverview.vue";
|
||||
|
||||
const expertCockpitStore = useExpertCockpitStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="expertCockpitStore.circles?.length">
|
||||
<div v-if="expertCockpitStore.currentCircle" class="container-large pt-10">
|
||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<ItDropdownSelect
|
||||
:as-heading="true"
|
||||
:model-value="expertCockpitStore.currentCircle"
|
||||
type-name="Circle:"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-auto"
|
||||
:items="expertCockpitStore.circles"
|
||||
@update:model-value="expertCockpitStore.setCurrentCourseCircleFromEvent"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<!-- Status -->
|
||||
<div class="mb-4 gap-4">
|
||||
<AttendanceOverview />
|
||||
</div>
|
||||
|
||||
<div class="mb-4 bg-white p-6">
|
||||
<CourseSessionDueDatesList
|
||||
:course-session-id="courseSession.id"
|
||||
:circle-id="expertCockpitStore.currentCircle.id"
|
||||
:max-count="4"
|
||||
></CourseSessionDueDatesList>
|
||||
</div>
|
||||
<SubmissionsOverview
|
||||
:course-session="courseSession"
|
||||
:selected-circle="expertCockpitStore.currentCircle.id"
|
||||
></SubmissionsOverview>
|
||||
<div class="pt-4">
|
||||
<!-- progress -->
|
||||
<div
|
||||
v-if="courseSessionDetailResult.filterMembers().length > 0"
|
||||
class="bg-white p-6"
|
||||
>
|
||||
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
||||
<ul>
|
||||
<ItPersonRow
|
||||
v-for="csu in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
>
|
||||
<template #center>
|
||||
<div
|
||||
class="mt-2 flex w-full flex-col items-center justify-start lg:mt-0 lg:flex-row"
|
||||
>
|
||||
<LearningPathDiagram
|
||||
:course-session-id="courseSession.id"
|
||||
:course-slug="props.courseSlug"
|
||||
:user-id="csu.user_id"
|
||||
:show-circle-slugs="[expertCockpitStore.currentCircle.slug]"
|
||||
diagram-type="singleSmall"
|
||||
class="mr-4"
|
||||
></LearningPathDiagram>
|
||||
<p class="lg:min-w-[150px]">
|
||||
{{ expertCockpitStore.currentCircle.title }}
|
||||
</p>
|
||||
<UserStatusCount
|
||||
:course-slug="props.courseSlug"
|
||||
:user-id="csu.user_id"
|
||||
></UserStatusCount>
|
||||
</div>
|
||||
</template>
|
||||
<template #link>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'profileLearningPath',
|
||||
params: { userId: csu.user_id, courseSlug: props.courseSlug },
|
||||
}"
|
||||
class="link w-full lg:text-right"
|
||||
>
|
||||
{{ $t("general.profileLink") }}
|
||||
</router-link>
|
||||
</template>
|
||||
</ItPersonRow>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container-large mt-4">
|
||||
<!-- No circle selected -->
|
||||
<span class="text-lg text-orange-600">
|
||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container-large mt-4">
|
||||
<span class="text-lg text-orange-600">
|
||||
<!-- No circle at all (should never happen, mostly
|
||||
for us to reduce confusion why the cockpit is just empty...) -->
|
||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||
|
||||
import SubmissionsOverview from "@/components/cockpit/SubmissionsOverview.vue";
|
||||
import UserStatusCount from "@/components/cockpit/UserStatusCount.vue";
|
||||
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
import log from "loglevel";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
log.debug("CockpitIndexPage created", props.courseSlug);
|
||||
|
||||
const { loading } = useExpertCockpitPageData(props.courseSlug);
|
||||
|
||||
const expertCockpitStore = useExpertCockpitStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!loading" class="bg-gray-200">
|
||||
<div v-if="expertCockpitStore.circles?.length">
|
||||
<div v-if="expertCockpitStore.currentCircle" class="container-large">
|
||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h1>Cockpit</h1>
|
||||
<ItDropdownSelect
|
||||
:model-value="expertCockpitStore.currentCircle"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
:items="expertCockpitStore.circles"
|
||||
@update:model-value="expertCockpitStore.setCurrentCourseCircleFromEvent"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<!-- Status -->
|
||||
<div class="mb-4 gap-4 lg:grid lg:grid-cols-3 lg:grid-rows-none">
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("Trainerunterlagen") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{ $t("cockpit.trainerFilesText") }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://vbvbern.sharepoint.com/sites/myVBV-AFA_K-CI"
|
||||
class="btn-secondary min-w-min"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t("MS Teams öffnen") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="courseSession.course.configuration.enable_circle_documents"
|
||||
class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0"
|
||||
data-cy="circle-documents"
|
||||
>
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("a.Unterlagen für Teilnehmenden") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{ $t("a.Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.") }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/documents`"
|
||||
class="btn-secondary min-w-min"
|
||||
>
|
||||
{{ $t("a.Zum Unterlagen-Upload") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("a.Anwesenheitskontrolle Präsenzkurse") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{
|
||||
$t(
|
||||
"Hier überprüfst und bestätigst du die Anwesenheit deiner Teilnehmenden."
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/attendance`"
|
||||
class="btn-secondary min-w-min"
|
||||
>
|
||||
{{ $t("Anwesenheit prüfen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 bg-white p-6">
|
||||
<CourseSessionDueDatesList
|
||||
:course-session-id="courseSession.id"
|
||||
:circle-id="expertCockpitStore.currentCircle.id"
|
||||
:max-count="4"
|
||||
></CourseSessionDueDatesList>
|
||||
</div>
|
||||
<SubmissionsOverview
|
||||
:course-session="courseSession"
|
||||
:selected-circle="expertCockpitStore.currentCircle.id"
|
||||
></SubmissionsOverview>
|
||||
<div class="pt-4">
|
||||
<!-- progress -->
|
||||
<div
|
||||
v-if="courseSessionDetailResult.filterMembers().length > 0"
|
||||
class="bg-white p-6"
|
||||
>
|
||||
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
||||
<ul>
|
||||
<ItPersonRow
|
||||
v-for="csu in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
>
|
||||
<template #center>
|
||||
<div
|
||||
class="mt-2 flex w-full flex-col items-center justify-start lg:mt-0 lg:flex-row"
|
||||
>
|
||||
<LearningPathDiagram
|
||||
:course-session-id="courseSession.id"
|
||||
:course-slug="props.courseSlug"
|
||||
:user-id="csu.user_id"
|
||||
:show-circle-slugs="[expertCockpitStore.currentCircle.slug]"
|
||||
diagram-type="singleSmall"
|
||||
class="mr-4"
|
||||
></LearningPathDiagram>
|
||||
<p class="lg:min-w-[150px]">
|
||||
{{ expertCockpitStore.currentCircle.title }}
|
||||
</p>
|
||||
<UserStatusCount
|
||||
:course-slug="props.courseSlug"
|
||||
:user-id="csu.user_id"
|
||||
></UserStatusCount>
|
||||
</div>
|
||||
</template>
|
||||
<template #link>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'profileLearningPath',
|
||||
params: { userId: csu.user_id, courseSlug: props.courseSlug },
|
||||
}"
|
||||
class="link w-full lg:text-right"
|
||||
>
|
||||
{{ $t("general.profileLink") }}
|
||||
</router-link>
|
||||
</template>
|
||||
</ItPersonRow>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container-large mt-4">
|
||||
<!-- No circle selected -->
|
||||
<span class="text-lg text-orange-600">
|
||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container-large mt-4">
|
||||
<span class="text-lg text-orange-600">
|
||||
<!-- No circle at all (should never happen, mostly
|
||||
for us to reduce confusion why the cockpit is just empty...) -->
|
||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<script setup lang="ts">
|
||||
import SubNavigation from "@/components/header/SubNavigation.vue";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||
import { COCKPIT_ROUTE, DOCUMENTS_ROUTE } from "@/router/names";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
log.debug("CockpitIndexPage created", props.courseSlug);
|
||||
|
||||
const { loading } = useExpertCockpitPageData(props.courseSlug);
|
||||
|
||||
const defaultRoute = {
|
||||
name: COCKPIT_ROUTE,
|
||||
};
|
||||
// const attendanceRoute = {
|
||||
// name: ATTENDANCE_ROUTE,
|
||||
// };
|
||||
const documentsRoute = {
|
||||
name: DOCUMENTS_ROUTE,
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const enableDocuments = computed(() => {
|
||||
return !!courseSession.value.course.configuration.enable_circle_documents;
|
||||
});
|
||||
|
||||
const items = computed(() => [
|
||||
{ id: 1, name: t("a.Übersicht"), route: defaultRoute },
|
||||
// { id: 2, name: t("a.Teilnehmer"), route: attendanceRoute }, // todo: re-enable with correct route in a later issue
|
||||
...(enableDocuments.value
|
||||
? [
|
||||
{
|
||||
id: 3,
|
||||
name: t("a.Unterlagen"),
|
||||
route: documentsRoute,
|
||||
dataCy: "circle-documents",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
id: 4,
|
||||
name: "Vorschau Teilnehmer",
|
||||
route: "https://iterativ.ch",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "MS Teams",
|
||||
route: "https://vbvbern.sharepoint.com/sites/myVBV-AFA_K-CI",
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!loading" class="bg-gray-200">
|
||||
<SubNavigation :items="items" />
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -136,21 +136,13 @@ async function uploadDocument(data: DocumentUploadData) {
|
|||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<div v-if="courseSession" class="container-large">
|
||||
<nav class="py-4 pb-4">
|
||||
<router-link
|
||||
class="btn-text inline-flex items-center pl-0"
|
||||
:to="`/course/${courseSession.course.slug}/cockpit`"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>{{ t("general.back") }}</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
<main>
|
||||
<main class="py-4">
|
||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h2>{{ t("a.Unterlagen für Teilnehmenden") }}</h2>
|
||||
<ItDropdownSelect
|
||||
:model-value="cockpitStore.currentCircle"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-auto"
|
||||
:as-heading="true"
|
||||
type-name="Circle:"
|
||||
:items="cockpitStore.circles"
|
||||
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
||||
></ItDropdownSelect>
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import {
|
|||
competenceCertificateProgressStatusCount,
|
||||
} from "@/pages/competence/utils";
|
||||
import type { CompetenceCertificate } from "@/types";
|
||||
import * as log from "loglevel";
|
||||
import log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
import CompetenceCertificateGrade from "./CompetenceCertificateGrade.vue";
|
||||
|
||||
log.debug("CompetenceCertificateComponent setup");
|
||||
|
||||
|
|
@ -85,34 +86,10 @@ const showCourseSession = computed(() => {
|
|||
</h3>
|
||||
</div>
|
||||
|
||||
<section v-if="userPointsEvaluatedAssignments > 0">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="py-4"
|
||||
:class="{ 'heading-1': props.detailView, 'heading-2': !props.detailView }"
|
||||
:data-cy="`certificate-${competenceCertificate.slug}-grade`"
|
||||
>
|
||||
{{ $t("a.Note") }}: {{ userGrade }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-gray-900"
|
||||
:data-cy="`certificate-${competenceCertificate.slug}-grade-percent`"
|
||||
>
|
||||
{{ $t("a.Ungerundete Note") }}: {{ userGradeRounded2Places }}.
|
||||
<a
|
||||
:href="$t('a.wegleitungUkUrl')"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="underline"
|
||||
>
|
||||
{{ $t("a.Wegleitung üK") }}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<section v-else class="py-2">
|
||||
{{ $t("a.competenceCertificateNoUserPoints") }}
|
||||
</section>
|
||||
<CompetenceCertificateGrade
|
||||
:detail-view="detailView"
|
||||
:competence-certificate="competenceCertificate"
|
||||
/>
|
||||
|
||||
<ItProgress :status-count="progressStatusCount" />
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<script setup lang="ts">
|
||||
import type { CompetenceCertificate } from "@/types";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { computed } from "vue";
|
||||
import { assignmentsUserPoints, calcCompetenceCertificateGrade } from "./utils";
|
||||
|
||||
export interface Props {
|
||||
detailView: boolean;
|
||||
competenceCertificate: CompetenceCertificate;
|
||||
compactView?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
compactView: false,
|
||||
});
|
||||
|
||||
const userGrade = computed(() => {
|
||||
return calcCompetenceCertificateGrade(props.competenceCertificate.assignments, true);
|
||||
});
|
||||
const userPointsEvaluatedAssignments = computed(() => {
|
||||
return assignmentsUserPoints(props.competenceCertificate.assignments);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section v-if="userPointsEvaluatedAssignments > 0">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="py-4"
|
||||
:class="detailView ? 'heading-1' : compactView ? 'heading-3' : 'heading-2'"
|
||||
:data-cy="`certificate-${competenceCertificate.slug}-grade`"
|
||||
>
|
||||
{{ label ?? $t("a.Note") }}: {{ userGrade }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section v-else class="py-2">
|
||||
{{ $t("a.competenceCertificateNoUserPoints") }}
|
||||
</section>
|
||||
</template>
|
||||
|
|
@ -22,7 +22,8 @@ const { id: currentUserId } = useUserStore();
|
|||
|
||||
const { competenceCertificates } = useAllCompetenceCertificates(
|
||||
props.userId ?? currentUserId,
|
||||
props.courseSlug
|
||||
props.courseSlug,
|
||||
true
|
||||
);
|
||||
|
||||
const assignments = computed(() => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<script setup lang="ts">
|
||||
import { useAllCompetenceCertificates } from "@/composables";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import CompetenceCertificateComponent from "./CompetenceCertificateComponent.vue";
|
||||
import CompetenceCertificateGrade from "./CompetenceCertificateGrade.vue";
|
||||
|
||||
export interface Props {
|
||||
courseSlug: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { id: currentUserId } = useUserStore();
|
||||
const { competenceCertificates } = useAllCompetenceCertificates(
|
||||
currentUserId,
|
||||
props.courseSlug
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<div class="container-large">
|
||||
<h1>Certificate Overview</h1>
|
||||
<div v-for="certificate in competenceCertificates" :key="certificate.id">
|
||||
<CompetenceCertificateGrade
|
||||
:label="$t('a.Note')"
|
||||
:compact-view="true"
|
||||
:detail-view="false"
|
||||
:competence-certificate="certificate"
|
||||
/>
|
||||
</div>
|
||||
<CompetenceCertificateComponent
|
||||
v-for="certificate in competenceCertificates"
|
||||
:key="certificate.id"
|
||||
:detail-view="false"
|
||||
:competence-certificate="certificate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,101 +1,67 @@
|
|||
<script setup lang="ts">
|
||||
import SubNavigation, { type SubNavEntry } from "@/components/header/SubNavigation.vue";
|
||||
import { useCurrentCourseSession, useEvaluationWithFeedback } from "@/composables";
|
||||
import {
|
||||
CERTIFICATES_ROUTE,
|
||||
COMPETENCE_ROUTE,
|
||||
COMPETENCES_ROUTE,
|
||||
SELF_EVALUATION_ROUTE,
|
||||
} from "@/router/names";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import * as log from "loglevel";
|
||||
import { onMounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
log.debug("CompetenceParentPage created");
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
function routeInOverview() {
|
||||
return route.path.endsWith("/competence");
|
||||
}
|
||||
|
||||
function routeInCompetenceCertificate() {
|
||||
return route.path.includes("/certificate");
|
||||
}
|
||||
|
||||
function routeInActionCompetences() {
|
||||
return route.path.endsWith("/competences");
|
||||
}
|
||||
|
||||
function routeInSelfEvaluationAndFeedback() {
|
||||
return route.path.endsWith("/self-evaluation-and-feedback");
|
||||
}
|
||||
const { t } = useTranslation();
|
||||
|
||||
const currentCourseSession = useCurrentCourseSession();
|
||||
const hasEvaluationFeedback = useEvaluationWithFeedback().hasFeedback;
|
||||
|
||||
onMounted(async () => {
|
||||
log.debug("CompetenceParentPage mounted", props.courseSlug);
|
||||
log.debug("CompetenceParentPage mounted");
|
||||
});
|
||||
|
||||
const competenceRoute = {
|
||||
name: COMPETENCE_ROUTE,
|
||||
};
|
||||
const certificatesRoute = {
|
||||
name: CERTIFICATES_ROUTE,
|
||||
};
|
||||
const selfEvaluationRoute = {
|
||||
name: SELF_EVALUATION_ROUTE,
|
||||
};
|
||||
const competencesRoute = {
|
||||
name: COMPETENCES_ROUTE,
|
||||
};
|
||||
|
||||
// todo: replace this menu with a real one before going live
|
||||
const items: SubNavEntry[] = [
|
||||
{ id: 0, name: t("a.Übersicht"), route: competenceRoute },
|
||||
...(currentCourseSession.value.course.configuration.enable_competence_certificates
|
||||
? [
|
||||
{
|
||||
id: 1,
|
||||
name: t("a.Kompetenznachweise"),
|
||||
route: certificatesRoute,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: 2,
|
||||
name: hasEvaluationFeedback.value
|
||||
? t("a.Selbst- und Fremdeinschätzungen")
|
||||
: t("a.Selbsteinschätzungen"),
|
||||
dataCy: "self-evaluation-and-feedback-navigation-link",
|
||||
route: selfEvaluationRoute,
|
||||
},
|
||||
{ id: 3, name: t("a.Handlungskompetenzen"), route: competencesRoute },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<nav class="border-b bg-white px-4 lg:px-8">
|
||||
<ul class="flex flex-col lg:flex-row">
|
||||
<li
|
||||
class="border-t-2 border-t-transparent"
|
||||
:class="{ 'border-b-2 border-b-blue-900': routeInOverview() }"
|
||||
>
|
||||
<router-link :to="`/course/${courseSlug}/competence`" class="block py-3">
|
||||
{{ $t("a.Übersicht") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li
|
||||
v-if="
|
||||
currentCourseSession.course.configuration.enable_competence_certificates
|
||||
"
|
||||
class="border-t-2 border-t-transparent lg:ml-12"
|
||||
:class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }"
|
||||
>
|
||||
<router-link
|
||||
:to="`/course/${courseSlug}/competence/certificates`"
|
||||
class="block py-3"
|
||||
>
|
||||
{{ $t("a.Kompetenznachweise") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li
|
||||
class="border-t-2 border-t-transparent lg:ml-12"
|
||||
:class="{
|
||||
'border-b-2 border-b-blue-900': routeInSelfEvaluationAndFeedback(),
|
||||
}"
|
||||
>
|
||||
<router-link
|
||||
:to="`/course/${courseSlug}/competence/self-evaluation-and-feedback`"
|
||||
class="block py-3"
|
||||
data-cy="self-evaluation-and-feedback-navigation-link"
|
||||
>
|
||||
{{
|
||||
hasEvaluationFeedback
|
||||
? $t("a.Selbst- und Fremdeinschätzungen")
|
||||
: $t("a.Selbsteinschätzungen")
|
||||
}}
|
||||
</router-link>
|
||||
</li>
|
||||
<li
|
||||
class="border-t-2 border-t-transparent lg:ml-12"
|
||||
:class="{ 'border-b-2 border-b-blue-900': routeInActionCompetences() }"
|
||||
>
|
||||
<router-link
|
||||
:to="`/course/${courseSlug}/competence/competences`"
|
||||
class="block py-3"
|
||||
>
|
||||
{{ $t("a.Handlungskompetenzen") }}
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
<!-- Add similar logic for other `li` items as you expand the list -->
|
||||
<li class="ml-6 inline-block lg:ml-12"></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<SubNavigation :items="items" />
|
||||
<main>
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -23,14 +23,21 @@ export function assignmentsUserPoints(assignments: CompetenceCertificateAssignme
|
|||
).toFixed(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the grade by summing up all the weighted percentage of points of each assignment.
|
||||
*
|
||||
* @param assignments - list of assignments
|
||||
* @param roundedToHalfGrade - should the grade be rounded?
|
||||
*/
|
||||
export function calcCompetenceCertificateGrade(
|
||||
assignments: CompetenceCertificateAssignment[],
|
||||
roundedToHalfGrade = true
|
||||
roundedToHalfGrade: boolean = true
|
||||
) {
|
||||
const evaluatedAssignments = assignments.filter(
|
||||
(a) => a.completions?.[0]?.completion_status === "EVALUATION_SUBMITTED"
|
||||
);
|
||||
|
||||
// sum((points_x / max_points) * weight_x)
|
||||
const adjustedResults = evaluatedAssignments.map((a) => {
|
||||
return (
|
||||
((a.completions?.[0]?.evaluation_points_final ?? 0) / a.max_points) *
|
||||
|
|
@ -38,6 +45,7 @@ export function calcCompetenceCertificateGrade(
|
|||
);
|
||||
});
|
||||
|
||||
// count only assignments with weight
|
||||
const adjustedAssignmentCount = _.sum(
|
||||
evaluatedAssignments.map((a) => a.competence_certificate_weight)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -63,9 +63,14 @@ const itemDetailUrl = (item: AssignmentStatisticsRecordType) => {
|
|||
|
||||
<template>
|
||||
<main>
|
||||
<div class="mb-10 flex items-center justify-between">
|
||||
<div class="mb-10 flex flex-wrap items-center justify-between">
|
||||
<h3>{{ $t("a.Kompetenznachweis-Elemente") }}</h3>
|
||||
<button v-if="true" class="flex" data-cy="export-button" @click="exportData">
|
||||
<button
|
||||
v-if="true"
|
||||
class="flex pt-3 sm:pt-0"
|
||||
data-cy="export-button"
|
||||
@click="exportData"
|
||||
>
|
||||
<it-icon-export></it-icon-export>
|
||||
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
|
||||
</button>
|
||||
|
|
@ -78,7 +83,7 @@ const itemDetailUrl = (item: AssignmentStatisticsRecordType) => {
|
|||
>
|
||||
<template #default="{ item }">
|
||||
<div
|
||||
class="flex justify-between"
|
||||
class="flex flex-wrap justify-between"
|
||||
:data-cy="`${(item as AssignmentStatisticsRecordType).assignment_title}@${(item as AssignmentStatisticsRecordType).course_session_id}`"
|
||||
>
|
||||
<div>
|
||||
|
|
@ -109,7 +114,7 @@ const itemDetailUrl = (item: AssignmentStatisticsRecordType) => {
|
|||
{{ total((item as AssignmentStatisticsRecordType).metrics) }}
|
||||
bestanden
|
||||
</div>
|
||||
<div v-else>Noch nicht bestätigt</div>
|
||||
<div v-else>{{ $t("a.Noch nicht bestätigt") }}</div>
|
||||
<ItProgress
|
||||
:status-count="
|
||||
assignmentStats((item as AssignmentStatisticsRecordType).metrics)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ async function exportData() {
|
|||
:items="courseStatistics.attendance_day_presences.records"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex flex-wrap justify-between">
|
||||
<div>
|
||||
<h4 class="font-bold">
|
||||
{{ $t("a.Präsenztag") }}: Circle «{{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const props = defineProps<{
|
|||
:items="courseStatistics.competences.records"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex flex-wrap justify-between space-y-2 md:space-y-0">
|
||||
<div>
|
||||
<h4 class="font-bold">
|
||||
{{ $t("a.Selbsteinschätzung") }}:
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ async function exportData() {
|
|||
:items="courseStatistics.feedback_responses.records"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex flex-wrap justify-between space-y-2 md:space-y-0">
|
||||
<div>
|
||||
<h4 class="font-bold">
|
||||
Feedback: Circle «{{ circleMeta(item.circle_id)?.name }}»
|
||||
|
|
|
|||
|
|
@ -242,7 +242,13 @@ watch(
|
|||
</button>
|
||||
</div>
|
||||
<DocumentSection v-if="showDocumentSection" :circle="circle" />
|
||||
<div v-if="!props.readonly" class="expert mt-8 border p-6">
|
||||
<div
|
||||
v-if="
|
||||
!props.readonly &&
|
||||
lpQueryResult.course.value?.configuration.enable_learning_mentor
|
||||
"
|
||||
class="expert mt-8 border p-6"
|
||||
>
|
||||
<h3 class="text-blue-dark">{{ $t("circlePage.gotQuestions") }}</h3>
|
||||
<div
|
||||
class="mt-4 leading-relaxed"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import MediaLibraryBlock from "./blocks/MediaLibraryBlock.vue";
|
|||
import PlaceholderBlock from "./blocks/PlaceholderBlock.vue";
|
||||
import RichTextBlock from "./blocks/RichTextBlock.vue";
|
||||
import VideoBlock from "./blocks/VideoBlock.vue";
|
||||
import FeedbackBlockAutomobilGewerbe from "./feedback/FeedbackBlockAutomobilGewerbe.vue";
|
||||
import FeedbackBlockUK from "./feedback/FeedbackBlockUK.vue";
|
||||
import FeedbackBlockVV from "./feedback/FeedbackBlockVV.vue";
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ const COMPONENTS: Record<LearningContentContentType, Component> = {
|
|||
"learnpath.LearningContentDocumentList": DocumentListBlock,
|
||||
"learnpath.LearningContentFeedbackUK": FeedbackBlockUK,
|
||||
"learnpath.LearningContentFeedbackVV": FeedbackBlockVV,
|
||||
"learnpath.LearningContentFeedbackAutomobilGewerbe": FeedbackBlockAutomobilGewerbe,
|
||||
"learnpath.LearningContentLearningModule": IframeBlock,
|
||||
"learnpath.LearningContentKnowledgeAssessment": IframeBlock,
|
||||
"learnpath.LearningContentMediaLibrary": MediaLibraryBlock,
|
||||
|
|
@ -58,6 +60,7 @@ const DEFAULT_BLOCK = PlaceholderBlock;
|
|||
const component = computed(() => {
|
||||
return COMPONENTS[props.learningContent.content_type] || DEFAULT_BLOCK;
|
||||
});
|
||||
console.log("component", component);
|
||||
|
||||
function handleFinishedLearningContent() {
|
||||
circleStore.continueFromLearningContent(
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ import { graphql } from "@/gql";
|
|||
import FeedbackCompletition from "@/pages/learningPath/learningContentPage/feedback/FeedbackCompletition.vue";
|
||||
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
|
||||
import type { LearningContentFeedback } from "@/types";
|
||||
import { useMutation } from "@urql/vue";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
|
||||
content: LearningContentFeedback;
|
||||
stepLabels: string[];
|
||||
questionData: any[];
|
||||
introduction: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<script setup lang="ts">
|
||||
import ItRadioGroup from "@/components/ui/ItRadioGroup.vue";
|
||||
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||
import {
|
||||
PERCENTAGES,
|
||||
RATINGS,
|
||||
YES_NO,
|
||||
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
||||
import FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
|
||||
import type { LearningContentFeedback } from "@/types";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
|
||||
const props = defineProps<{
|
||||
content: LearningContentFeedback;
|
||||
}>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const stepLabels = [
|
||||
t("general.introduction"),
|
||||
t("feedback.satisfactionLabel"),
|
||||
t("feedback.goalAttainmentLabel"),
|
||||
t("feedback.proficiencyLabelVV"),
|
||||
t("feedback.recommendLabelVV"),
|
||||
t("feedback.coursePositiveFeedbackLabel"),
|
||||
t("feedback.courseNegativeFeedbackLabel"),
|
||||
t("general.submission"),
|
||||
];
|
||||
|
||||
const questionData = [
|
||||
{
|
||||
modelKey: "satisfaction",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "goal_attainment",
|
||||
items: RATINGS,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "proficiency",
|
||||
items: PERCENTAGES,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "would_recommend",
|
||||
items: YES_NO,
|
||||
component: ItRadioGroup,
|
||||
},
|
||||
{
|
||||
modelKey: "course_positive_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
{
|
||||
modelKey: "course_negative_feedback",
|
||||
component: ItTextarea,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FeedbackBase
|
||||
:step-labels="stepLabels"
|
||||
:question-data="questionData"
|
||||
:content="props.content"
|
||||
:introduction="$t('a.feedback.introductionVV')"
|
||||
:title="$t('Feedback')"
|
||||
:completion-title="$t('feedback.sendFeedback')"
|
||||
:completion-description="$t('feedback.completionDescriptionVV')"
|
||||
:show-avatar="false"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -8,12 +8,12 @@ import {
|
|||
YES_NO,
|
||||
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
||||
import FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
|
||||
import type { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
|
||||
import type { LearningContentFeedback } from "@/types";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
|
||||
content: LearningContentFeedback;
|
||||
}>();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import {
|
|||
YES_NO,
|
||||
} from "@/pages/learningPath/learningContentPage/feedback/feedback.constants";
|
||||
import FeedbackBase from "@/pages/learningPath/learningContentPage/feedback/FeedbackBase.vue";
|
||||
import type { LearningContentFeedbackUK, LearningContentFeedbackVV } from "@/types";
|
||||
import type { LearningContentFeedback } from "@/types";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
|
||||
const props = defineProps<{
|
||||
content: LearningContentFeedbackVV | LearningContentFeedbackUK;
|
||||
content: LearningContentFeedback;
|
||||
}>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ function render() {
|
|||
<div class="aspect-square content-center">
|
||||
<pre hidden>{{ pieData }}</pre>
|
||||
<pre hidden>{{ render() }}</pre>
|
||||
<svg :id="svgId" class="h-full min-w-[20px]">
|
||||
<svg :id="svgId" class="h-full">
|
||||
<circle :cx="width / 2" :cy="height / 2" :r="radius" :color="colors.gray[300]" />
|
||||
<circle :cx="width / 2" :cy="height / 2" :r="radius / 2.5" color="white" />
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { useEntities } from "@/services/entities";
|
|||
import { getLocalSessionKey } from "@/statistics";
|
||||
import { type User, useUserStore } from "@/stores/user";
|
||||
import { normalizeSwissPhoneNumber, validatePhoneNumber } from "@/utils/phone";
|
||||
import { validatePostalCode } from "@/utils/postalcode";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import log from "loglevel";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
|
@ -129,6 +130,8 @@ function validateAddress() {
|
|||
|
||||
if (!address.value.postal_code) {
|
||||
formErrors.value.personal.push(t("a.PLZ"));
|
||||
} else if (!validatePostalCode(address.value.postal_code)) {
|
||||
formErrors.value.personal.push(t("a.Postleizahl hat das falsche Format"));
|
||||
}
|
||||
|
||||
if (!address.value.city) {
|
||||
|
|
@ -172,6 +175,8 @@ function validateAddress() {
|
|||
|
||||
if (!address.value.organisation_postal_code) {
|
||||
formErrors.value.company.push(t("a.PLZ"));
|
||||
} else if (!validatePostalCode(address.value.organisation_postal_code)) {
|
||||
formErrors.value.personal.push(t("a.Postleizahl hat das falsche Format"));
|
||||
}
|
||||
|
||||
if (!address.value.organisation_city) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,18 @@ import {
|
|||
import { addToHistory, setLastNavigationWasPush } from "@/router/history";
|
||||
import { onboardingRedirect } from "@/router/onboarding";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import {
|
||||
ATTENDANCE_ROUTE,
|
||||
CERTIFICATE_OVERVIEW_ROUTE,
|
||||
CERTIFICATES_ROUTE,
|
||||
COCKPIT_ROUTE,
|
||||
COMPETENCE_ROUTE,
|
||||
COMPETENCES_ROUTE,
|
||||
DOCUMENTS_ROUTE,
|
||||
PERSONAL_PROFILE_ROUTE,
|
||||
SELF_EVALUATION_ROUTE,
|
||||
SETTINGS_ROUTE,
|
||||
} from "./names";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
|
@ -79,85 +91,284 @@ const router = createRouter({
|
|||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/media",
|
||||
path: "/dashboard/certificates/:courseSlug",
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateOverviewPage.vue"),
|
||||
props: true,
|
||||
component: () => import("@/pages/mediaLibrary/MediaLibraryParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/pages/mediaLibrary/MediaLibraryIndexPage.vue"),
|
||||
},
|
||||
{
|
||||
path: ":categorySlug",
|
||||
props: true,
|
||||
component: () => import("@/pages/mediaLibrary/MediaLibraryCategoryPage.vue"),
|
||||
},
|
||||
{
|
||||
path: ":categorySlug/:contentSlug",
|
||||
props: true,
|
||||
component: () => import("@/pages/mediaLibrary/MediaLibraryContentPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/competence",
|
||||
path: "/course/:courseSlug",
|
||||
props: true,
|
||||
component: () => import("@/pages/competence/CompetenceParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
path: "media",
|
||||
component: () => import("@/pages/mediaLibrary/MediaLibraryParentPage.vue"),
|
||||
props: true,
|
||||
component: () => import("@/pages/competence/CompetenceIndexPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/pages/mediaLibrary/MediaLibraryIndexPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: ":categorySlug",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/mediaLibrary/MediaLibraryCategoryPage.vue"),
|
||||
},
|
||||
{
|
||||
path: ":categorySlug/:contentSlug",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/mediaLibrary/MediaLibraryContentPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: "competence",
|
||||
component: () => import("@/pages/competence/CompetenceParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
props: true,
|
||||
name: COMPETENCE_ROUTE,
|
||||
component: () => import("@/pages/competence/CompetenceIndexPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "certificates",
|
||||
name: CERTIFICATES_ROUTE,
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateListPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "certificates/:certificateSlug",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||
},
|
||||
{
|
||||
name: SELF_EVALUATION_ROUTE,
|
||||
path: "self-evaluation-and-feedback",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/SelfEvaluationAndFeedbackPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "competences",
|
||||
name: COMPETENCES_ROUTE,
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/ActionCompetenceListPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "learn",
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("../pages/learningPath/learningPathPage/LearningPathPage.vue"),
|
||||
},
|
||||
{
|
||||
path: ":circleSlug",
|
||||
component: () =>
|
||||
import("../pages/learningPath/circlePage/CirclePage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: ":circleSlug/evaluate/:learningUnitSlug",
|
||||
component: () =>
|
||||
import(
|
||||
"../pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue"
|
||||
),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: ":circleSlug/:contentSlug",
|
||||
component: () =>
|
||||
import(
|
||||
"../pages/learningPath/learningContentPage/LearningContentPage.vue"
|
||||
),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: "certificates",
|
||||
name: CERTIFICATE_OVERVIEW_ROUTE,
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateListPage.vue"),
|
||||
import("@/pages/competence/CompetenceCertificateOverviewPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "certificates/:certificateSlug",
|
||||
path: "profile/:userId",
|
||||
component: () => import("@/pages/userProfile/UserProfilePage.vue"),
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: "learning-path",
|
||||
component: () =>
|
||||
import("@/pages/userProfile/LearningPathProfilePage.vue"),
|
||||
props: true,
|
||||
name: "profileLearningPath",
|
||||
meta: {
|
||||
hideChrome: true,
|
||||
showCloseButton: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "competence",
|
||||
component: () => import("@/pages/userProfile/CompetenceProfilePage.vue"),
|
||||
props: true,
|
||||
name: "profileCompetence",
|
||||
meta: {
|
||||
hideChrome: true,
|
||||
showCloseButton: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "competenceMain",
|
||||
component: () =>
|
||||
import(
|
||||
"@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "evaluations",
|
||||
name: "competenceEvaluations",
|
||||
component: () =>
|
||||
import(
|
||||
"@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackList.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "certificates/:certificateSlug",
|
||||
name: "competenceCertificateDetail",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "certificates",
|
||||
name: "competenceCertificates",
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateListPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "learning-mentor",
|
||||
component: () => import("@/pages/learningMentor/mentor/MentorIndexPage.vue"),
|
||||
name: "learningMentor",
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorParticipantsPage.vue"),
|
||||
name: "mentorsAndParticipants",
|
||||
},
|
||||
{
|
||||
path: "tasks",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorOverviewPage.vue"),
|
||||
name: "learningMentorOverview",
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback/:learningUnitId",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/SelfEvaluationFeedbackPage.vue"),
|
||||
name: "mentorSelfEvaluationFeedback",
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "details",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorDetailParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "praxis-assignments/:praxisAssignmentId",
|
||||
component: () =>
|
||||
import(
|
||||
"@/pages/learningMentor/mentor/MentorPraxisAssignmentPage.vue"
|
||||
),
|
||||
name: "learningMentorPraxisAssignments",
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback-assignments/:learningUnitId",
|
||||
component: () =>
|
||||
import(
|
||||
"@/pages/learningMentor/mentor/MentorSelfEvaluationFeedbackAssignmentPage.vue"
|
||||
),
|
||||
name: "learningMentorSelfEvaluationFeedbackAssignments",
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "assignment-evaluation/:assignmentId/:userId",
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||
import("@/pages/assignmentEvaluation/AssignmentEvaluationPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
name: "selfEvaluationAndFeedback",
|
||||
path: "self-evaluation-and-feedback",
|
||||
props: true,
|
||||
path: "cockpit",
|
||||
name: "cockpit",
|
||||
component: () =>
|
||||
import("@/pages/competence/SelfEvaluationAndFeedbackPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "competences",
|
||||
import("@/pages/cockpit/cockpitPage/CockpitExpertParentPage.vue"),
|
||||
props: true,
|
||||
component: () => import("@/pages/competence/ActionCompetenceListPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/cockpitPage/CockpitExpertHomePage.vue"),
|
||||
name: COCKPIT_ROUTE,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "profile/:userId/:circleSlug",
|
||||
component: () => import("@/pages/cockpit/CockpitUserCirclePage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "feedback/:circleId",
|
||||
component: () => import("@/pages/cockpit/FeedbackPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "assignment/:assignmentId",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/assignmentsPage/AssignmentsPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "attendance",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/attendanceCheckPage/AttendanceCheckPage.vue"),
|
||||
props: true,
|
||||
name: ATTENDANCE_ROUTE,
|
||||
},
|
||||
{
|
||||
path: "documents",
|
||||
component: () => import("@/pages/cockpit/documentPage/DocumentPage.vue"),
|
||||
props: true,
|
||||
name: DOCUMENTS_ROUTE,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/learn",
|
||||
component: () =>
|
||||
import("../pages/learningPath/learningPathPage/LearningPathPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/learn/:circleSlug",
|
||||
component: () => import("../pages/learningPath/circlePage/CirclePage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/learn/:circleSlug/evaluate/:learningUnitSlug",
|
||||
component: () =>
|
||||
import("../pages/learningPath/selfEvaluationPage/SelfEvaluationPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/learn/:circleSlug/:contentSlug",
|
||||
component: () =>
|
||||
import("../pages/learningPath/learningContentPage/LearningContentPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
|
||||
{
|
||||
path: "/lernbegleitung/:courseId/invitation/:invitationId",
|
||||
component: () => import("@/pages/learningMentor/InvitationAcceptPage.vue"),
|
||||
|
|
@ -168,158 +379,6 @@ const router = createRouter({
|
|||
public: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/profile/:userId",
|
||||
component: () => import("@/pages/userProfile/UserProfilePage.vue"),
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: "learning-path",
|
||||
component: () => import("@/pages/userProfile/LearningPathProfilePage.vue"),
|
||||
props: true,
|
||||
name: "profileLearningPath",
|
||||
meta: {
|
||||
hideChrome: true,
|
||||
showCloseButton: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "competence",
|
||||
component: () => import("@/pages/userProfile/CompetenceProfilePage.vue"),
|
||||
props: true,
|
||||
name: "profileCompetence",
|
||||
meta: {
|
||||
hideChrome: true,
|
||||
showCloseButton: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "competenceMain",
|
||||
component: () =>
|
||||
import(
|
||||
"@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "evaluations",
|
||||
name: "competenceEvaluations",
|
||||
component: () =>
|
||||
import(
|
||||
"@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackList.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "certificates/:certificateSlug",
|
||||
name: "competenceCertificateDetail",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "certificates",
|
||||
name: "competenceCertificates",
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateListPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/learning-mentor",
|
||||
component: () => import("@/pages/learningMentor/mentor/MentorIndexPage.vue"),
|
||||
props: true,
|
||||
name: "learningMentor",
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorParticipantsPage.vue"),
|
||||
name: "mentorsAndParticipants",
|
||||
},
|
||||
{
|
||||
path: "tasks",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorOverviewPage.vue"),
|
||||
name: "learningMentorOverview",
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback/:learningUnitId",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/SelfEvaluationFeedbackPage.vue"),
|
||||
name: "mentorSelfEvaluationFeedback",
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "details",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorDetailParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "praxis-assignments/:praxisAssignmentId",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorPraxisAssignmentPage.vue"),
|
||||
name: "learningMentorPraxisAssignments",
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback-assignments/:learningUnitId",
|
||||
component: () =>
|
||||
import(
|
||||
"@/pages/learningMentor/mentor/MentorSelfEvaluationFeedbackAssignmentPage.vue"
|
||||
),
|
||||
name: "learningMentorSelfEvaluationFeedbackAssignments",
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/assignment-evaluation/:assignmentId/:userId",
|
||||
component: () =>
|
||||
import("@/pages/assignmentEvaluation/AssignmentEvaluationPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/cockpit",
|
||||
name: "cockpit",
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/pages/cockpit/cockpitPage/CockpitExpertPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "profile/:userId/:circleSlug",
|
||||
component: () => import("@/pages/cockpit/CockpitUserCirclePage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "feedback/:circleId",
|
||||
component: () => import("@/pages/cockpit/FeedbackPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "assignment/:assignmentId",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/assignmentsPage/AssignmentsPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "attendance",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/attendanceCheckPage/AttendanceCheckPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "documents",
|
||||
component: () => import("@/pages/cockpit/documentPage/DocumentPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/statistic/:courseSlug",
|
||||
props: true,
|
||||
|
|
@ -389,10 +448,11 @@ const router = createRouter({
|
|||
{
|
||||
path: "/profile",
|
||||
component: () => import("@/pages/personalProfile/PersonalProfilePage.vue"),
|
||||
name: "personalProfile",
|
||||
name: PERSONAL_PROFILE_ROUTE,
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
name: SETTINGS_ROUTE,
|
||||
component: () => import("@/pages/SettingsPage.vue"),
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
export const COMPETENCE_ROUTE = "competence";
|
||||
export const CERTIFICATES_ROUTE = "certificates";
|
||||
export const SELF_EVALUATION_ROUTE = "selfEvaluationAndFeedback";
|
||||
export const COMPETENCES_ROUTE = "competences";
|
||||
export const SETTINGS_ROUTE = "settings";
|
||||
export const COCKPIT_ROUTE = "cockpit-home";
|
||||
export const ATTENDANCE_ROUTE = "attendance";
|
||||
export const DOCUMENTS_ROUTE = "documents";
|
||||
export const PERSONAL_PROFILE_ROUTE = "personalProfile";
|
||||
export const MENTORS_PARTICIPANTS_ROUTE = "mentorsAndParticipants";
|
||||
export const MENTOR_OVERVIEW_ROUTE = "learningMentorOverview";
|
||||
export const LEARN_ROUTE = "learn";
|
||||
export const CERTIFICATE_OVERVIEW_ROUTE = "certificateOverview";
|
||||
|
|
@ -1,50 +1,14 @@
|
|||
import { useCourseData } from "@/composables";
|
||||
import { useCourseData, useCourseSessionDetailQuery } from "@/composables";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { CircleLight, CourseSessionUser, ExpertSessionUser } from "@/types";
|
||||
import log from "loglevel";
|
||||
import { defineStore } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
type CircleExpertCockpit = CircleLight & {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ExpertCockpitStoreState = {
|
||||
courseSessionMembers: CourseSessionUser[] | undefined;
|
||||
circles: CircleExpertCockpit[] | undefined;
|
||||
currentCircle: CircleExpertCockpit | undefined;
|
||||
};
|
||||
|
||||
export const useExpertCockpitStore = defineStore({
|
||||
id: "expertCockpit",
|
||||
state: () => {
|
||||
return {
|
||||
courseSessionMembers: undefined,
|
||||
circles: [],
|
||||
currentCircle: undefined,
|
||||
} as ExpertCockpitStoreState;
|
||||
},
|
||||
actions: {
|
||||
async loadCircles(
|
||||
courseSlug: string,
|
||||
currentCourseSessionUser: CourseSessionUser | undefined
|
||||
) {
|
||||
log.debug("loadCircles called", courseSlug);
|
||||
|
||||
this.circles = await courseCircles(courseSlug, currentCourseSessionUser);
|
||||
|
||||
if (this.circles?.length) {
|
||||
await this.setCurrentCourseCircle(this.circles[0].slug);
|
||||
}
|
||||
},
|
||||
async setCurrentCourseCircle(circleSlug: string) {
|
||||
this.currentCircle = this.circles?.find((c) => c.slug === circleSlug);
|
||||
},
|
||||
async setCurrentCourseCircleFromEvent(event: CircleLight) {
|
||||
await this.setCurrentCourseCircle(event.slug);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function courseCircles(
|
||||
courseSlug: string,
|
||||
currentCourseSessionUser: CourseSessionUser | undefined
|
||||
|
|
@ -74,3 +38,51 @@ async function courseCircles(
|
|||
|
||||
return [];
|
||||
}
|
||||
|
||||
export const useExpertCockpitStore = defineStore("expertCockpit", () => {
|
||||
const courseSessionMembers = ref<CourseSessionUser[] | undefined>(undefined);
|
||||
const circles = ref<CircleExpertCockpit[] | undefined>([]);
|
||||
const currentCircle = ref<CircleExpertCockpit | undefined>(undefined);
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const attendanceCourses = computed(() => {
|
||||
return (
|
||||
courseSessionDetailResult.courseSessionDetail.value?.attendance_courses ?? []
|
||||
);
|
||||
});
|
||||
|
||||
const currentCourse = computed(() => {
|
||||
return attendanceCourses.value.find(
|
||||
(i) => i.learning_content.circle?.id == currentCircle.value?.id
|
||||
);
|
||||
});
|
||||
|
||||
const loadCircles = async (
|
||||
courseSlug: string,
|
||||
currentCourseSessionUser: CourseSessionUser | undefined
|
||||
) => {
|
||||
log.debug("loadCircles called", courseSlug);
|
||||
|
||||
circles.value = await courseCircles(courseSlug, currentCourseSessionUser);
|
||||
|
||||
if (circles.value?.length) {
|
||||
await setCurrentCourseCircle(circles.value[0].slug);
|
||||
}
|
||||
};
|
||||
|
||||
const setCurrentCourseCircle = async (circleSlug: string) => {
|
||||
currentCircle.value = circles.value?.find((c) => c.slug === circleSlug);
|
||||
};
|
||||
const setCurrentCourseCircleFromEvent = async (event: CircleLight) => {
|
||||
await setCurrentCourseCircle(event.slug);
|
||||
};
|
||||
|
||||
return {
|
||||
courseSessionMembers,
|
||||
circles,
|
||||
currentCircle,
|
||||
loadCircles,
|
||||
currentCourse,
|
||||
setCurrentCourseCircleFromEvent,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,14 +5,6 @@ import { directUpload } from "@/services/files";
|
|||
import dayjs from "dayjs";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
let logoutRedirectUrl = import.meta.env.VITE_LOGOUT_REDIRECT || "/";
|
||||
|
||||
if (import.meta.env.VITE_OAUTH_API_BASE_URL) {
|
||||
logoutRedirectUrl = `${
|
||||
import.meta.env.VITE_OAUTH_API_BASE_URL
|
||||
}logout/?post_logout_redirect_uri=${window.location.origin}&client_id=iterativ`;
|
||||
}
|
||||
|
||||
const AVAILABLE_LANGUAGES = ["de", "fr", "it"];
|
||||
|
||||
export type AvailableLanguages = "de" | "fr" | "it";
|
||||
|
|
@ -150,16 +142,7 @@ export const useUserStore = defineStore({
|
|||
handleLogout() {
|
||||
Object.assign(this, initialUserState);
|
||||
|
||||
itPost("/api/core/logout/", {}).then(() => {
|
||||
let redirectUrl;
|
||||
|
||||
if (logoutRedirectUrl !== "") {
|
||||
redirectUrl = logoutRedirectUrl;
|
||||
} else {
|
||||
redirectUrl = "/";
|
||||
}
|
||||
window.location.href = redirectUrl;
|
||||
});
|
||||
window.location.href = `${window.location.origin}/sso/logout`;
|
||||
},
|
||||
async fetchUser() {
|
||||
const data: any = await itGetCached("/api/core/me/");
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import type {
|
|||
LearningContentAttendanceCourseObjectType,
|
||||
LearningContentDocumentListObjectType,
|
||||
LearningContentEdoniqTestObjectType,
|
||||
LearningContentFeedbackAutomobilGewerbeObjectType,
|
||||
LearningContentFeedbackUkObjectType,
|
||||
LearningContentFeedbackVvObjectType,
|
||||
LearningContentKnowledgeAssessmentObjectType,
|
||||
|
|
@ -77,6 +78,11 @@ export type LearningContentFeedbackUK = LearningContentFeedbackUkObjectType & {
|
|||
readonly content_type: "learnpath.LearningContentFeedbackUK";
|
||||
};
|
||||
|
||||
export type LearningContentFeedbackAutomobilGewerbe =
|
||||
LearningContentFeedbackAutomobilGewerbeObjectType & {
|
||||
readonly content_type: "learnpath.LearningContentFeedbackAutomobilGewerbe";
|
||||
};
|
||||
|
||||
export type LearningContentLearningModule = LearningContentLearningModuleObjectType & {
|
||||
readonly content_type: "learnpath.LearningContentLearningModule";
|
||||
};
|
||||
|
|
@ -109,6 +115,7 @@ export type LearningContent =
|
|||
| LearningContentEdoniqTest
|
||||
| LearningContentFeedbackUK
|
||||
| LearningContentFeedbackVV
|
||||
| LearningContentFeedbackAutomobilGewerbe
|
||||
| LearningContentLearningModule
|
||||
| LearningContentKnowledgeAssessment
|
||||
| LearningContentMediaLibrary
|
||||
|
|
@ -116,6 +123,11 @@ export type LearningContent =
|
|||
| LearningContentRichText
|
||||
| LearningContentVideo;
|
||||
|
||||
export type LearningContentFeedback =
|
||||
| LearningContentFeedbackUK
|
||||
| LearningContentFeedbackVV
|
||||
| LearningContentFeedbackAutomobilGewerbe;
|
||||
|
||||
export type LearningContentWithCompletion = LearningContent &
|
||||
Completable & {
|
||||
continueUrl?: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import { isInFuture } from "@/components/dueDates/dueDatesUtils";
|
||||
|
||||
export type Status = "done" | "soon" | "now";
|
||||
|
||||
export const getStatus = (done: boolean, date: string): Status => {
|
||||
if (done) {
|
||||
return "done";
|
||||
}
|
||||
if (isInFuture(date)) {
|
||||
return "soon";
|
||||
}
|
||||
return "now";
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { computed } from "vue";
|
||||
import type { RouteLocationRaw } from "vue-router";
|
||||
import { useRouteLookups } from "./route";
|
||||
|
||||
export function useNavigationAttributes() {
|
||||
|
|
@ -77,3 +78,7 @@ export function useNavigationAttributes() {
|
|||
hasSessionTitle,
|
||||
};
|
||||
}
|
||||
|
||||
export const isExternalLink = (route: string | RouteLocationRaw) => {
|
||||
return typeof route === "string" && route.startsWith("https");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
export function validatePostalCode(input: string) {
|
||||
// Remove non-ASCII characters
|
||||
// eslint-disable-next-line no-control-regex
|
||||
input = input.replace(/[^\x00-\x7F]/g, "");
|
||||
if (input.length < 4) {
|
||||
return false;
|
||||
}
|
||||
const regex = /^[0-9]+$/;
|
||||
return regex.test(input);
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
export function useRouteLookups() {
|
||||
|
|
@ -7,11 +8,15 @@ export function useRouteLookups() {
|
|||
return route.path.startsWith("/course/");
|
||||
}
|
||||
|
||||
const isInCourse = computed(() => inCourse());
|
||||
|
||||
function inCockpit() {
|
||||
const regex = new RegExp("/course/[^/]+/cockpit($|/)");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
const isInCockpit = computed(() => inCockpit());
|
||||
|
||||
function inLearningPath() {
|
||||
const regex = new RegExp("/course/[^/]+/learn($|/)");
|
||||
return regex.test(route.path);
|
||||
|
|
@ -39,7 +44,9 @@ export function useRouteLookups() {
|
|||
|
||||
return {
|
||||
inMediaLibrary,
|
||||
isInCourse,
|
||||
inCockpit,
|
||||
isInCockpit,
|
||||
inLearningPath,
|
||||
inCompetenceProfile,
|
||||
inLearningMentor,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export function learningContentTypeData(
|
|||
return { title: t("learningContentTypes.text"), icon: "it-icon-lc-resource" };
|
||||
case "learnpath.LearningContentFeedbackUK":
|
||||
case "learnpath.LearningContentFeedbackVV":
|
||||
case "learnpath.LearningContentFeedbackAutomobilGewerbe":
|
||||
return { title: t("learningContentTypes.feedback"), icon: "it-icon-lc-feedback" };
|
||||
case "learnpath.LearningContentPlaceholder":
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -176,8 +176,12 @@ textarea {
|
|||
@apply rounded-full bg-blue-900 px-4 py-2 font-semibold text-white;
|
||||
}
|
||||
|
||||
.nav-item-base {
|
||||
@apply inline-flex items-center border-b-4 border-transparent px-1 pt-1 text-white;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
@apply inline-flex items-center border-b-4 border-transparent px-1 pt-1 text-white hover:text-sky-500;
|
||||
@apply nav-item-base hover:text-sky-500;
|
||||
}
|
||||
|
||||
.nav-item-no-mobile {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ export default defineConfig(({ mode }) => {
|
|||
host: true,
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
watch: {
|
||||
// This fixes HMR but leads to schema changes not being taken into account unless the page is refreshed
|
||||
ignored: ["**/gql/dist/minifiedSchema.json"],
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
# Run every 6 hours
|
||||
0 */6 * * * /usr/local/bin/python /app/manage.py simple_dummy_job
|
||||
|
||||
# Run every hour at minute 11
|
||||
0 */11 * * * /usr/local/bin/python /app/manage.py handle_sso_sync_errors
|
||||
|
||||
# Run every hour at minute 17
|
||||
17 * * * * /usr/local/bin/python /app/manage.py edoniq_import_results
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ describe("selfEvaluation.cy.js", () => {
|
|||
cy.visit("/course/test-lehrgang/competence");
|
||||
cy.get('[data-cy="self-evaluation-fail"]').should("have.text", "0");
|
||||
cy.get('[data-cy="self-evaluation-success"]').should("have.text", "0");
|
||||
cy.get('[data-cy="self-evaluation-unknown"]').should("have.text", "4");
|
||||
cy.get('[data-cy="self-evaluation-unknown"]').should("have.text", "6");
|
||||
|
||||
// learning unit id = 692 also known as:
|
||||
// Bedarfsanalyse, Ist- und Soll-Situation <<Reisen>>
|
||||
|
|
@ -45,7 +45,7 @@ describe("selfEvaluation.cy.js", () => {
|
|||
cy.makeSelfEvaluation([true, false]);
|
||||
cy.url().should(
|
||||
"include",
|
||||
"/course/test-lehrgang/competence/self-evaluation-and-feedback"
|
||||
"/course/test-lehrgang/competence/self-evaluation-and-feedback",
|
||||
);
|
||||
|
||||
// check data again on KompetenzNavi
|
||||
|
|
@ -57,7 +57,7 @@ describe("selfEvaluation.cy.js", () => {
|
|||
cy.visit("/course/test-lehrgang/competence");
|
||||
cy.get('[data-cy="self-evaluation-fail"]').should("have.text", "1");
|
||||
cy.get('[data-cy="self-evaluation-success"]').should("have.text", "1");
|
||||
cy.get('[data-cy="self-evaluation-unknown"]').should("have.text", "2");
|
||||
cy.get('[data-cy="self-evaluation-unknown"]').should("have.text", "4");
|
||||
});
|
||||
|
||||
it("should be able to make a happy self evaluation", () => {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
describe("with data", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days"
|
||||
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days",
|
||||
);
|
||||
login("test-supervisor1@example.com", "test");
|
||||
cy.visit("/");
|
||||
|
|
@ -28,7 +28,7 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
// -> makes sure that the numbers are correct
|
||||
getDashboardStatistics("assignments.completed").should(
|
||||
"have.text",
|
||||
"1"
|
||||
"1",
|
||||
);
|
||||
getDashboardStatistics("assignments.passed").should("have.text", "34%");
|
||||
});
|
||||
|
|
@ -48,11 +48,11 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("attendance.dayCompleted").should(
|
||||
"have.text",
|
||||
"1"
|
||||
"1",
|
||||
);
|
||||
getDashboardStatistics("attendance.participantsPresent").should(
|
||||
"have.text",
|
||||
"34%"
|
||||
"34%",
|
||||
);
|
||||
});
|
||||
it("contains correct details link", () => {
|
||||
|
|
@ -70,7 +70,7 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
describe("feedback summary box", () => {
|
||||
it("contains correct numbers", () => {
|
||||
getDashboardStatistics("feedback.average").should("have.text", "3.3");
|
||||
getDashboardStatistics("feedback.count").should("have.text", "6");
|
||||
getDashboardStatistics("feedback.count").should("have.text", "9");
|
||||
});
|
||||
it("contains correct details link", () => {
|
||||
clickOnDetailsLink("feedback");
|
||||
|
|
@ -106,7 +106,7 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
describe("with deducted points", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand(
|
||||
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 6,6,6,3,3 --assignment-points-deducted 14 --create-edoniq-test-results 19 24 8"
|
||||
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 6,6,6,3,3 --assignment-points-deducted 14 --create-edoniq-test-results 19 24 8",
|
||||
);
|
||||
login("test-supervisor1@example.com", "test");
|
||||
cy.visit("/");
|
||||
|
|
@ -120,14 +120,14 @@ describe("dashboardSupervisor.cy.js", () => {
|
|||
|
||||
// check data on the details page
|
||||
cy.get(
|
||||
'[data-cy="dashboard.stats.assignments"] [data-cy="basebox.detailsLink"]'
|
||||
'[data-cy="dashboard.stats.assignments"] [data-cy="basebox.detailsLink"]',
|
||||
).click();
|
||||
cy.get(
|
||||
'[data-cy="Edoniq Wissens- und Verständisfragen - Circle Fahrzeug (Demo)@-1"]'
|
||||
'[data-cy="Edoniq Wissens- und Verständisfragen - Circle Fahrzeug (Demo)@-1"]',
|
||||
).should("contain", "0 von 3 bestanden");
|
||||
|
||||
cy.get(
|
||||
'[data-cy="Überprüfen einer Motorfahrzeugs-Versicherungspolice@-1"]'
|
||||
'[data-cy="Überprüfen einer Motorfahrzeugs-Versicherungspolice@-1"]',
|
||||
).should("contain", "0 von 3 bestanden");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,93 +30,93 @@ describe("feedbackStudent.cy.js", () => {
|
|||
// fill feedback form
|
||||
// step 1
|
||||
cy.url().should("include", "step=1");
|
||||
cy.get("[data-cy=\"question-1\"]").should(
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt"
|
||||
"Zufriedenheit insgesamt",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-4\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-4"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 2
|
||||
cy.url().should("include", "step=2");
|
||||
cy.get("[data-cy=\"question-2\"]").should(
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt"
|
||||
"Zielerreichung insgesamt",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
// the system should store after every step -> check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.false;
|
||||
expect(ac.data.satisfaction).to.equal(4);
|
||||
expect(ac.data.instructor_competence).to.equal(null);
|
||||
}
|
||||
},
|
||||
);
|
||||
cy.get("[data-cy=\"radio-3\"]").click();
|
||||
cy.get('[data-cy="radio-3"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 3
|
||||
cy.url().should("include", "step=3");
|
||||
cy.get("[data-cy=\"question-3\"]").should(
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Kurs?"
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Kurs?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-80\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-80"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 4
|
||||
cy.url().should("include", "step=4");
|
||||
cy.get("[data-cy=\"question-4\"]").should(
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Waren die Vorbereitungsaufträge klar und verständlich?"
|
||||
"Waren die Vorbereitungsaufträge klar und verständlich?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-false\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-false"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 5
|
||||
cy.url().should("include", "step=5");
|
||||
cy.get("[data-cy=\"question-5\"]").should(
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du die Themensicherheit und Fachkompetenz des Kursleiters/der Kursleiterin?"
|
||||
"Wie beurteilst du die Themensicherheit und Fachkompetenz des Kursleiters/der Kursleiterin?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-2\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-2"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 6
|
||||
cy.url().should("include", "step=6");
|
||||
cy.get("[data-cy=\"question-6\"]").should(
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Wurden Fragen und Anregungen der Kursteilnehmenden ernst genommen und aufgegriffen?"
|
||||
"Wurden Fragen und Anregungen der Kursteilnehmenden ernst genommen und aufgegriffen?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-1\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-1"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 7
|
||||
cy.url().should("include", "step=7");
|
||||
cy.get("[data-cy=\"question-7\"]").should(
|
||||
cy.get('[data-cy="question-7"]').should(
|
||||
"contain",
|
||||
"Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?"
|
||||
"Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"it-textarea-instructor_open_feedback\"]").type(
|
||||
"Der Kursleiter ist eigentlich ganz nett."
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-instructor_open_feedback"]').type(
|
||||
"Der Kursleiter ist eigentlich ganz nett.",
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
|
@ -124,26 +124,26 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// step 8
|
||||
cy.url().should("include", "step=8");
|
||||
cy.get("[data-cy=\"question-8\"]").should(
|
||||
cy.get('[data-cy="question-8"]').should(
|
||||
"contain",
|
||||
"Würdest du den Kurs weiterempfehlen?"
|
||||
"Würdest du den Kurs weiterempfehlen?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-true\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-true"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 9
|
||||
cy.url().should("include", "step=9");
|
||||
cy.get("[data-cy=\"question-9\"]").should(
|
||||
cy.get('[data-cy="question-9"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?"
|
||||
"Was hat dir besonders gut gefallen?",
|
||||
);
|
||||
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"it-textarea-course_positive_feedback\"]").type(
|
||||
"Ich bin zufrieden mit den meisten Dingen."
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_positive_feedback"]').type(
|
||||
"Ich bin zufrieden mit den meisten Dingen.",
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
|
@ -151,21 +151,21 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// step 10
|
||||
cy.url().should("include", "step=10");
|
||||
cy.get("[data-cy=\"question-10\"]").should(
|
||||
cy.get('[data-cy="question-10"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?"
|
||||
"Wo siehst du Verbesserungspotential?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"it-textarea-course_negative_feedback\"]").type(
|
||||
"Ich bin unzufrieden mit einigen Sachen."
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_negative_feedback"]').type(
|
||||
"Ich bin unzufrieden mit einigen Sachen.",
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
cy.url().should("include", "step=11");
|
||||
cy.get("[data-cy=\"sendFeedbackButton\"]").click();
|
||||
cy.get("[data-cy=\"complete-and-continue\"]").click({ force: true });
|
||||
cy.get('[data-cy="sendFeedbackButton"]').click();
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
|
||||
// marked complete in circle
|
||||
cy.url().should((url) => {
|
||||
|
|
@ -173,7 +173,7 @@ describe("feedbackStudent.cy.js", () => {
|
|||
});
|
||||
cy.reload();
|
||||
cy.get(
|
||||
"[data-cy=\"test-lehrgang-lp-circle-fahrzeug-lc-feedback-status\"]"
|
||||
'[data-cy="test-lehrgang-lp-circle-fahrzeug-lc-feedback-status"]',
|
||||
).should("have.attr", "aria-checked", "true");
|
||||
|
||||
// reopening page should get directly to last step
|
||||
|
|
@ -197,9 +197,9 @@ describe("feedbackStudent.cy.js", () => {
|
|||
proficiency: 80,
|
||||
satisfaction: 4,
|
||||
would_recommend: true,
|
||||
feedback_type: "uk"
|
||||
feedback_type: "uk",
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -219,8 +219,8 @@ describe("feedbackStudent.cy.js", () => {
|
|||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/reisen\/feedback(\?step=0)?$/);
|
||||
});
|
||||
cy.get("[data-cy=\"introduction\"]").contains(
|
||||
"Wir bitten dich um dein Feedback. Es hilft uns, damit wir deine Lernerlebnisse verbessern können."
|
||||
cy.get('[data-cy="introduction"]').contains(
|
||||
"Wir bitten dich um dein Feedback. Es hilft uns, damit wir deine Lernerlebnisse verbessern können.",
|
||||
);
|
||||
|
||||
cy.wait(200);
|
||||
|
|
@ -230,82 +230,82 @@ describe("feedbackStudent.cy.js", () => {
|
|||
// fill feedback form
|
||||
// step 1
|
||||
cy.url().should("include", "step=1");
|
||||
cy.get("[data-cy=\"question-1\"]").should(
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt"
|
||||
"Zufriedenheit insgesamt",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-4\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-4"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 2
|
||||
cy.url().should("include", "step=2");
|
||||
cy.get("[data-cy=\"question-2\"]").should(
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt"
|
||||
"Zielerreichung insgesamt",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
// the system should store after every step -> check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.false;
|
||||
expect(ac.data.satisfaction).to.equal(4);
|
||||
expect(ac.data.course_positive_feedback).to.equal(null);
|
||||
}
|
||||
},
|
||||
);
|
||||
cy.get("[data-cy=\"radio-3\"]").click();
|
||||
cy.get('[data-cy="radio-3"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 3
|
||||
cy.url().should("include", "step=3");
|
||||
cy.get("[data-cy=\"question-3\"]").should(
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?"
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-80\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-80"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 4
|
||||
cy.url().should("include", "step=4");
|
||||
cy.get("[data-cy=\"question-4\"]").should(
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Waren die Praxisaufträge klar und verständlich?"
|
||||
"Waren die Praxisaufträge klar und verständlich?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-false\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-false"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 5
|
||||
cy.url().should("include", "step=5");
|
||||
cy.get("[data-cy=\"question-5\"]").should(
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Würdest du den Circle weiterempfehlen?"
|
||||
"Würdest du den Circle weiterempfehlen?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"radio-false\"]").click();
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-false"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 6
|
||||
cy.url().should("include", "step=6");
|
||||
cy.get("[data-cy=\"question-6\"]").should(
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?"
|
||||
"Was hat dir besonders gut gefallen?",
|
||||
);
|
||||
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"it-textarea-course_positive_feedback\"]").type(
|
||||
"Der Circle ist eigentlich ganz nett."
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_positive_feedback"]').type(
|
||||
"Der Circle ist eigentlich ganz nett.",
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
|
|
@ -313,21 +313,21 @@ describe("feedbackStudent.cy.js", () => {
|
|||
|
||||
// step 7
|
||||
cy.url().should("include", "step=7");
|
||||
cy.get("[data-cy=\"question-7\"]").should(
|
||||
cy.get('[data-cy="question-7"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?"
|
||||
"Wo siehst du Verbesserungspotential?",
|
||||
);
|
||||
cy.get("[data-cy=\"next-step\"]").should("be.disabled");
|
||||
cy.get("[data-cy=\"it-textarea-course_negative_feedback\"]").type(
|
||||
"Ich bin unzufrieden mit einigen Sachen."
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_negative_feedback"]').type(
|
||||
"Ich bin unzufrieden mit einigen Sachen.",
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
cy.url().should("include", "step=8");
|
||||
cy.get("[data-cy=\"sendFeedbackButton\"]").click();
|
||||
cy.get("[data-cy=\"complete-and-continue\"]").click({ force: true });
|
||||
cy.get('[data-cy="sendFeedbackButton"]').click();
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
|
||||
// marked complete in circle
|
||||
cy.url().should((url) => {
|
||||
|
|
@ -335,7 +335,7 @@ describe("feedbackStudent.cy.js", () => {
|
|||
});
|
||||
cy.reload();
|
||||
cy.get(
|
||||
"[data-cy=\"test-lehrgang-lp-circle-reisen-lc-feedback-status\"]"
|
||||
'[data-cy="test-lehrgang-lp-circle-reisen-lc-feedback-status"]',
|
||||
).should("have.attr", "aria-checked", "true");
|
||||
|
||||
// reopening page should get directly to last step
|
||||
|
|
@ -354,9 +354,155 @@ describe("feedbackStudent.cy.js", () => {
|
|||
proficiency: 80,
|
||||
satisfaction: 4,
|
||||
would_recommend: false,
|
||||
feedback_type: "vv"
|
||||
feedback_type: "vv",
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Feedback Automobilgewerbe", () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/course/test-lehrgang/learn/automobilgewerbe/feedback");
|
||||
});
|
||||
|
||||
it("can open feedback page", () => {
|
||||
cy.testLearningContentTitle("Feedback");
|
||||
cy.testLearningContentSubtitle("Feedback");
|
||||
});
|
||||
|
||||
it("can create feedback by giving answers to all steps", () => {
|
||||
// initial wait for step 0 (or none with step==0) is required for pipelines
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(/\/automobilgewerbe\/feedback(\?step=0)?$/);
|
||||
});
|
||||
cy.get('[data-cy="introduction"]').contains(
|
||||
"Wir bitten dich um dein Feedback. Es hilft uns, damit wir deine Lernerlebnisse verbessern können.",
|
||||
);
|
||||
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// fill feedback form
|
||||
// step 1
|
||||
cy.url().should("include", "step=1");
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt",
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-4"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 2
|
||||
cy.url().should("include", "step=2");
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt",
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
// the system should store after every step -> check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.false;
|
||||
expect(ac.data.satisfaction).to.equal(4);
|
||||
expect(ac.data.course_positive_feedback).to.equal(null);
|
||||
},
|
||||
);
|
||||
cy.get('[data-cy="radio-3"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 3
|
||||
cy.url().should("include", "step=3");
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?",
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-80"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 4
|
||||
cy.url().should("include", "step=4");
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Würdest du den Circle weiterempfehlen?",
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="radio-false"]').click();
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 5
|
||||
cy.url().should("include", "step=5");
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?",
|
||||
);
|
||||
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_positive_feedback"]').type(
|
||||
"Der Circle ist eigentlich ganz nett.",
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
// step 6
|
||||
cy.url().should("include", "step=6");
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?",
|
||||
);
|
||||
cy.get('[data-cy="next-step"]').should("be.disabled");
|
||||
cy.get('[data-cy="it-textarea-course_negative_feedback"]').type(
|
||||
"Ich bin unzufrieden mit einigen Sachen.",
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.learningContentMultiLayoutNextStep();
|
||||
cy.wait(200);
|
||||
|
||||
cy.url().should("include", "step=7");
|
||||
cy.get('[data-cy="sendFeedbackButton"]').click();
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
|
||||
// marked complete in circle
|
||||
cy.url().should((url) => {
|
||||
expect(url).to.match(
|
||||
/\/automobilgewerbe#lu-transfer-reflexion-feedback?$/,
|
||||
);
|
||||
});
|
||||
cy.reload();
|
||||
cy.get(
|
||||
'[data-cy="test-lehrgang-lp-circle-automobilgewerbe-lc-feedback-status"]',
|
||||
).should("have.attr", "aria-checked", "true");
|
||||
|
||||
// reopening page should get directly to last step
|
||||
cy.visit("/course/test-lehrgang/learn/automobilgewerbe/feedback");
|
||||
cy.url().should("include", "step=7");
|
||||
|
||||
// check stored data
|
||||
cy.loadFeedbackResponse("feedback_user_id", TEST_STUDENT1_USER_ID).then(
|
||||
(ac) => {
|
||||
expect(ac.submitted).to.be.true;
|
||||
expect(ac.data).to.deep.equal({
|
||||
course_negative_feedback: "Ich bin unzufrieden mit einigen Sachen.",
|
||||
course_positive_feedback: "Der Circle ist eigentlich ganz nett.",
|
||||
goal_attainment: 3,
|
||||
proficiency: 80,
|
||||
satisfaction: 4,
|
||||
would_recommend: false,
|
||||
feedback_type: "automobilgewerbe",
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {EXPERT_COCKPIT_URL, login} from "../helpers";
|
||||
import { EXPERT_COCKPIT_URL, login } from "../helpers";
|
||||
|
||||
describe("feedbackTrainer.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -10,7 +10,7 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
login("test-trainer1@example.com", "test");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get(
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]'
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]',
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="feedback-data-amount"]').should("contain", "0");
|
||||
|
|
@ -22,7 +22,7 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
login("test-trainer1@example.com", "test");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get(
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]'
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-fahrzeug-lc-feedback"]',
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="feedback-data-amount"]').should("contain", "3");
|
||||
|
|
@ -30,43 +30,43 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
// check titles of questions
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt"
|
||||
"Zufriedenheit insgesamt",
|
||||
);
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt"
|
||||
"Zielerreichung insgesamt",
|
||||
);
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Kurs?"
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Kurs?",
|
||||
);
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Waren die Vorbereitungsaufträge klar und verständlich?"
|
||||
"Waren die Vorbereitungsaufträge klar und verständlich?",
|
||||
);
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du die Themensicherheit und Fachkompetenz des Kursleiters/der Kursleiterin?"
|
||||
"Wie beurteilst du die Themensicherheit und Fachkompetenz des Kursleiters/der Kursleiterin?",
|
||||
);
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Wurden Fragen und Anregungen der Kursteilnehmenden ernst genommen und aufgegriffen?"
|
||||
"Wurden Fragen und Anregungen der Kursteilnehmenden ernst genommen und aufgegriffen?",
|
||||
);
|
||||
cy.get('[data-cy="question-7"]').should(
|
||||
"contain",
|
||||
"Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?"
|
||||
"Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?",
|
||||
);
|
||||
cy.get('[data-cy="question-8"]').should(
|
||||
"contain",
|
||||
"Würdest du den Kurs weiterempfehlen?"
|
||||
"Würdest du den Kurs weiterempfehlen?",
|
||||
);
|
||||
cy.get('[data-cy="question-9"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?"
|
||||
"Wo siehst du Verbesserungspotential?",
|
||||
);
|
||||
cy.get('[data-cy="question-10"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?"
|
||||
"Was hat dir besonders gut gefallen?",
|
||||
);
|
||||
|
||||
cy.get('[data-cy="question-1"]')
|
||||
|
|
@ -142,7 +142,7 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Reisen"]').click();
|
||||
cy.get(
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-reisen-lc-feedback"]'
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-reisen-lc-feedback"]',
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="feedback-data-amount"]').should("contain", "3");
|
||||
|
|
@ -150,31 +150,31 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
// check titles of questions
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt"
|
||||
"Zufriedenheit insgesamt",
|
||||
);
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt"
|
||||
"Zielerreichung insgesamt",
|
||||
);
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?"
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?",
|
||||
);
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Waren die Praxisaufträge klar und verständlich?"
|
||||
"Waren die Praxisaufträge klar und verständlich?",
|
||||
);
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Würdest du den Circle weiterempfehlen?"
|
||||
"Würdest du den Circle weiterempfehlen?",
|
||||
);
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?"
|
||||
"Wo siehst du Verbesserungspotential?",
|
||||
);
|
||||
cy.get('[data-cy="question-7"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?"
|
||||
"Was hat dir besonders gut gefallen?",
|
||||
);
|
||||
|
||||
cy.get('[data-cy="question-1"]')
|
||||
|
|
@ -228,4 +228,87 @@ describe("feedbackTrainer.cy.js", () => {
|
|||
.should("contain", "Die Präsentation war super");
|
||||
});
|
||||
});
|
||||
|
||||
describe("FeedbackAutomobilGewerbe", function () {
|
||||
it("can open feedback results page with results", () => {
|
||||
cy.manageCommand("cypress_reset --create-feedback-responses");
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.get('[data-cy="dropdown-select"]').click();
|
||||
cy.get('[data-cy="dropdown-select-option-Automobilgewerbe"]').click();
|
||||
cy.get(
|
||||
'[data-cy="show-feedback-btn-test-lehrgang-lp-circle-automobilgewerbe-lc-feedback"]',
|
||||
).click();
|
||||
|
||||
cy.get('[data-cy="feedback-data-amount"]').should("contain", "3");
|
||||
|
||||
// check titles of questions
|
||||
cy.get('[data-cy="question-1"]').should(
|
||||
"contain",
|
||||
"Zufriedenheit insgesamt",
|
||||
);
|
||||
cy.get('[data-cy="question-2"]').should(
|
||||
"contain",
|
||||
"Zielerreichung insgesamt",
|
||||
);
|
||||
cy.get('[data-cy="question-3"]').should(
|
||||
"contain",
|
||||
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?",
|
||||
);
|
||||
cy.get('[data-cy="question-4"]').should(
|
||||
"contain",
|
||||
"Würdest du den Circle weiterempfehlen?",
|
||||
);
|
||||
cy.get('[data-cy="question-5"]').should(
|
||||
"contain",
|
||||
"Wo siehst du Verbesserungspotential?",
|
||||
);
|
||||
cy.get('[data-cy="question-6"]').should(
|
||||
"contain",
|
||||
"Was hat dir besonders gut gefallen?",
|
||||
);
|
||||
|
||||
cy.get('[data-cy="question-1"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.3");
|
||||
|
||||
cy.get('[data-cy="question-2"]')
|
||||
.find('[data-cy="rating-scale-average"]')
|
||||
.should("contain", "3.0");
|
||||
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-40%"]')
|
||||
.should("contain", "33.3");
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-80%"]')
|
||||
.should("contain", "33.3");
|
||||
cy.get('[data-cy="question-3"]')
|
||||
.find('[data-cy="percentage-value-100%"]')
|
||||
.should("contain", "33.3");
|
||||
|
||||
cy.get('[data-cy="question-4"]')
|
||||
.find('[data-cy="popover-yes"]')
|
||||
.click()
|
||||
.find('[data-cy="num-yes"]')
|
||||
.should("contain", "2");
|
||||
cy.get('[data-cy="question-4"]')
|
||||
.find('[data-cy="popover-no"]')
|
||||
.click()
|
||||
.find('[data-cy="num-no"]')
|
||||
.should("contain", "1");
|
||||
|
||||
cy.get('[data-cy="question-5"]')
|
||||
.should("contain", "Nichts Schlechtes")
|
||||
.should("contain", "Es wäre praktisch, Zugang zu einer FAQ zu haben.")
|
||||
.should("contain", "Mehr Videos wären schön.");
|
||||
|
||||
cy.get('[data-cy="question-6"]')
|
||||
.should("contain", "Nur Gutes.")
|
||||
.should(
|
||||
"contain",
|
||||
"Das Beispiel mit der Katze fand ich sehr gut veranschaulicht!",
|
||||
)
|
||||
.should("contain", "Die Präsentation war super");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,57 +8,61 @@ describe("learningPath.cy.js", () => {
|
|||
});
|
||||
|
||||
it("can open learningPath page", () => {
|
||||
cy.get("[data-cy=\"learning-path-title\"]").should(
|
||||
cy.get('[data-cy="learning-path-title"]').should(
|
||||
"contain",
|
||||
"Test Lehrgang"
|
||||
"Test Lehrgang",
|
||||
);
|
||||
});
|
||||
|
||||
it("can click on circle to open it", () => {
|
||||
cy.get("[data-cy=\"circle-Fahrzeug\"]").click({ force: true });
|
||||
cy.get('[data-cy="circle-Fahrzeug"]').click({ force: true });
|
||||
|
||||
cy.url().should("include", "/course/test-lehrgang/learn/fahrzeug");
|
||||
cy.get("[data-cy=\"circle-title\"]").should("contain", "Fahrzeug");
|
||||
cy.get('[data-cy="circle-title"]').should("contain", "Fahrzeug");
|
||||
});
|
||||
|
||||
it("switch between list and path view", () => {
|
||||
cy.get("[data-cy=\"lp-path-view\"]").should("be.visible");
|
||||
cy.get("[data-cy=\"view-switch\"]").click();
|
||||
cy.get("[data-cy=\"lp-list-view\"]").should("be.visible");
|
||||
cy.get("[data-cy=\"view-switch\"]").click();
|
||||
cy.get("[data-cy=\"lp-path-view\"]").should("be.visible");
|
||||
cy.get('[data-cy="lp-path-view"]').should("be.visible");
|
||||
cy.get('[data-cy="view-switch"]').click();
|
||||
cy.get('[data-cy="lp-list-view"]').should("be.visible");
|
||||
cy.get('[data-cy="view-switch"]').click();
|
||||
cy.get('[data-cy="lp-path-view"]').should("be.visible");
|
||||
});
|
||||
|
||||
it("weiter gehts button will open next circle", () => {
|
||||
// first click will open first circle
|
||||
cy.get("[data-cy=\"lp-continue-button\"]")
|
||||
cy.get('[data-cy="lp-continue-button"]')
|
||||
.filter(":visible")
|
||||
.should("contain", "Los geht's")
|
||||
.click();
|
||||
cy.get("[data-cy=\"circle-title\"]").should("contain", "Fahrzeug");
|
||||
cy.get("[data-cy=\"back-to-learning-path-button\"]").click();
|
||||
cy.get('[data-cy="circle-title"]').should("contain", "Fahrzeug");
|
||||
cy.get('[data-cy="back-to-learning-path-button"]').click();
|
||||
|
||||
// mark a learning content in second circle
|
||||
cy.get("[data-cy=\"circle-Reisen\"]").click({ force: true });
|
||||
cy.get("[data-cy=\"ls-continue-button\"]").click();
|
||||
cy.get("[data-cy=\"complete-and-continue\"]").click({ force: true });
|
||||
cy.get("[data-cy=\"back-to-learning-path-button\"]").click();
|
||||
cy.get('[data-cy="circle-Reisen"]').click({ force: true });
|
||||
cy.get('[data-cy="ls-continue-button"]').click();
|
||||
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
|
||||
cy.get('[data-cy="back-to-learning-path-button"]').click();
|
||||
|
||||
// click on continue should go to unit-test-circle
|
||||
cy.get("[data-cy=\"lp-continue-button\"]")
|
||||
cy.get('[data-cy="lp-continue-button"]')
|
||||
.filter(":visible")
|
||||
.should("contain", "Weiter geht's")
|
||||
.click();
|
||||
cy.get("[data-cy=\"circle-title\"]").should("contain", "Reisen");
|
||||
cy.get('[data-cy="circle-title"]').should("contain", "Reisen");
|
||||
});
|
||||
|
||||
it("checks contents", () => {
|
||||
cy.get("[data-cy=\"lp-topic\"]").should("have.length", 2);
|
||||
cy.get("[data-cy=\"lp-topic\"]").first().should("contain", "Circle ÜK");
|
||||
cy.get("[data-cy=\"lp-topic\"]").eq(1).should("contain", "Circle VV");
|
||||
cy.get('[data-cy="lp-topic"]').should("have.length", 3);
|
||||
cy.get('[data-cy="lp-topic"]').first().should("contain", "Circle ÜK");
|
||||
cy.get('[data-cy="lp-topic"]').eq(1).should("contain", "Circle VV");
|
||||
cy.get('[data-cy="lp-topic"]')
|
||||
.eq(2)
|
||||
.should("contain", "Circle Automobilgewerbe");
|
||||
|
||||
cy.get(".cy-lp-circle").should("have.length", 2);
|
||||
cy.get(".cy-lp-circle").should("have.length", 3);
|
||||
cy.get(".cy-lp-circle").first().should("contain", "Fahrzeug");
|
||||
cy.get(".cy-lp-circle").eq(1).should("contain", "Reisen");
|
||||
cy.get(".cy-lp-circle").eq(2).should("contain", "Automobilgewerbe");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
import {EXPERT_COCKPIT_URL, login} from "./helpers";
|
||||
import { EXPERT_COCKPIT_URL, login } from "./helpers";
|
||||
|
||||
describe("settings.cy.js", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
cy.intercept("/server/graphql").as("graphql");
|
||||
});
|
||||
|
||||
describe("with circle documents enabled", () => {
|
||||
it("student can see circle documents", () => {
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
||||
cy.wait(["@graphql", "@graphql"]);
|
||||
cy.get('[data-cy="circle-document-section"]').should("exist");
|
||||
});
|
||||
|
||||
it("trainer can see circle documents", () => {
|
||||
login("test-trainer1@example.com", "test");
|
||||
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.wait(["@graphql", "@graphql"]);
|
||||
cy.get('[data-cy="circle-documents"]').should("exist");
|
||||
});
|
||||
});
|
||||
|
|
@ -27,6 +31,7 @@ describe("settings.cy.js", () => {
|
|||
it("student cannot see circle documents", () => {
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
||||
cy.wait(["@graphql", "@graphql"]);
|
||||
cy.get('[data-cy="circle-title"]').should("contain", "Fahrzeug");
|
||||
cy.get('[data-cy="circle-document-section"]').should("not.exist");
|
||||
});
|
||||
|
|
@ -34,6 +39,7 @@ describe("settings.cy.js", () => {
|
|||
it("trainer cannot see circle documents", () => {
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.wait(["@graphql", "@graphql"]);
|
||||
cy.get('[data-cy="circle-documents"]').should("not.exist");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 491 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 381 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,24 @@
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import django
|
||||
from django.db import transaction
|
||||
|
||||
sys.path.append("../server")
|
||||
|
||||
os.environ.setdefault("IT_APP_ENVIRONMENT", "local")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.base")
|
||||
django.setup()
|
||||
|
||||
|
||||
from vbv_lernwelt.core.models import User
|
||||
|
||||
# Get the user whose password you want to use as the reference
|
||||
reference_user = User.objects.get(email='axel.manderbach@lernetz.ch')
|
||||
reference_user.set_password('test')
|
||||
reference_user.save()
|
||||
|
||||
# Update the password for all users
|
||||
with transaction.atomic():
|
||||
User.objects.update(password=reference_user.password)
|
||||
|
|
@ -639,6 +639,8 @@ OAUTH_SIGNIN_REDIRECT_URI = env(
|
|||
"OAUTH_SIGNIN_REDIRECT_URI", default="http://localhost:8000/sso/callback"
|
||||
)
|
||||
|
||||
OAUTH_LOGOUT_REDIRECT_URI = env("OAUTH_LOGOUT_REDIRECT_URI", default="/")
|
||||
|
||||
OAUTH_SIGNIN_URL = env("OAUTH_SIGNIN_URL", default="")
|
||||
OAUTH_SIGNIN_REALM = env("OAUTH_SIGNIN_REALM", default="vbv")
|
||||
OAUTH_SIGNIN_ADMIN_CLIENT_ID = env("OAUTH_SIGNIN_ADMIN_CLIENT_ID", default="")
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ from vbv_lernwelt.core.views import (
|
|||
rate_limit_exceeded_view,
|
||||
vue_home,
|
||||
vue_login,
|
||||
vue_logout,
|
||||
)
|
||||
from vbv_lernwelt.course.views import (
|
||||
course_page_api_view,
|
||||
|
|
@ -124,7 +123,6 @@ urlpatterns = [
|
|||
|
||||
re_path(r'api/core/login/$', django_view_authentication_exempt(vue_login),
|
||||
name='vue_login'),
|
||||
re_path(r'api/core/logout/$', vue_logout, name='vue_logout'),
|
||||
|
||||
# notifications
|
||||
re_path(r'^notifications/', include(notifications.urls, namespace='notifications')),
|
||||
|
|
@ -241,7 +239,7 @@ urlpatterns = [
|
|||
# testing and debug
|
||||
path('server/raise_error/',
|
||||
user_passes_test(lambda u: u.is_superuser, login_url='/login/')(
|
||||
raise_example_error) ),
|
||||
raise_example_error)),
|
||||
path("server/checkratelimit/", check_rate_limit),
|
||||
|
||||
]
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue