Merged in feature/trainer-cockpit-VBV-776--2024-11-14 (pull request #425)
Feature/trainer cockpit VBV-776 2024 11 14 Approved-by: Stéphanie Rotzetter Approved-by: Elia Bieri
This commit is contained in:
commit
2c4512ba91
|
|
@ -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();
|
||||||
|
});
|
||||||
|
|
@ -59,3 +59,12 @@ export const getWeekday = (date: Dayjs) => {
|
||||||
}
|
}
|
||||||
return "";
|
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");
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ const { t } = useTranslation();
|
||||||
<template v-if="isInCourse">
|
<template v-if="isInCourse">
|
||||||
<div class="flex h-full items-center border-r border-slate-500">
|
<div class="flex h-full items-center border-r border-slate-500">
|
||||||
<router-link to="/" class="flex items-center pr-3">
|
<router-link to="/" class="flex items-center pr-3">
|
||||||
<it-icon-arrow-left />
|
<it-icon-arrow-left class="fill-current text-slate-500" />
|
||||||
<span class="hidden text-slate-500 lg:inline">
|
<span class="hidden text-slate-500 lg:inline">
|
||||||
{{ t("a.Dashboard") }}
|
{{ t("a.Dashboard") }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ onMounted(() => {
|
||||||
<CourseSessionNavigation />
|
<CourseSessionNavigation />
|
||||||
</div>
|
</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
|
<router-link
|
||||||
v-if="hasMediaLibraryMenu"
|
v-if="hasMediaLibraryMenu"
|
||||||
:to="
|
:to="
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||||
import { useVVByLink } from "@/composables";
|
import { useVVByLink } from "@/composables";
|
||||||
import { SETTINGS_ROUTE } from "@/router/names";
|
import { PERSONAL_PROFILE_ROUTE, SETTINGS_ROUTE } from "@/router/names";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type { User } from "@/stores/user";
|
import type { User } from "@/stores/user";
|
||||||
import type { CourseSession } from "@/types";
|
import type { CourseSession } from "@/types";
|
||||||
|
|
@ -54,13 +54,16 @@ const mentorTabTitle = computed(() =>
|
||||||
const settingsRoute = {
|
const settingsRoute = {
|
||||||
name: SETTINGS_ROUTE,
|
name: SETTINGS_ROUTE,
|
||||||
};
|
};
|
||||||
|
const profileRoute = {
|
||||||
|
name: PERSONAL_PROFILE_ROUTE,
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ItFullScreenModal :show="show" @closemodal="emit('closemodal')">
|
<ItFullScreenModal :show="show" @closemodal="emit('closemodal')">
|
||||||
<div>
|
<div class="-mx-4">
|
||||||
<div>
|
<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 class="-ml-4 flex">
|
||||||
<div v-if="user?.avatar_url">
|
<div v-if="user?.avatar_url">
|
||||||
<img
|
<img
|
||||||
|
|
@ -71,16 +74,27 @@ const settingsRoute = {
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-6">
|
<div class="ml-6">
|
||||||
<h3>{{ user?.first_name }} {{ user?.last_name }}</h3>
|
<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>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="courseSession" class="mt-6 border-b">
|
<div v-if="courseSession" class="border-b px-4 py-6">
|
||||||
<h4 class="px-4 text-sm text-gray-900">{{ courseSession.course.title }}</h4>
|
<h4 class="mb-4 px-4 text-sm text-gray-900">
|
||||||
<ul class="mt-6 flex flex-col">
|
{{ courseSession.course.title }}
|
||||||
<li v-if="hasCockpitMenu" class="mb-6">
|
</h4>
|
||||||
|
<ul class="flex flex-col gap-2">
|
||||||
|
<li v-if="hasCockpitMenu">
|
||||||
<router-link
|
<router-link
|
||||||
class="w-full px-4 py-2"
|
class="block w-full px-4 py-2"
|
||||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||||
data-cy="navigation-mobile-cockpit-link"
|
data-cy="navigation-mobile-cockpit-link"
|
||||||
:to="getCockpitUrl(courseSession.course.slug)"
|
:to="getCockpitUrl(courseSession.course.slug)"
|
||||||
|
|
@ -89,9 +103,9 @@ const settingsRoute = {
|
||||||
{{ $t("cockpit.title") }}
|
{{ $t("cockpit.title") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="hasPreviewMenu" class="mb-2 flex">
|
<li v-if="hasPreviewMenu">
|
||||||
<router-link
|
<router-link
|
||||||
class="w-full px-4 py-2"
|
class="block w-full px-4 py-2"
|
||||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||||
data-cy="navigation-mobile-preview-link"
|
data-cy="navigation-mobile-preview-link"
|
||||||
:to="getLearningPathUrl(courseSession.course.slug)"
|
:to="getLearningPathUrl(courseSession.course.slug)"
|
||||||
|
|
@ -100,9 +114,9 @@ const settingsRoute = {
|
||||||
{{ $t("a.Vorschau Teilnehmer") }}
|
{{ $t("a.Vorschau Teilnehmer") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="hasLearningPathMenu" class="mb-2 flex">
|
<li v-if="hasLearningPathMenu">
|
||||||
<router-link
|
<router-link
|
||||||
class="w-full px-4 py-2"
|
class="block w-full px-4 py-2"
|
||||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||||
data-cy="navigation-mobile-learning-path-link"
|
data-cy="navigation-mobile-learning-path-link"
|
||||||
:to="getLearningPathUrl(courseSession.course.slug)"
|
:to="getLearningPathUrl(courseSession.course.slug)"
|
||||||
|
|
@ -111,9 +125,9 @@ const settingsRoute = {
|
||||||
{{ $t("general.learningPath") }}
|
{{ $t("general.learningPath") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="hasCompetenceNaviMenu" class="mb-2 flex">
|
<li v-if="hasCompetenceNaviMenu">
|
||||||
<router-link
|
<router-link
|
||||||
class="w-full px-4 py-2"
|
class="block w-full px-4 py-2"
|
||||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||||
data-cy="navigation-mobile-competence-profile-link"
|
data-cy="navigation-mobile-competence-profile-link"
|
||||||
:to="getCompetenceNaviUrl(courseSession.course.slug)"
|
:to="getCompetenceNaviUrl(courseSession.course.slug)"
|
||||||
|
|
@ -122,9 +136,9 @@ const settingsRoute = {
|
||||||
{{ $t("competences.title") }}
|
{{ $t("competences.title") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="hasLearningMentor" class="mb-2 flex">
|
<li v-if="hasLearningMentor">
|
||||||
<router-link
|
<router-link
|
||||||
class="w-full px-4 py-2"
|
class="block w-full px-4 py-2"
|
||||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||||
data-cy="navigation-mobile-mentor-link"
|
data-cy="navigation-mobile-mentor-link"
|
||||||
:to="getLearningMentorUrl(courseSession.course.slug)"
|
:to="getLearningMentorUrl(courseSession.course.slug)"
|
||||||
|
|
@ -147,9 +161,9 @@ const settingsRoute = {
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6 border-b">
|
<div v-if="courseSession" class="border-b px-4">
|
||||||
<ul>
|
<ul class="flex flex-col gap-2 py-6">
|
||||||
<li v-if="courseSession && hasMediaLibraryMenu" class="mb-6 flex">
|
<li v-if="courseSession && hasMediaLibraryMenu" class="flex">
|
||||||
<router-link
|
<router-link
|
||||||
data-cy="medialibrary-link"
|
data-cy="medialibrary-link"
|
||||||
class="flex w-full items-center gap-2 px-4 py-2"
|
class="flex w-full items-center gap-2 px-4 py-2"
|
||||||
|
|
@ -161,7 +175,7 @@ const settingsRoute = {
|
||||||
{{ $t("a.Mediathek") }}
|
{{ $t("a.Mediathek") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="courseSession && hasMediaLibraryMenu" class="mb-6 flex">
|
<li v-if="courseSession && hasMediaLibraryMenu" class="flex">
|
||||||
<router-link
|
<router-link
|
||||||
data-cy="calendar-link"
|
data-cy="calendar-link"
|
||||||
class="flex w-full items-center gap-2 px-4 py-2"
|
class="flex w-full items-center gap-2 px-4 py-2"
|
||||||
|
|
@ -176,25 +190,28 @@ const settingsRoute = {
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
|
||||||
:to="settingsRoute"
|
<div class="flex flex-col gap-2 px-4 py-6">
|
||||||
class="mt-6 flex w-full items-center gap-2 px-4 py-2"
|
<router-link
|
||||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
v-if="user?.loggedIn"
|
||||||
v-if="user?.loggedIn"
|
:to="settingsRoute"
|
||||||
type="button"
|
class="flex w-full items-center gap-2 px-4 py-2"
|
||||||
>
|
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||||
<it-icon-settings />
|
type="button"
|
||||||
{{ $t("a.Einstellungen") }}
|
>
|
||||||
</router-link>
|
<it-icon-settings />
|
||||||
<button
|
{{ $t("a.Einstellungen") }}
|
||||||
v-if="user?.loggedIn"
|
</router-link>
|
||||||
type="button"
|
<button
|
||||||
class="mt-6 flex items-center px-4 py-2"
|
v-if="user?.loggedIn"
|
||||||
@click="$emit('logout')"
|
type="button"
|
||||||
>
|
class="flex items-center px-4 py-2"
|
||||||
<it-icon-logout class="inline-block" />
|
@click="$emit('logout')"
|
||||||
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
>
|
||||||
</button>
|
<it-icon-logout class="inline-block" />
|
||||||
|
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -36,14 +36,13 @@ const showCourseSessionMenu = computed(
|
||||||
v-if="hasSessionTitle"
|
v-if="hasSessionTitle"
|
||||||
class="nav-item-base inline-flex items-center lg:inline-flex"
|
class="nav-item-base inline-flex items-center lg:inline-flex"
|
||||||
>
|
>
|
||||||
<div data-cy="current-course-session-title" class="text-bold">
|
|
||||||
{{ selectedCourseSessionTitle }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Popover v-if="showCourseSessionMenu" class="relative">
|
<Popover v-if="showCourseSessionMenu" class="relative">
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
class="group flex items-center rounded-md bg-transparent px-3 text-base focus:outline-none"
|
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" />
|
<it-icon-arrow-down class="h-6 w-6" />
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
<PopoverPanel class="absolute left-0 z-10 mt-3 w-64 px-1 sm:px-0 lg:max-w-3xl">
|
<PopoverPanel class="absolute left-0 z-10 mt-3 w-64 px-1 sm:px-0 lg:max-w-3xl">
|
||||||
|
|
@ -59,5 +58,9 @@ const showCourseSessionMenu = computed(
|
||||||
</div>
|
</div>
|
||||||
</PopoverPanel>
|
</PopoverPanel>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
|
<div v-else data-cy="current-course-session-title" class="text-bold">
|
||||||
|
{{ selectedCourseSessionTitle }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const isExternalLink = computed(() => {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<it-icon-external-link class="w-6" />
|
<it-icon-external-link class="h-6 w-6" />
|
||||||
</a>
|
</a>
|
||||||
<!-- make `:to` explicit -->
|
<!-- make `:to` explicit -->
|
||||||
<router-link
|
<router-link
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ interface Props {
|
||||||
items?: DropdownSelectable[];
|
items?: DropdownSelectable[];
|
||||||
borderless?: boolean;
|
borderless?: boolean;
|
||||||
placeholderText?: string | null;
|
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<{
|
const emit = defineEmits<{
|
||||||
|
|
@ -24,6 +26,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
},
|
},
|
||||||
items: () => [],
|
items: () => [],
|
||||||
placeholderText: null,
|
placeholderText: null,
|
||||||
|
asHeading: false,
|
||||||
|
typeName: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const dropdownSelected = computed<DropdownSelectable>({
|
const dropdownSelected = computed<DropdownSelectable>({
|
||||||
|
|
@ -36,26 +40,34 @@ const dropdownSelected = computed<DropdownSelectable>({
|
||||||
<Listbox v-model="dropdownSelected" as="div">
|
<Listbox v-model="dropdownSelected" as="div">
|
||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<ListboxButton
|
<ListboxButton
|
||||||
class="relative flex w-full cursor-default flex-row items-center bg-white py-3 pl-5 pr-10 text-left"
|
:class="[
|
||||||
:class="{
|
{
|
||||||
border: !props.borderless,
|
border: !borderless && !asHeading,
|
||||||
'font-bold': !props.borderless,
|
'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"
|
data-cy="dropdown-select"
|
||||||
>
|
>
|
||||||
<span v-if="dropdownSelected.iconName" class="mr-4">
|
<span v-if="dropdownSelected.iconName" class="mr-4">
|
||||||
<component :is="dropdownSelected.iconName"></component>
|
<component :is="dropdownSelected.iconName"></component>
|
||||||
</span>
|
</span>
|
||||||
<span class="block truncate">
|
<span :class="[asHeading ? 'h-11 text-4xl' : '']" class="block truncate">
|
||||||
{{ dropdownSelected.name }}
|
{{ typeName }} {{ dropdownSelected.name }}
|
||||||
<span v-if="placeholderText && !dropdownSelected.name" class="text-gray-900">
|
<span v-if="placeholderText && !dropdownSelected.name" class="text-gray-900">
|
||||||
{{ placeholderText }}
|
{{ placeholderText }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</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>
|
</span>
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
|
|
||||||
|
|
@ -79,7 +91,7 @@ const dropdownSelected = computed<DropdownSelectable>({
|
||||||
active ? 'bg-blue-900 text-white' : 'text-black',
|
active ? 'bg-blue-900 text-white' : 'text-black',
|
||||||
'relative cursor-default select-none py-2 pl-3 pr-9',
|
'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}`"
|
:data-cy="`dropdown-select-option-${item.name}`"
|
||||||
>
|
>
|
||||||
<span v-if="item.iconName" class="mr-4">
|
<span v-if="item.iconName" class="mr-4">
|
||||||
|
|
@ -98,7 +110,11 @@ const dropdownSelected = computed<DropdownSelectable>({
|
||||||
v-if="dropdownSelected"
|
v-if="dropdownSelected"
|
||||||
class="absolute inset-y-0 right-0 flex items-center pr-4 text-blue-900"
|
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>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ListboxOption>
|
</ListboxOption>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
"a.An Durchführung teilnehmen": "An Durchführung teilnehmen",
|
"a.An Durchführung teilnehmen": "An Durchführung teilnehmen",
|
||||||
"a.Anmelden": "Anmelden",
|
"a.Anmelden": "Anmelden",
|
||||||
"a.Anwesenheit": "Anwesenheit",
|
"a.Anwesenheit": "Anwesenheit",
|
||||||
|
"a.Anwesenheit anschauen": "Anwesenheit anschauen",
|
||||||
"a.Anwesenheit Präsenzkurse": "Anwesenheit Präsenzkurse",
|
"a.Anwesenheit Präsenzkurse": "Anwesenheit Präsenzkurse",
|
||||||
"a.Anwesenheitskontrolle Präsenzkurse": "Anwesenheitskontrolle Präsenzkurse",
|
"a.Anwesenheitskontrolle Präsenzkurse": "Anwesenheitskontrolle Präsenzkurse",
|
||||||
"a.Arbeiten": "Arbeiten",
|
"a.Arbeiten": "Arbeiten",
|
||||||
|
|
@ -84,6 +85,8 @@
|
||||||
"a.Deine Änderungen wurden gespeichert": "Deine Änderungen wurden gespeichert",
|
"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 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 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 anschauen": "Details anschauen",
|
||||||
"a.Details anzeigen": "Details anzeigen",
|
"a.Details anzeigen": "Details anzeigen",
|
||||||
"a.Deutsch": "Deutsch",
|
"a.Deutsch": "Deutsch",
|
||||||
|
|
@ -95,6 +98,7 @@
|
||||||
"a.Du hast alles erledigt.": "Du hast alles erledigt.",
|
"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 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 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 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 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.",
|
"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.",
|
||||||
|
|
@ -310,6 +314,7 @@
|
||||||
"a.Überbetriebliche Kurse": "Überbetriebliche Kurse",
|
"a.Überbetriebliche Kurse": "Überbetriebliche Kurse",
|
||||||
"a.Übergangslösung Innendienst-Mitarbeitende": "Übergangslösung Innendienst-Mitarbeitende",
|
"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 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": "Übersicht",
|
||||||
"a.Übersicht anschauen": "Übersicht anschauen",
|
"a.Übersicht anschauen": "Übersicht anschauen",
|
||||||
"Abgabe": "Abgabe",
|
"Abgabe": "Abgabe",
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
"a.An Durchführung teilnehmen": "Participer à la session",
|
"a.An Durchführung teilnehmen": "Participer à la session",
|
||||||
"a.Anmelden": "Connexion",
|
"a.Anmelden": "Connexion",
|
||||||
"a.Anwesenheit": "Présence",
|
"a.Anwesenheit": "Présence",
|
||||||
|
"a.Anwesenheit anschauen": "Voir le contrôle de présence",
|
||||||
"a.Anwesenheit Präsenzkurse": "Présence aux cours",
|
"a.Anwesenheit Präsenzkurse": "Présence aux cours",
|
||||||
"a.Anwesenheitskontrolle Präsenzkurse": "Contrôle de présence aux cours",
|
"a.Anwesenheitskontrolle Präsenzkurse": "Contrôle de présence aux cours",
|
||||||
"a.Arbeiten": "Travaux",
|
"a.Arbeiten": "Travaux",
|
||||||
|
|
@ -84,6 +85,8 @@
|
||||||
"a.Deine Änderungen wurden gespeichert": "Tes modifications ont été enregistrées",
|
"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 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 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 anschauen": "Voir les détails",
|
||||||
"a.Details anzeigen": "Afficher les détails",
|
"a.Details anzeigen": "Afficher les détails",
|
||||||
"a.Deutsch": "Allemand",
|
"a.Deutsch": "Allemand",
|
||||||
|
|
@ -95,6 +98,7 @@
|
||||||
"a.Du hast alles erledigt.": "Tu as tout fini.",
|
"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 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 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 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 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.",
|
"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.",
|
||||||
|
|
@ -310,6 +314,7 @@
|
||||||
"a.Überbetriebliche Kurse": "Cours interentreprises",
|
"a.Überbetriebliche Kurse": "Cours interentreprises",
|
||||||
"a.Übergangslösung Innendienst-Mitarbeitende": "Solution transitoire pour le service interne",
|
"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 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": "Aperçu",
|
||||||
"a.Übersicht anschauen": "Consulter l'aperçu",
|
"a.Übersicht anschauen": "Consulter l'aperçu",
|
||||||
"Abgabe": "Remise",
|
"Abgabe": "Remise",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
"a.Abgabetermin": "Termine di consegna",
|
"a.Abgabetermin": "Termine di consegna",
|
||||||
"a.Abgezogene Punkte": "Punti detratti",
|
"a.Abgezogene Punkte": "Punti detratti",
|
||||||
"a.Adresse": "Indirizzo",
|
"a.Adresse": "Indirizzo",
|
||||||
"a.AGB": "Condizioni generali",
|
|
||||||
"a.Aktuell begleitest du niemanden als Lernbegleitung.": "Attualmente non stai accompagnando nessuno come mentore di apprendimento.",
|
"a.Aktuell begleitest du niemanden als Lernbegleitung.": "Attualmente non stai accompagnando nessuno come mentore di apprendimento.",
|
||||||
"a.Aktuell begleitest du niemanden als Praxisbildner.": "Attualmente non stai accompagnando nessuno come formatore/-trice pratico/a.",
|
"a.Aktuell begleitest du niemanden als Praxisbildner.": "Attualmente non stai accompagnando nessuno come formatore/-trice pratico/a.",
|
||||||
"a.Aktuell bist du leider keiner Durchführung zugewiesen.": "Attualmente non sei assegnato a nessuna sessione, purtroppo.",
|
"a.Aktuell bist du leider keiner Durchführung zugewiesen.": "Attualmente non sei assegnato a nessuna sessione, purtroppo.",
|
||||||
|
|
@ -34,6 +33,7 @@
|
||||||
"a.An Durchführung teilnehmen": "Partecipare alla sessione",
|
"a.An Durchführung teilnehmen": "Partecipare alla sessione",
|
||||||
"a.Anmelden": "Login",
|
"a.Anmelden": "Login",
|
||||||
"a.Anwesenheit": "Presenza",
|
"a.Anwesenheit": "Presenza",
|
||||||
|
"a.Anwesenheit anschauen": "Visualizza il controllo di presenza",
|
||||||
"a.Anwesenheit Präsenzkurse": "Presenza ai corsi",
|
"a.Anwesenheit Präsenzkurse": "Presenza ai corsi",
|
||||||
"a.Anwesenheitskontrolle Präsenzkurse": "Controllo di presenza ai corsi",
|
"a.Anwesenheitskontrolle Präsenzkurse": "Controllo di presenza ai corsi",
|
||||||
"a.Arbeiten": "Lavori",
|
"a.Arbeiten": "Lavori",
|
||||||
|
|
@ -74,7 +74,6 @@
|
||||||
"a.Datei auswählen": "Selezionare il file",
|
"a.Datei auswählen": "Selezionare il file",
|
||||||
"a.Datei hochladen": "Carica il file",
|
"a.Datei hochladen": "Carica il file",
|
||||||
"a.Datei kann nicht gespeichert werden.": "Impossibile salvare il file.",
|
"a.Datei kann nicht gespeichert werden.": "Impossibile salvare il file.",
|
||||||
"a.Datenschutzerklärung": "Informativa sulla privacy",
|
|
||||||
"a.Datum": "Data",
|
"a.Datum": "Data",
|
||||||
"a.Debit-/Kreditkarte/Twint": "Carta di debito/credito / Twint",
|
"a.Debit-/Kreditkarte/Twint": "Carta di debito/credito / Twint",
|
||||||
"a.Dein Feedback für x y wurde freigegeben.": "Il tuo feedback per {{x}} {{y}} è stato pubblicato.",
|
"a.Dein Feedback für x y wurde freigegeben.": "Il tuo feedback per {{x}} {{y}} è stato pubblicato.",
|
||||||
|
|
@ -83,7 +82,9 @@
|
||||||
"a.Deine Selbsteinschätzung": "La tua autovalutazione",
|
"a.Deine Selbsteinschätzung": "La tua autovalutazione",
|
||||||
"a.Deine Änderungen wurden gespeichert": "Le tue modifiche sono state salvate",
|
"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 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 {course} è {price} IVA esclusa.",
|
"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 anschauen": "Visualizza dettagli",
|
||||||
"a.Details anzeigen": "Mostrare i dettagli",
|
"a.Details anzeigen": "Mostrare i dettagli",
|
||||||
"a.Deutsch": "Tedesco",
|
"a.Deutsch": "Tedesco",
|
||||||
|
|
@ -95,6 +96,7 @@
|
||||||
"a.Du hast alles erledigt.": "Hai fatto tutto.",
|
"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 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 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 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 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.",
|
"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.",
|
||||||
|
|
@ -117,7 +119,6 @@
|
||||||
"a.Ergebnisse bewerten": "Valutare i risultati",
|
"a.Ergebnisse bewerten": "Valutare i risultati",
|
||||||
"a.Ergebnisse teilen": "Condividere i risultati",
|
"a.Ergebnisse teilen": "Condividere i risultati",
|
||||||
"a.Erneut bearbeiten": "Modifica di nuovo",
|
"a.Erneut bearbeiten": "Modifica di nuovo",
|
||||||
"a.Es gelten die {cembraTos} und die {cembraPrivacy} der CembraPay AG.": "Si applicano le {cembraTos} e l'{cembraPrivacy} di CembraPay AG.",
|
|
||||||
"a.Experte": "Esperto",
|
"a.Experte": "Esperto",
|
||||||
"a.Feedback abschliessen": "Completa il feedback",
|
"a.Feedback abschliessen": "Completa il feedback",
|
||||||
"a.Feedback ansehen": "Visualizza il feedback",
|
"a.Feedback ansehen": "Visualizza il feedback",
|
||||||
|
|
@ -213,7 +214,6 @@
|
||||||
"a.Nicht bestanden": "Non superato",
|
"a.Nicht bestanden": "Non superato",
|
||||||
"a.Nicht bewertet": "Non valutato",
|
"a.Nicht bewertet": "Non valutato",
|
||||||
"a.Nichtleben": "Non vita",
|
"a.Nichtleben": "Non vita",
|
||||||
"a.Noch nicht bestätigt": "Non ancora confermato",
|
|
||||||
"a.Note": "Note",
|
"a.Note": "Note",
|
||||||
"a.NUMBER Elemente abgeschlossen": "{NUMBER} elementi completati",
|
"a.NUMBER Elemente abgeschlossen": "{NUMBER} elementi completati",
|
||||||
"a.NUMBER Präsenztage abgeschlossen": "{NUMBER} giorni di presenza completati",
|
"a.NUMBER Präsenztage abgeschlossen": "{NUMBER} giorni di presenza completati",
|
||||||
|
|
@ -225,7 +225,6 @@
|
||||||
"a.Personen, die du begleitest": "Persone che accompagni",
|
"a.Personen, die du begleitest": "Persone che accompagni",
|
||||||
"a.Persönliche Informationen": "Informazioni personali",
|
"a.Persönliche Informationen": "Informazioni personali",
|
||||||
"a.PLZ": "CAP",
|
"a.PLZ": "CAP",
|
||||||
"a.Postleizahl hat das falsche Format": "Il codice postale ha un formato sbagliato",
|
|
||||||
"a.Praxisauftrag": "Lavoro pratico",
|
"a.Praxisauftrag": "Lavoro pratico",
|
||||||
"a.Praxisaufträge anschauen": "Visualizzare gli incarichi pratici",
|
"a.Praxisaufträge anschauen": "Visualizzare gli incarichi pratici",
|
||||||
"a.Praxisbildner": "Formatore pratico",
|
"a.Praxisbildner": "Formatore pratico",
|
||||||
|
|
@ -270,7 +269,6 @@
|
||||||
"a.Teilnehmer": "Partecipanti",
|
"a.Teilnehmer": "Partecipanti",
|
||||||
"a.Teilnehmer im": "I partecipanti al",
|
"a.Teilnehmer im": "I partecipanti al",
|
||||||
"a.Teilnehmer nach Zulassungsprofilen im": "Partecipanti per profilo di ammissione nel",
|
"a.Teilnehmer nach Zulassungsprofilen im": "Partecipanti per profilo di ammissione nel",
|
||||||
"a.Teilnehmer Vorschau": "Anteprima dei partecipanti",
|
|
||||||
"a.Telefonnummer": "Numero di telefono",
|
"a.Telefonnummer": "Numero di telefono",
|
||||||
"a.Telefonnummer hat das falsche Format": "Il numero di telefono ha un formato sbagliato",
|
"a.Telefonnummer hat das falsche Format": "Il numero di telefono ha un formato sbagliato",
|
||||||
"a.Termin": "Data",
|
"a.Termin": "Data",
|
||||||
|
|
@ -310,6 +308,7 @@
|
||||||
"a.Überbetriebliche Kurse": "Corsi interaziendali",
|
"a.Überbetriebliche Kurse": "Corsi interaziendali",
|
||||||
"a.Übergangslösung Innendienst-Mitarbeitende": "Soluzione transitoria per il servizio interno",
|
"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 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": "Panoramica",
|
||||||
"a.Übersicht anschauen": "Vedere la panoramica",
|
"a.Übersicht anschauen": "Vedere la panoramica",
|
||||||
"Abgabe": "Consegna",
|
"Abgabe": "Consegna",
|
||||||
|
|
@ -357,8 +356,6 @@
|
||||||
"Berufsbildner": "Formatore professionale",
|
"Berufsbildner": "Formatore professionale",
|
||||||
"Bestanden": "Superato",
|
"Bestanden": "Superato",
|
||||||
"Bewertung von x y": "Valutazione di {{x}} {{y}}",
|
"Bewertung von x y": "Valutazione di {{x}} {{y}}",
|
||||||
"cembraPrivacyLink": "https://cembrapay.ch/it/privacy",
|
|
||||||
"cembraTosLink": "https://cembrapay.ch/it/terms/CP",
|
|
||||||
"Circle": "Cerchio",
|
"Circle": "Cerchio",
|
||||||
"circlePage.circleContentBoxTitle": "Cosa apprenderai in questo Circle",
|
"circlePage.circleContentBoxTitle": "Cosa apprenderai in questo Circle",
|
||||||
"circlePage.contactExpertButton": "Contattare il/la trainer",
|
"circlePage.contactExpertButton": "Contattare il/la trainer",
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,30 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
|
||||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||||
import type { AttendanceUserStatus } from "@/gql/graphql";
|
import type {
|
||||||
import { graphqlClient } from "@/graphql/client";
|
AttendanceUserStatus,
|
||||||
|
CourseSessionAttendanceCourseObjectType,
|
||||||
|
} from "@/gql/graphql";
|
||||||
import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations";
|
import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations";
|
||||||
import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
|
import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
|
||||||
import { exportAttendance } from "@/services/dashboard";
|
import { exportAttendance } from "@/services/dashboard";
|
||||||
|
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type { DropdownSelectable } from "@/types";
|
|
||||||
import { openDataAsXls } from "@/utils/export";
|
import { openDataAsXls } from "@/utils/export";
|
||||||
import { useMutation } from "@urql/vue";
|
import { useMutation, useQuery } from "@urql/vue";
|
||||||
import dayjs from "dayjs";
|
import { useDateFormat } from "@vueuse/core";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import log from "loglevel";
|
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 attendanceMutation = useMutation(ATTENDANCE_CHECK_MUTATION);
|
||||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
const expertCockpitStore = useExpertCockpitStore();
|
||||||
|
|
||||||
const attendanceCourses = computed(() => {
|
const attendanceCourses = computed(() => {
|
||||||
return courseSessionDetailResult.courseSessionDetail.value?.attendance_courses ?? [];
|
return courseSessionDetailResult.courseSessionDetail.value?.attendance_courses ?? [];
|
||||||
|
|
@ -31,46 +34,18 @@ const courseSessionDetail = computed(() => {
|
||||||
return courseSessionDetailResult.courseSessionDetail.value;
|
return courseSessionDetailResult.courseSessionDetail.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const attendanceCourseCircleId = computed(() => {
|
const currentCourse = computed(() => expertCockpitStore.currentCourse);
|
||||||
const selectedAttendandeCourse = attendanceCourses.value.find(
|
|
||||||
(course) => course.id === state.attendanceCourseSelected.id
|
|
||||||
);
|
|
||||||
return selectedAttendandeCourse?.learning_content?.circle?.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const presenceCoursesDropdownOptions = computed(() => {
|
const userPresence = ref(new Map<string, boolean>());
|
||||||
return attendanceCourses.value.map(
|
const disclaimerConfirmed = ref(false);
|
||||||
(attendanceCourse) =>
|
const attendanceSaved = ref(false);
|
||||||
({
|
|
||||||
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 state = reactive({
|
const { t } = useTranslation();
|
||||||
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 }
|
|
||||||
);
|
|
||||||
|
|
||||||
function resetState() {
|
function resetState() {
|
||||||
state.userPresence = new Map<string, boolean>();
|
userPresence.value = new Map<string, boolean>();
|
||||||
state.disclaimerConfirmed = false;
|
disclaimerConfirmed.value = false;
|
||||||
state.attendanceSaved = false;
|
attendanceSaved.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
|
|
@ -78,78 +53,89 @@ const onSubmit = async () => {
|
||||||
user_id: string;
|
user_id: string;
|
||||||
status: AttendanceUserStatus;
|
status: AttendanceUserStatus;
|
||||||
};
|
};
|
||||||
const attendanceUserList: UserPresence[] = Array.from(state.userPresence.keys()).map(
|
const attendanceUserList: UserPresence[] = Array.from(userPresence.value.keys()).map(
|
||||||
(key) => ({
|
(key) => ({
|
||||||
user_id: key,
|
user_id: key,
|
||||||
status: state.userPresence.get(key) ? "PRESENT" : "ABSENT",
|
status: userPresence.value.get(key) ? "PRESENT" : "ABSENT",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const res = await attendanceMutation.executeMutation({
|
const res = await attendanceMutation.executeMutation({
|
||||||
attendanceCourseId: state.attendanceCourseSelected.id.toString(),
|
attendanceCourseId: (
|
||||||
|
currentCourse.value as CourseSessionAttendanceCourseObjectType
|
||||||
|
).id.toString(),
|
||||||
attendanceUserList: attendanceUserList,
|
attendanceUserList: attendanceUserList,
|
||||||
});
|
});
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
log.error("Could not submit attendance check: ", res.error);
|
log.error("Could not submit attendance check: ", res.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.disclaimerConfirmed = false;
|
disclaimerConfirmed.value = false;
|
||||||
state.attendanceSaved = true;
|
attendanceSaved.value = true;
|
||||||
log.info("Attendance check submitted: ", res);
|
log.info("Attendance check submitted: ", res);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadAttendanceData = async () => {
|
const loadAttendanceData = async () => {
|
||||||
resetState();
|
resetState();
|
||||||
// with changing variables `useQuery` does not seem to work correctly
|
// with changing variables `useQuery` does not seem to work correctly
|
||||||
if (state.attendanceCourseSelected) {
|
if (currentCourse.value) {
|
||||||
const res = await graphqlClient.query(
|
const result = await useQuery({
|
||||||
ATTENDANCE_CHECK_QUERY,
|
query: ATTENDANCE_CHECK_QUERY,
|
||||||
{
|
variables: {
|
||||||
courseSessionId: state.attendanceCourseSelected.id.toString(),
|
courseSessionId: currentCourse.value.id.toString(),
|
||||||
},
|
},
|
||||||
{
|
requestPolicy: "network-only",
|
||||||
requestPolicy: "network-only",
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
const attendanceUserList =
|
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) {
|
for (const user of attendanceUserList) {
|
||||||
if (!user) continue;
|
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) {
|
if (attendanceUserList.length !== 0) {
|
||||||
state.attendanceSaved = true;
|
attendanceSaved.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function editAgain() {
|
function editAgain() {
|
||||||
state.attendanceSaved = false;
|
attendanceSaved.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggleDisclaimer = (newValue: boolean) => {
|
||||||
|
disclaimerConfirmed.value = newValue;
|
||||||
|
};
|
||||||
|
|
||||||
async function exportData() {
|
async function exportData() {
|
||||||
const data = await exportAttendance(
|
const data = await exportAttendance(
|
||||||
{
|
{
|
||||||
courseSessionIds: [Number(courseSession.value.id)],
|
courseSessionIds: [Number(courseSession.value.id)],
|
||||||
circleIds: [Number(attendanceCourseCircleId.value)],
|
circleIds: [Number(currentCourse.value?.learning_content.circle?.id)],
|
||||||
},
|
},
|
||||||
userStore.language
|
userStore.language
|
||||||
);
|
);
|
||||||
openDataAsXls(data.encoded_data, data.file_name);
|
openDataAsXls(data.encoded_data, data.file_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
log.debug("AttendanceCheckPage mounted");
|
log.debug("AttendanceCheckPage mounted");
|
||||||
loadAttendanceData();
|
loadAttendanceData();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
const courseDueDate = computed(() => {
|
||||||
() => state.attendanceCourseSelected,
|
if (currentCourse.value && currentCourse.value.due_date?.start) {
|
||||||
() => {
|
return currentCourse.value.due_date.start;
|
||||||
log.debug("attendanceCourseSelected changed", state.attendanceCourseSelected);
|
}
|
||||||
loadAttendanceData();
|
return "";
|
||||||
},
|
});
|
||||||
{ immediate: true }
|
|
||||||
);
|
const formattedCourseDueDate = computed(() => {
|
||||||
|
if (courseDueDate.value) {
|
||||||
|
return useDateFormat(courseDueDate.value, "D. MMMM YYYY", {
|
||||||
|
locales: "de-CH",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -164,66 +150,50 @@ watch(
|
||||||
<span>{{ $t("general.back") }}</span>
|
<span>{{ $t("general.back") }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between"></div>
|
||||||
<h3 class="pb-4 text-xl font-bold">{{ $t("a.Anwesenheit Präsenzkurse") }}</h3>
|
<section v-if="attendanceCourses.length && currentCourse">
|
||||||
<button
|
<div class="grid grid-cols-[2fr_1fr] justify-between gap-8 bg-white py-6">
|
||||||
v-if="state.attendanceSaved"
|
<div class="col-span-1 flex flex-col gap-2 px-6">
|
||||||
class="flex"
|
<h3 class="pb-1 text-4xl font-bold">{{ $t("a.Präsenzkurs") }}</h3>
|
||||||
data-cy="export-button"
|
<h5>
|
||||||
@click="exportData"
|
{{ t("a.Circle") }} «{{ currentCourse?.learning_content.circle?.title }}»
|
||||||
>
|
</h5>
|
||||||
<it-icon-export></it-icon-export>
|
<h5>{{ formattedCourseDueDate }}</h5>
|
||||||
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
|
</div>
|
||||||
</button>
|
<button
|
||||||
</div>
|
v-if="attendanceSaved"
|
||||||
<section v-if="attendanceCourses.length && state.attendanceCourseSelected">
|
class="col-span-1 mr-4 hidden justify-self-end lg:flex"
|
||||||
<div class="flex flex-row flex-wrap justify-between bg-white p-6">
|
data-cy="export-button"
|
||||||
<ItDropdownSelect
|
@click="exportData"
|
||||||
v-model="state.attendanceCourseSelected"
|
|
||||||
:items="presenceCoursesDropdownOptions ?? []"
|
|
||||||
></ItDropdownSelect>
|
|
||||||
<div
|
|
||||||
v-if="!state.attendanceSaved"
|
|
||||||
class="flex flex-row flex-wrap items-center space-y-2 md:space-y-0"
|
|
||||||
>
|
>
|
||||||
<ItCheckbox
|
<it-icon-export class="fill-current text-blue-900"></it-icon-export>
|
||||||
:checkbox-item="{
|
<span class="ml inline-block text-blue-900">
|
||||||
value: true,
|
{{ $t("a.Als Excel exportieren") }}
|
||||||
checked: state.disclaimerConfirmed,
|
</span>
|
||||||
}"
|
</button>
|
||||||
@toggle="state.disclaimerConfirmed = !state.disclaimerConfirmed"
|
<div
|
||||||
></ItCheckbox>
|
class="col-span-2 flex flex-col items-start gap-4 px-6 lg:gap-6"
|
||||||
<p class="w-64 pr-4 text-sm">
|
:class="attendanceSaved ? 'lg:flex-row lg:items-center' : 'gap-8 lg:gap-8'"
|
||||||
{{
|
>
|
||||||
$t(
|
<AttendanceStatus
|
||||||
"Ich will die Anwesenheit der untenstehenden Personen definitiv bestätigen."
|
class="inline-flex px-6"
|
||||||
)
|
:done="attendanceSaved"
|
||||||
}}
|
:date="courseDueDate"
|
||||||
</p>
|
/>
|
||||||
<button
|
|
||||||
class="btn-primary"
|
|
||||||
:disabled="!state.disclaimerConfirmed"
|
|
||||||
@click="onSubmit"
|
|
||||||
>
|
|
||||||
{{ $t("Anwesenheit bestätigen") }}
|
|
||||||
</button>
|
|
||||||
</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">
|
<AttendanceCheck
|
||||||
<div
|
:attendance-saved="attendanceSaved"
|
||||||
v-for="(csu, index) in courseSessionDetailResult.filterMembers()"
|
:disclaimer-confirmed="disclaimerConfirmed"
|
||||||
:key="csu.user_id"
|
@reopen="editAgain"
|
||||||
>
|
@toggle="toggleDisclaimer"
|
||||||
|
@confirm="onSubmit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-2 border-t border-gray-500 px-6">
|
||||||
<ItPersonRow
|
<ItPersonRow
|
||||||
|
v-for="(csu, index) in courseSessionDetailResult.filterMembers()"
|
||||||
|
:key="csu.user_id"
|
||||||
:name="`${csu.first_name} ${csu.last_name}`"
|
:name="`${csu.first_name} ${csu.last_name}`"
|
||||||
:avatar-url="csu.avatar_url"
|
:avatar-url="csu.avatar_url"
|
||||||
:class="0 === index ? 'border-none' : ''"
|
:class="0 === index ? 'border-none' : ''"
|
||||||
|
|
@ -233,16 +203,13 @@ watch(
|
||||||
>
|
>
|
||||||
<template #leading>
|
<template #leading>
|
||||||
<ItCheckbox
|
<ItCheckbox
|
||||||
:disabled="state.attendanceSaved"
|
:disabled="attendanceSaved"
|
||||||
:checkbox-item="{
|
:checkbox-item="{
|
||||||
value: true,
|
value: true,
|
||||||
checked: state.userPresence.get(csu.user_id) as boolean,
|
checked: userPresence.get(csu.user_id) as boolean,
|
||||||
}"
|
}"
|
||||||
@toggle="
|
@toggle="
|
||||||
state.userPresence.set(
|
userPresence.set(csu.user_id, !userPresence.get(csu.user_id))
|
||||||
csu.user_id,
|
|
||||||
!state.userPresence.get(csu.user_id)
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
></ItCheckbox>
|
></ItCheckbox>
|
||||||
</template>
|
</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>
|
<template>
|
||||||
<div class="bg-gray-200">
|
<div class="bg-gray-200">
|
||||||
<div v-if="courseSession" class="container-large">
|
<div v-if="courseSession" class="container-large">
|
||||||
<nav class="py-4 pb-4">
|
<main class="py-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>
|
|
||||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
<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
|
<ItDropdownSelect
|
||||||
:model-value="cockpitStore.currentCircle"
|
: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"
|
:items="cockpitStore.circles"
|
||||||
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
||||||
></ItDropdownSelect>
|
></ItDropdownSelect>
|
||||||
|
|
|
||||||
|
|
@ -56,17 +56,6 @@ const items: SubNavEntry[] = [
|
||||||
route: selfEvaluationRoute,
|
route: selfEvaluationRoute,
|
||||||
},
|
},
|
||||||
{ id: 3, name: t("a.Handlungskompetenzen"), route: competencesRoute },
|
{ id: 3, name: t("a.Handlungskompetenzen"), route: competencesRoute },
|
||||||
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: "MS Teams",
|
|
||||||
route: "https://iterativ.ch",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
name: "Vorschau Teilnehmer",
|
|
||||||
route: "https://iterativ.ch",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,13 @@ import { addToHistory, setLastNavigationWasPush } from "@/router/history";
|
||||||
import { onboardingRedirect } from "@/router/onboarding";
|
import { onboardingRedirect } from "@/router/onboarding";
|
||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
import {
|
import {
|
||||||
|
ATTENDANCE_ROUTE,
|
||||||
CERTIFICATES_ROUTE,
|
CERTIFICATES_ROUTE,
|
||||||
|
COCKPIT_ROUTE,
|
||||||
COMPETENCE_ROUTE,
|
COMPETENCE_ROUTE,
|
||||||
COMPETENCES_ROUTE,
|
COMPETENCES_ROUTE,
|
||||||
|
DOCUMENTS_ROUTE,
|
||||||
|
PERSONAL_PROFILE_ROUTE,
|
||||||
SELF_EVALUATION_ROUTE,
|
SELF_EVALUATION_ROUTE,
|
||||||
SETTINGS_ROUTE,
|
SETTINGS_ROUTE,
|
||||||
} from "./names";
|
} from "./names";
|
||||||
|
|
@ -86,88 +90,269 @@ const router = createRouter({
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/course/:courseSlug/media",
|
path: "/course/:courseSlug",
|
||||||
props: true,
|
props: true,
|
||||||
component: () => import("@/pages/mediaLibrary/MediaLibraryParentPage.vue"),
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "media",
|
||||||
component: () => import("@/pages/mediaLibrary/MediaLibraryIndexPage.vue"),
|
component: () => import("@/pages/mediaLibrary/MediaLibraryParentPage.vue"),
|
||||||
|
props: true,
|
||||||
|
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: ":categorySlug",
|
path: "competence",
|
||||||
props: true,
|
component: () => import("@/pages/competence/CompetenceParentPage.vue"),
|
||||||
component: () => import("@/pages/mediaLibrary/MediaLibraryCategoryPage.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: ":categorySlug/:contentSlug",
|
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: "profile/:userId",
|
||||||
|
component: () => import("@/pages/userProfile/UserProfilePage.vue"),
|
||||||
props: true,
|
props: true,
|
||||||
component: () => import("@/pages/mediaLibrary/MediaLibraryContentPage.vue"),
|
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/assignmentEvaluation/AssignmentEvaluationPage.vue"),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "cockpit",
|
||||||
|
name: "cockpit",
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/cockpit/cockpitPage/CockpitExpertParentPage.vue"),
|
||||||
|
props: true,
|
||||||
|
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/competence",
|
|
||||||
props: true,
|
|
||||||
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: "/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",
|
path: "/lernbegleitung/:courseId/invitation/:invitationId",
|
||||||
component: () => import("@/pages/learningMentor/InvitationAcceptPage.vue"),
|
component: () => import("@/pages/learningMentor/InvitationAcceptPage.vue"),
|
||||||
|
|
@ -178,158 +363,6 @@ const router = createRouter({
|
||||||
public: true,
|
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",
|
path: "/statistic/:courseSlug",
|
||||||
props: true,
|
props: true,
|
||||||
|
|
@ -399,7 +432,7 @@ const router = createRouter({
|
||||||
{
|
{
|
||||||
path: "/profile",
|
path: "/profile",
|
||||||
component: () => import("@/pages/personalProfile/PersonalProfilePage.vue"),
|
component: () => import("@/pages/personalProfile/PersonalProfilePage.vue"),
|
||||||
name: "personalProfile",
|
name: PERSONAL_PROFILE_ROUTE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,7 @@ export const CERTIFICATES_ROUTE = "certificates";
|
||||||
export const SELF_EVALUATION_ROUTE = "selfEvaluationAndFeedback";
|
export const SELF_EVALUATION_ROUTE = "selfEvaluationAndFeedback";
|
||||||
export const COMPETENCES_ROUTE = "competences";
|
export const COMPETENCES_ROUTE = "competences";
|
||||||
export const SETTINGS_ROUTE = "settings";
|
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";
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,14 @@
|
||||||
import { useCourseData } from "@/composables";
|
import { useCourseData, useCourseSessionDetailQuery } from "@/composables";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type { CircleLight, CourseSessionUser, ExpertSessionUser } from "@/types";
|
import type { CircleLight, CourseSessionUser, ExpertSessionUser } from "@/types";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
type CircleExpertCockpit = CircleLight & {
|
type CircleExpertCockpit = CircleLight & {
|
||||||
name: string;
|
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(
|
async function courseCircles(
|
||||||
courseSlug: string,
|
courseSlug: string,
|
||||||
currentCourseSessionUser: CourseSessionUser | undefined
|
currentCourseSessionUser: CourseSessionUser | undefined
|
||||||
|
|
@ -74,3 +38,51 @@ async function courseCircles(
|
||||||
|
|
||||||
return [];
|
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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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,20 +1,24 @@
|
||||||
import {EXPERT_COCKPIT_URL, login} from "./helpers";
|
import { EXPERT_COCKPIT_URL, login } from "./helpers";
|
||||||
|
|
||||||
describe("settings.cy.js", () => {
|
describe("settings.cy.js", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.manageCommand("cypress_reset");
|
cy.manageCommand("cypress_reset");
|
||||||
|
cy.intercept("/server/graphql").as("graphql");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("with circle documents enabled", () => {
|
describe("with circle documents enabled", () => {
|
||||||
it("student can see circle documents", () => {
|
it("student can see circle documents", () => {
|
||||||
login("test-student1@example.com", "test");
|
login("test-student1@example.com", "test");
|
||||||
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
||||||
|
cy.wait(["@graphql", "@graphql"]);
|
||||||
cy.get('[data-cy="circle-document-section"]').should("exist");
|
cy.get('[data-cy="circle-document-section"]').should("exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("trainer can see circle documents", () => {
|
it("trainer can see circle documents", () => {
|
||||||
login("test-trainer1@example.com", "test");
|
login("test-trainer1@example.com", "test");
|
||||||
|
|
||||||
cy.visit(EXPERT_COCKPIT_URL);
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
|
cy.wait(["@graphql", "@graphql"]);
|
||||||
cy.get('[data-cy="circle-documents"]').should("exist");
|
cy.get('[data-cy="circle-documents"]').should("exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -27,6 +31,7 @@ describe("settings.cy.js", () => {
|
||||||
it("student cannot see circle documents", () => {
|
it("student cannot see circle documents", () => {
|
||||||
login("test-student1@example.com", "test");
|
login("test-student1@example.com", "test");
|
||||||
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
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-title"]').should("contain", "Fahrzeug");
|
||||||
cy.get('[data-cy="circle-document-section"]').should("not.exist");
|
cy.get('[data-cy="circle-document-section"]').should("not.exist");
|
||||||
});
|
});
|
||||||
|
|
@ -34,6 +39,7 @@ describe("settings.cy.js", () => {
|
||||||
it("trainer cannot see circle documents", () => {
|
it("trainer cannot see circle documents", () => {
|
||||||
login("test-trainer1@example.com", "test");
|
login("test-trainer1@example.com", "test");
|
||||||
cy.visit(EXPERT_COCKPIT_URL);
|
cy.visit(EXPERT_COCKPIT_URL);
|
||||||
|
cy.wait(["@graphql", "@graphql"]);
|
||||||
cy.get('[data-cy="circle-documents"]').should("not.exist");
|
cy.get('[data-cy="circle-documents"]').should("not.exist");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue