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 "";
|
||||
};
|
||||
|
||||
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">
|
||||
<div class="flex h-full items-center border-r border-slate-500">
|
||||
<router-link to="/" class="flex items-center pr-3">
|
||||
<it-icon-arrow-left />
|
||||
<it-icon-arrow-left class="fill-current text-slate-500" />
|
||||
<span class="hidden text-slate-500 lg:inline">
|
||||
{{ t("a.Dashboard") }}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ onMounted(() => {
|
|||
<CourseSessionNavigation />
|
||||
</div>
|
||||
|
||||
<div class="flex items-stretch justify-start space-x-8">
|
||||
<div class="flex items-stretch justify-start gap-2 lg:gap-4">
|
||||
<router-link
|
||||
v-if="hasMediaLibraryMenu"
|
||||
:to="
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||
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 type { User } from "@/stores/user";
|
||||
import type { CourseSession } from "@/types";
|
||||
|
|
@ -54,13 +54,16 @@ const mentorTabTitle = computed(() =>
|
|||
const settingsRoute = {
|
||||
name: SETTINGS_ROUTE,
|
||||
};
|
||||
const profileRoute = {
|
||||
name: PERSONAL_PROFILE_ROUTE,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ItFullScreenModal :show="show" @closemodal="emit('closemodal')">
|
||||
<div>
|
||||
<div class="-mx-4">
|
||||
<div>
|
||||
<div v-if="user?.loggedIn" class="-mx-4 border-b px-8 pb-4">
|
||||
<div v-if="user?.loggedIn" class="border-b px-8 pb-4">
|
||||
<div class="-ml-4 flex">
|
||||
<div v-if="user?.avatar_url">
|
||||
<img
|
||||
|
|
@ -71,16 +74,27 @@ const settingsRoute = {
|
|||
</div>
|
||||
<div class="ml-6">
|
||||
<h3>{{ user?.first_name }} {{ user?.last_name }}</h3>
|
||||
|
||||
<div class="mb-3 text-sm text-gray-800">{{ user.email }}</div>
|
||||
<router-link
|
||||
:to="profileRoute"
|
||||
class="underline"
|
||||
@click="emit('closemodal')"
|
||||
>
|
||||
{{ $t("a.Profil anzeigen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="courseSession" class="mt-6 border-b">
|
||||
<h4 class="px-4 text-sm text-gray-900">{{ courseSession.course.title }}</h4>
|
||||
<ul class="mt-6 flex flex-col">
|
||||
<li v-if="hasCockpitMenu" class="mb-6">
|
||||
<div v-if="courseSession" class="border-b px-4 py-6">
|
||||
<h4 class="mb-4 px-4 text-sm text-gray-900">
|
||||
{{ courseSession.course.title }}
|
||||
</h4>
|
||||
<ul class="flex flex-col gap-2">
|
||||
<li v-if="hasCockpitMenu">
|
||||
<router-link
|
||||
class="w-full px-4 py-2"
|
||||
class="block w-full px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
data-cy="navigation-mobile-cockpit-link"
|
||||
:to="getCockpitUrl(courseSession.course.slug)"
|
||||
|
|
@ -89,9 +103,9 @@ const settingsRoute = {
|
|||
{{ $t("cockpit.title") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="hasPreviewMenu" class="mb-2 flex">
|
||||
<li v-if="hasPreviewMenu">
|
||||
<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"
|
||||
data-cy="navigation-mobile-preview-link"
|
||||
:to="getLearningPathUrl(courseSession.course.slug)"
|
||||
|
|
@ -100,9 +114,9 @@ const settingsRoute = {
|
|||
{{ $t("a.Vorschau Teilnehmer") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="hasLearningPathMenu" class="mb-2 flex">
|
||||
<li v-if="hasLearningPathMenu">
|
||||
<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"
|
||||
data-cy="navigation-mobile-learning-path-link"
|
||||
:to="getLearningPathUrl(courseSession.course.slug)"
|
||||
|
|
@ -111,9 +125,9 @@ const settingsRoute = {
|
|||
{{ $t("general.learningPath") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="hasCompetenceNaviMenu" class="mb-2 flex">
|
||||
<li v-if="hasCompetenceNaviMenu">
|
||||
<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"
|
||||
data-cy="navigation-mobile-competence-profile-link"
|
||||
:to="getCompetenceNaviUrl(courseSession.course.slug)"
|
||||
|
|
@ -122,9 +136,9 @@ const settingsRoute = {
|
|||
{{ $t("competences.title") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="hasLearningMentor" class="mb-2 flex">
|
||||
<li v-if="hasLearningMentor">
|
||||
<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"
|
||||
data-cy="navigation-mobile-mentor-link"
|
||||
:to="getLearningMentorUrl(courseSession.course.slug)"
|
||||
|
|
@ -147,9 +161,9 @@ const settingsRoute = {
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-6 border-b">
|
||||
<ul>
|
||||
<li v-if="courseSession && hasMediaLibraryMenu" class="mb-6 flex">
|
||||
<div v-if="courseSession" class="border-b px-4">
|
||||
<ul class="flex flex-col gap-2 py-6">
|
||||
<li v-if="courseSession && hasMediaLibraryMenu" class="flex">
|
||||
<router-link
|
||||
data-cy="medialibrary-link"
|
||||
class="flex w-full items-center gap-2 px-4 py-2"
|
||||
|
|
@ -161,7 +175,7 @@ const settingsRoute = {
|
|||
{{ $t("a.Mediathek") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="courseSession && hasMediaLibraryMenu" class="mb-6 flex">
|
||||
<li v-if="courseSession && hasMediaLibraryMenu" class="flex">
|
||||
<router-link
|
||||
data-cy="calendar-link"
|
||||
class="flex w-full items-center gap-2 px-4 py-2"
|
||||
|
|
@ -176,25 +190,28 @@ const settingsRoute = {
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<router-link
|
||||
:to="settingsRoute"
|
||||
class="mt-6 flex w-full items-center gap-2 px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
v-if="user?.loggedIn"
|
||||
type="button"
|
||||
>
|
||||
<it-icon-settings />
|
||||
{{ $t("a.Einstellungen") }}
|
||||
</router-link>
|
||||
<button
|
||||
v-if="user?.loggedIn"
|
||||
type="button"
|
||||
class="mt-6 flex items-center px-4 py-2"
|
||||
@click="$emit('logout')"
|
||||
>
|
||||
<it-icon-logout class="inline-block" />
|
||||
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
||||
</button>
|
||||
|
||||
<div class="flex flex-col gap-2 px-4 py-6">
|
||||
<router-link
|
||||
v-if="user?.loggedIn"
|
||||
:to="settingsRoute"
|
||||
class="flex w-full items-center gap-2 px-4 py-2"
|
||||
active-class="bg-gray-200 text-blue-900 font-bold"
|
||||
type="button"
|
||||
>
|
||||
<it-icon-settings />
|
||||
{{ $t("a.Einstellungen") }}
|
||||
</router-link>
|
||||
<button
|
||||
v-if="user?.loggedIn"
|
||||
type="button"
|
||||
class="flex items-center px-4 py-2"
|
||||
@click="$emit('logout')"
|
||||
>
|
||||
<it-icon-logout class="inline-block" />
|
||||
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,14 +36,13 @@ const showCourseSessionMenu = computed(
|
|||
v-if="hasSessionTitle"
|
||||
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">
|
||||
<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" />
|
||||
</PopoverButton>
|
||||
<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>
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
|
||||
<div v-else data-cy="current-course-session-title" class="text-bold">
|
||||
{{ selectedCourseSessionTitle }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const isExternalLink = computed(() => {
|
|||
target="_blank"
|
||||
>
|
||||
<slot />
|
||||
<it-icon-external-link class="w-6" />
|
||||
<it-icon-external-link class="h-6 w-6" />
|
||||
</a>
|
||||
<!-- make `:to` explicit -->
|
||||
<router-link
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ interface Props {
|
|||
items?: DropdownSelectable[];
|
||||
borderless?: boolean;
|
||||
placeholderText?: string | null;
|
||||
asHeading?: boolean; // style the dropdown to be used as a page heading
|
||||
typeName?: string; // to display the type of the selected item, e.g. `Circle: Fahrzeug` instead of `Fahrzeug`
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
@ -24,6 +26,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
},
|
||||
items: () => [],
|
||||
placeholderText: null,
|
||||
asHeading: false,
|
||||
typeName: "",
|
||||
});
|
||||
|
||||
const dropdownSelected = computed<DropdownSelectable>({
|
||||
|
|
@ -36,26 +40,34 @@ const dropdownSelected = computed<DropdownSelectable>({
|
|||
<Listbox v-model="dropdownSelected" as="div">
|
||||
<div class="relative w-full">
|
||||
<ListboxButton
|
||||
class="relative flex w-full cursor-default flex-row items-center bg-white py-3 pl-5 pr-10 text-left"
|
||||
:class="{
|
||||
border: !props.borderless,
|
||||
'font-bold': !props.borderless,
|
||||
}"
|
||||
:class="[
|
||||
{
|
||||
border: !borderless && !asHeading,
|
||||
'font-bold': !borderless,
|
||||
},
|
||||
asHeading
|
||||
? 'group flex w-full items-center gap-1 rounded-md bg-transparent text-base focus:outline-none'
|
||||
: 'relative flex w-full cursor-default flex-row items-center bg-white py-3 pl-5 pr-10 text-left',
|
||||
]"
|
||||
data-cy="dropdown-select"
|
||||
>
|
||||
<span v-if="dropdownSelected.iconName" class="mr-4">
|
||||
<component :is="dropdownSelected.iconName"></component>
|
||||
</span>
|
||||
<span class="block truncate">
|
||||
{{ dropdownSelected.name }}
|
||||
<span :class="[asHeading ? 'h-11 text-4xl' : '']" class="block truncate">
|
||||
{{ typeName }} {{ dropdownSelected.name }}
|
||||
<span v-if="placeholderText && !dropdownSelected.name" class="text-gray-900">
|
||||
{{ placeholderText }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||
class="pointer-events-none flex items-center pr-2"
|
||||
:class="asHeading ? '' : 'absolute inset-y-0 right-0'"
|
||||
>
|
||||
<it-icon-arrow-down class="h-5 w-5" aria-hidden="true" />
|
||||
<it-icon-arrow-down
|
||||
:class="asHeading ? 'h-12 w-12' : 'h-5 w-5'"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
|
|
@ -79,7 +91,7 @@ const dropdownSelected = computed<DropdownSelectable>({
|
|||
active ? 'bg-blue-900 text-white' : 'text-black',
|
||||
'relative cursor-default select-none py-2 pl-3 pr-9',
|
||||
]"
|
||||
class="flex flex-row items-center"
|
||||
class="group flex flex-row items-center"
|
||||
:data-cy="`dropdown-select-option-${item.name}`"
|
||||
>
|
||||
<span v-if="item.iconName" class="mr-4">
|
||||
|
|
@ -98,7 +110,11 @@ const dropdownSelected = computed<DropdownSelectable>({
|
|||
v-if="dropdownSelected"
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-4 text-blue-900"
|
||||
>
|
||||
<it-icon-check v-if="selected" class="h-5 w-5" aria-hidden="true" />
|
||||
<it-icon-check
|
||||
v-if="selected"
|
||||
class="h-5 w-5 fill-current group-hover:text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"a.An Durchführung teilnehmen": "An Durchführung teilnehmen",
|
||||
"a.Anmelden": "Anmelden",
|
||||
"a.Anwesenheit": "Anwesenheit",
|
||||
"a.Anwesenheit anschauen": "Anwesenheit anschauen",
|
||||
"a.Anwesenheit Präsenzkurse": "Anwesenheit Präsenzkurse",
|
||||
"a.Anwesenheitskontrolle Präsenzkurse": "Anwesenheitskontrolle Präsenzkurse",
|
||||
"a.Arbeiten": "Arbeiten",
|
||||
|
|
@ -84,6 +85,8 @@
|
|||
"a.Deine Änderungen wurden gespeichert": "Deine Änderungen wurden gespeichert",
|
||||
"a.Der Lehrgang und die Prüfung zum Erwerb des Verbandszertifikats als Versicherungsvermittler/-in.": "Der Lehrgang und die Prüfung zum Erwerb des Verbandszertifikats als Versicherungsvermittler/-in.",
|
||||
"a.Der Preis für den Lehrgang {course} beträgt {price}.": "Der Preis für den Lehrgang {course} beträgt {price} exkl. MWSt.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._one": "Der Präsenzkurs findet in einem Tag statt.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._other": "Der Präsenzkurs findet in {{count}} Tagen statt.",
|
||||
"a.Details anschauen": "Details anschauen",
|
||||
"a.Details anzeigen": "Details anzeigen",
|
||||
"a.Deutsch": "Deutsch",
|
||||
|
|
@ -95,6 +98,7 @@
|
|||
"a.Du hast alles erledigt.": "Du hast alles erledigt.",
|
||||
"a.Du hast deine Fremdeinschätzung freigegeben": "Du hast deine Fremdeinschätzung freigegeben.",
|
||||
"a.Du hast deine Selbsteinschätzung erfolgreich mit FULL_NAME geteilt.": "Du hast deine Selbsteinschätzung erfolgreich mit {{FULL_NAME}} geteilt.",
|
||||
"a.Du hast die Anwesenheit bestätigt.": "Du hast die Anwesenheit bestätigt.",
|
||||
"a.Du hast die Einladung von {name} erfolgreich akzeptiert.": "Du hast die Einladung von {name} erfolgreich akzeptiert.",
|
||||
"a.Du hast erfolgreich ein Konto für EMAIL erstellt.": "Du hast erfolgreich ein Konto für {{email}} erstellt.",
|
||||
"a.Du kannst deine Selbsteinschätzung mit deiner Lernbegleitung teilen, damit sie eine Fremdeinschätzung vornimmt.": "Du kannst deine Selbsteinschätzung mit deiner Lernbegleitung teilen, damit sie eine Fremdeinschätzung vornimmt.",
|
||||
|
|
@ -310,6 +314,7 @@
|
|||
"a.Überbetriebliche Kurse": "Überbetriebliche Kurse",
|
||||
"a.Übergangslösung Innendienst-Mitarbeitende": "Übergangslösung Innendienst-Mitarbeitende",
|
||||
"a.Überprüfe deine Eingaben unten und gib anschliessend deine Fremdeinschätzung für FEEDBACK_REQUESTER frei": "Überprüfe deine Eingaben unten und gib anschliessend deine Fremdeinschätzung für {{FEEDBACK_REQUESTER}} frei.",
|
||||
"a.Überprüfe jetzt die Anwesenheit.": "Überprüfe jetzt die Anwesenheit.",
|
||||
"a.Übersicht": "Übersicht",
|
||||
"a.Übersicht anschauen": "Übersicht anschauen",
|
||||
"Abgabe": "Abgabe",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"a.An Durchführung teilnehmen": "Participer à la session",
|
||||
"a.Anmelden": "Connexion",
|
||||
"a.Anwesenheit": "Présence",
|
||||
"a.Anwesenheit anschauen": "Voir le contrôle de présence",
|
||||
"a.Anwesenheit Präsenzkurse": "Présence aux cours",
|
||||
"a.Anwesenheitskontrolle Präsenzkurse": "Contrôle de présence aux cours",
|
||||
"a.Arbeiten": "Travaux",
|
||||
|
|
@ -84,6 +85,8 @@
|
|||
"a.Deine Änderungen wurden gespeichert": "Tes modifications ont été enregistrées",
|
||||
"a.Der Lehrgang und die Prüfung zum Erwerb des Verbandszertifikats als Versicherungsvermittler/-in.": "Le cours et l'examen pour obtenir le certificat d'association comme courtier/agent d'assurance.",
|
||||
"a.Der Preis für den Lehrgang {course} beträgt {price}.": "Le prix de la formation {course} est de {price} hors TVA.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._one": "Le cours de présence se déroule en une jour.",
|
||||
"a.Der Präsenzkurs findet in {{days}} Tagen statt._other": "Le cours de présence se déroule en {{count}} jours.",
|
||||
"a.Details anschauen": "Voir les détails",
|
||||
"a.Details anzeigen": "Afficher les détails",
|
||||
"a.Deutsch": "Allemand",
|
||||
|
|
@ -95,6 +98,7 @@
|
|||
"a.Du hast alles erledigt.": "Tu as tout fini.",
|
||||
"a.Du hast deine Fremdeinschätzung freigegeben": "Tu as autorisé ton évaluation externe.",
|
||||
"a.Du hast deine Selbsteinschätzung erfolgreich mit FULL_NAME geteilt.": "Tu as partagé avec succès ton auto-évaluation avec {{FULL_NAME}}.",
|
||||
"a.Du hast die Anwesenheit bestätigt.": "Tu as confirmé la présence.",
|
||||
"a.Du hast die Einladung von {name} erfolgreich akzeptiert.": "Tu as accepté avec succès l'invitation de {name}.",
|
||||
"a.Du hast erfolgreich ein Konto für EMAIL erstellt.": "Vous avez créé un compte avec succès pour {{email}}.",
|
||||
"a.Du kannst deine Selbsteinschätzung mit deiner Lernbegleitung teilen, damit sie eine Fremdeinschätzung vornimmt.": "Tu peux partager ton auto-évaluation avec ton accompagnateur d'apprentissage afin qu'il puisse effectuer une évaluation externe.",
|
||||
|
|
@ -310,6 +314,7 @@
|
|||
"a.Überbetriebliche Kurse": "Cours interentreprises",
|
||||
"a.Übergangslösung Innendienst-Mitarbeitende": "Solution transitoire pour le service interne",
|
||||
"a.Überprüfe deine Eingaben unten und gib anschliessend deine Fremdeinschätzung für FEEDBACK_REQUESTER frei": "Vérifie tes saisies ci-dessous et libère ensuite ton évaluation externe pour {{FEEDBACK_REQUESTER}}.",
|
||||
"a.Überprüfe jetzt die Anwesenheit.": "Vérifie maintenant la présence.",
|
||||
"a.Übersicht": "Aperçu",
|
||||
"a.Übersicht anschauen": "Consulter l'aperçu",
|
||||
"Abgabe": "Remise",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
"a.Abgabetermin": "Termine di consegna",
|
||||
"a.Abgezogene Punkte": "Punti detratti",
|
||||
"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 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.",
|
||||
|
|
@ -34,6 +33,7 @@
|
|||
"a.An Durchführung teilnehmen": "Partecipare alla sessione",
|
||||
"a.Anmelden": "Login",
|
||||
"a.Anwesenheit": "Presenza",
|
||||
"a.Anwesenheit anschauen": "Visualizza il controllo di presenza",
|
||||
"a.Anwesenheit Präsenzkurse": "Presenza ai corsi",
|
||||
"a.Anwesenheitskontrolle Präsenzkurse": "Controllo di presenza ai corsi",
|
||||
"a.Arbeiten": "Lavori",
|
||||
|
|
@ -74,7 +74,6 @@
|
|||
"a.Datei auswählen": "Selezionare il file",
|
||||
"a.Datei hochladen": "Carica il file",
|
||||
"a.Datei kann nicht gespeichert werden.": "Impossibile salvare il file.",
|
||||
"a.Datenschutzerklärung": "Informativa sulla privacy",
|
||||
"a.Datum": "Data",
|
||||
"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.",
|
||||
|
|
@ -83,7 +82,9 @@
|
|||
"a.Deine Selbsteinschätzung": "La tua autovalutazione",
|
||||
"a.Deine Änderungen wurden gespeichert": "Le tue modifiche sono state salvate",
|
||||
"a.Der Lehrgang und die Prüfung zum Erwerb des Verbandszertifikats als Versicherungsvermittler/-in.": "Il corso e l'esame per ottenere il certificato di associazione come intermediario/agente di assicurazione.",
|
||||
"a.Der Preis für den Lehrgang {course} beträgt {price}.": "Il prezzo del {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 anzeigen": "Mostrare i dettagli",
|
||||
"a.Deutsch": "Tedesco",
|
||||
|
|
@ -95,6 +96,7 @@
|
|||
"a.Du hast alles erledigt.": "Hai fatto tutto.",
|
||||
"a.Du hast deine Fremdeinschätzung freigegeben": "Hai rilasciato la tua valutazione esterna.",
|
||||
"a.Du hast deine Selbsteinschätzung erfolgreich mit FULL_NAME geteilt.": "Hai condiviso con successo la tua autovalutazione con {{FULL_NAME}}.",
|
||||
"a.Du hast die Anwesenheit bestätigt.": "Hai confermato la presenza.",
|
||||
"a.Du hast die Einladung von {name} erfolgreich akzeptiert.": "Hai accettato con successo l'invito di {name}.",
|
||||
"a.Du hast erfolgreich ein Konto für EMAIL erstellt.": "Hai creato con successo un account per {{email}}.",
|
||||
"a.Du kannst deine Selbsteinschätzung mit deiner Lernbegleitung teilen, damit sie eine Fremdeinschätzung vornimmt.": "Puoi condividere la tua autovalutazione con il tuo tutor didattico affinché possa effettuare una valutazione esterna.",
|
||||
|
|
@ -117,7 +119,6 @@
|
|||
"a.Ergebnisse bewerten": "Valutare i risultati",
|
||||
"a.Ergebnisse teilen": "Condividere i risultati",
|
||||
"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.Feedback abschliessen": "Completa il feedback",
|
||||
"a.Feedback ansehen": "Visualizza il feedback",
|
||||
|
|
@ -213,7 +214,6 @@
|
|||
"a.Nicht bestanden": "Non superato",
|
||||
"a.Nicht bewertet": "Non valutato",
|
||||
"a.Nichtleben": "Non vita",
|
||||
"a.Noch nicht bestätigt": "Non ancora confermato",
|
||||
"a.Note": "Note",
|
||||
"a.NUMBER Elemente abgeschlossen": "{NUMBER} elementi completati",
|
||||
"a.NUMBER Präsenztage abgeschlossen": "{NUMBER} giorni di presenza completati",
|
||||
|
|
@ -225,7 +225,6 @@
|
|||
"a.Personen, die du begleitest": "Persone che accompagni",
|
||||
"a.Persönliche Informationen": "Informazioni personali",
|
||||
"a.PLZ": "CAP",
|
||||
"a.Postleizahl hat das falsche Format": "Il codice postale ha un formato sbagliato",
|
||||
"a.Praxisauftrag": "Lavoro pratico",
|
||||
"a.Praxisaufträge anschauen": "Visualizzare gli incarichi pratici",
|
||||
"a.Praxisbildner": "Formatore pratico",
|
||||
|
|
@ -270,7 +269,6 @@
|
|||
"a.Teilnehmer": "Partecipanti",
|
||||
"a.Teilnehmer im": "I partecipanti al",
|
||||
"a.Teilnehmer nach Zulassungsprofilen im": "Partecipanti per profilo di ammissione nel",
|
||||
"a.Teilnehmer Vorschau": "Anteprima dei partecipanti",
|
||||
"a.Telefonnummer": "Numero di telefono",
|
||||
"a.Telefonnummer hat das falsche Format": "Il numero di telefono ha un formato sbagliato",
|
||||
"a.Termin": "Data",
|
||||
|
|
@ -310,6 +308,7 @@
|
|||
"a.Überbetriebliche Kurse": "Corsi interaziendali",
|
||||
"a.Übergangslösung Innendienst-Mitarbeitende": "Soluzione transitoria per il servizio interno",
|
||||
"a.Überprüfe deine Eingaben unten und gib anschliessend deine Fremdeinschätzung für FEEDBACK_REQUESTER frei": "Controlla le tue voci qui sotto e poi rilascia la tua valutazione esterna per {{FEEDBACK_REQUESTER}}.",
|
||||
"a.Überprüfe jetzt die Anwesenheit.": "Controllare la presenza ora.",
|
||||
"a.Übersicht": "Panoramica",
|
||||
"a.Übersicht anschauen": "Vedere la panoramica",
|
||||
"Abgabe": "Consegna",
|
||||
|
|
@ -357,8 +356,6 @@
|
|||
"Berufsbildner": "Formatore professionale",
|
||||
"Bestanden": "Superato",
|
||||
"Bewertung von x y": "Valutazione di {{x}} {{y}}",
|
||||
"cembraPrivacyLink": "https://cembrapay.ch/it/privacy",
|
||||
"cembraTosLink": "https://cembrapay.ch/it/terms/CP",
|
||||
"Circle": "Cerchio",
|
||||
"circlePage.circleContentBoxTitle": "Cosa apprenderai in questo Circle",
|
||||
"circlePage.contactExpertButton": "Contattare il/la trainer",
|
||||
|
|
|
|||
|
|
@ -1,27 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import type { AttendanceUserStatus } from "@/gql/graphql";
|
||||
import { graphqlClient } from "@/graphql/client";
|
||||
import type {
|
||||
AttendanceUserStatus,
|
||||
CourseSessionAttendanceCourseObjectType,
|
||||
} from "@/gql/graphql";
|
||||
import { ATTENDANCE_CHECK_MUTATION } from "@/graphql/mutations";
|
||||
import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
|
||||
import { exportAttendance } from "@/services/dashboard";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { DropdownSelectable } from "@/types";
|
||||
import { openDataAsXls } from "@/utils/export";
|
||||
import { useMutation } from "@urql/vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useMutation, useQuery } from "@urql/vue";
|
||||
import { useDateFormat } from "@vueuse/core";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import log from "loglevel";
|
||||
import { computed, onMounted, reactive, watch } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import AttendanceCheck from "../cockpitPage/AttendanceCheck.vue";
|
||||
import AttendanceStatus from "../cockpitPage/AttendanceStatus.vue";
|
||||
|
||||
const { t } = useTranslation();
|
||||
const attendanceMutation = useMutation(ATTENDANCE_CHECK_MUTATION);
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
const userStore = useUserStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const expertCockpitStore = useExpertCockpitStore();
|
||||
|
||||
const attendanceCourses = computed(() => {
|
||||
return courseSessionDetailResult.courseSessionDetail.value?.attendance_courses ?? [];
|
||||
|
|
@ -31,46 +34,18 @@ const courseSessionDetail = computed(() => {
|
|||
return courseSessionDetailResult.courseSessionDetail.value;
|
||||
});
|
||||
|
||||
const attendanceCourseCircleId = computed(() => {
|
||||
const selectedAttendandeCourse = attendanceCourses.value.find(
|
||||
(course) => course.id === state.attendanceCourseSelected.id
|
||||
);
|
||||
return selectedAttendandeCourse?.learning_content?.circle?.id;
|
||||
});
|
||||
const currentCourse = computed(() => expertCockpitStore.currentCourse);
|
||||
|
||||
const presenceCoursesDropdownOptions = computed(() => {
|
||||
return attendanceCourses.value.map(
|
||||
(attendanceCourse) =>
|
||||
({
|
||||
id: attendanceCourse.id,
|
||||
name: `${t("a.Präsenzkurs")} ${
|
||||
attendanceCourse.learning_content.circle?.title
|
||||
} ${dayjs(attendanceCourse.due_date?.start).format("DD.MM.YYYY")}`,
|
||||
}) as DropdownSelectable
|
||||
);
|
||||
});
|
||||
const userPresence = ref(new Map<string, boolean>());
|
||||
const disclaimerConfirmed = ref(false);
|
||||
const attendanceSaved = ref(false);
|
||||
|
||||
const state = reactive({
|
||||
userPresence: new Map<string, boolean>(),
|
||||
attendanceCourseSelected: presenceCoursesDropdownOptions.value[0],
|
||||
disclaimerConfirmed: false,
|
||||
attendanceSaved: false,
|
||||
});
|
||||
|
||||
watch(
|
||||
attendanceCourses,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.length > 0) {
|
||||
state.attendanceCourseSelected = presenceCoursesDropdownOptions.value[0];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
function resetState() {
|
||||
state.userPresence = new Map<string, boolean>();
|
||||
state.disclaimerConfirmed = false;
|
||||
state.attendanceSaved = false;
|
||||
userPresence.value = new Map<string, boolean>();
|
||||
disclaimerConfirmed.value = false;
|
||||
attendanceSaved.value = false;
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
|
|
@ -78,78 +53,89 @@ const onSubmit = async () => {
|
|||
user_id: string;
|
||||
status: AttendanceUserStatus;
|
||||
};
|
||||
const attendanceUserList: UserPresence[] = Array.from(state.userPresence.keys()).map(
|
||||
const attendanceUserList: UserPresence[] = Array.from(userPresence.value.keys()).map(
|
||||
(key) => ({
|
||||
user_id: key,
|
||||
status: state.userPresence.get(key) ? "PRESENT" : "ABSENT",
|
||||
status: userPresence.value.get(key) ? "PRESENT" : "ABSENT",
|
||||
})
|
||||
);
|
||||
const res = await attendanceMutation.executeMutation({
|
||||
attendanceCourseId: state.attendanceCourseSelected.id.toString(),
|
||||
attendanceCourseId: (
|
||||
currentCourse.value as CourseSessionAttendanceCourseObjectType
|
||||
).id.toString(),
|
||||
attendanceUserList: attendanceUserList,
|
||||
});
|
||||
if (res.error) {
|
||||
log.error("Could not submit attendance check: ", res.error);
|
||||
return;
|
||||
}
|
||||
state.disclaimerConfirmed = false;
|
||||
state.attendanceSaved = true;
|
||||
disclaimerConfirmed.value = false;
|
||||
attendanceSaved.value = true;
|
||||
log.info("Attendance check submitted: ", res);
|
||||
};
|
||||
|
||||
const loadAttendanceData = async () => {
|
||||
resetState();
|
||||
// with changing variables `useQuery` does not seem to work correctly
|
||||
if (state.attendanceCourseSelected) {
|
||||
const res = await graphqlClient.query(
|
||||
ATTENDANCE_CHECK_QUERY,
|
||||
{
|
||||
courseSessionId: state.attendanceCourseSelected.id.toString(),
|
||||
if (currentCourse.value) {
|
||||
const result = await useQuery({
|
||||
query: ATTENDANCE_CHECK_QUERY,
|
||||
variables: {
|
||||
courseSessionId: currentCourse.value.id.toString(),
|
||||
},
|
||||
{
|
||||
requestPolicy: "network-only",
|
||||
}
|
||||
);
|
||||
requestPolicy: "network-only",
|
||||
});
|
||||
const attendanceUserList =
|
||||
res.data?.course_session_attendance_course?.attendance_user_list ?? [];
|
||||
result.data?.value?.course_session_attendance_course?.attendance_user_list ?? [];
|
||||
for (const user of attendanceUserList) {
|
||||
if (!user) continue;
|
||||
state.userPresence.set(user.user_id, user.status === "PRESENT");
|
||||
userPresence.value.set(user.user_id, user.status === "PRESENT");
|
||||
}
|
||||
if (attendanceUserList.length !== 0) {
|
||||
state.attendanceSaved = true;
|
||||
attendanceSaved.value = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function editAgain() {
|
||||
state.attendanceSaved = false;
|
||||
attendanceSaved.value = false;
|
||||
}
|
||||
|
||||
const toggleDisclaimer = (newValue: boolean) => {
|
||||
disclaimerConfirmed.value = newValue;
|
||||
};
|
||||
|
||||
async function exportData() {
|
||||
const data = await exportAttendance(
|
||||
{
|
||||
courseSessionIds: [Number(courseSession.value.id)],
|
||||
circleIds: [Number(attendanceCourseCircleId.value)],
|
||||
circleIds: [Number(currentCourse.value?.learning_content.circle?.id)],
|
||||
},
|
||||
userStore.language
|
||||
);
|
||||
openDataAsXls(data.encoded_data, data.file_name);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
log.debug("AttendanceCheckPage mounted");
|
||||
loadAttendanceData();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => state.attendanceCourseSelected,
|
||||
() => {
|
||||
log.debug("attendanceCourseSelected changed", state.attendanceCourseSelected);
|
||||
loadAttendanceData();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const courseDueDate = computed(() => {
|
||||
if (currentCourse.value && currentCourse.value.due_date?.start) {
|
||||
return currentCourse.value.due_date.start;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
const formattedCourseDueDate = computed(() => {
|
||||
if (courseDueDate.value) {
|
||||
return useDateFormat(courseDueDate.value, "D. MMMM YYYY", {
|
||||
locales: "de-CH",
|
||||
});
|
||||
}
|
||||
return "";
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -164,66 +150,50 @@ watch(
|
|||
<span>{{ $t("general.back") }}</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="pb-4 text-xl font-bold">{{ $t("a.Anwesenheit Präsenzkurse") }}</h3>
|
||||
<button
|
||||
v-if="state.attendanceSaved"
|
||||
class="flex"
|
||||
data-cy="export-button"
|
||||
@click="exportData"
|
||||
>
|
||||
<it-icon-export></it-icon-export>
|
||||
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<section v-if="attendanceCourses.length && state.attendanceCourseSelected">
|
||||
<div class="flex flex-row flex-wrap justify-between bg-white p-6">
|
||||
<ItDropdownSelect
|
||||
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"
|
||||
<div class="flex items-center justify-between"></div>
|
||||
<section v-if="attendanceCourses.length && currentCourse">
|
||||
<div class="grid grid-cols-[2fr_1fr] justify-between gap-8 bg-white py-6">
|
||||
<div class="col-span-1 flex flex-col gap-2 px-6">
|
||||
<h3 class="pb-1 text-4xl font-bold">{{ $t("a.Präsenzkurs") }}</h3>
|
||||
<h5>
|
||||
{{ t("a.Circle") }} «{{ currentCourse?.learning_content.circle?.title }}»
|
||||
</h5>
|
||||
<h5>{{ formattedCourseDueDate }}</h5>
|
||||
</div>
|
||||
<button
|
||||
v-if="attendanceSaved"
|
||||
class="col-span-1 mr-4 hidden justify-self-end lg:flex"
|
||||
data-cy="export-button"
|
||||
@click="exportData"
|
||||
>
|
||||
<ItCheckbox
|
||||
:checkbox-item="{
|
||||
value: true,
|
||||
checked: state.disclaimerConfirmed,
|
||||
}"
|
||||
@toggle="state.disclaimerConfirmed = !state.disclaimerConfirmed"
|
||||
></ItCheckbox>
|
||||
<p class="w-64 pr-4 text-sm">
|
||||
{{
|
||||
$t(
|
||||
"Ich will die Anwesenheit der untenstehenden Personen definitiv bestätigen."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<button
|
||||
class="btn-primary"
|
||||
:disabled="!state.disclaimerConfirmed"
|
||||
@click="onSubmit"
|
||||
>
|
||||
{{ $t("Anwesenheit bestätigen") }}
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
<it-icon-export class="fill-current text-blue-900"></it-icon-export>
|
||||
<span class="ml inline-block text-blue-900">
|
||||
{{ $t("a.Als Excel exportieren") }}
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="col-span-2 flex flex-col items-start gap-4 px-6 lg:gap-6"
|
||||
:class="attendanceSaved ? 'lg:flex-row lg:items-center' : 'gap-8 lg:gap-8'"
|
||||
>
|
||||
<AttendanceStatus
|
||||
class="inline-flex px-6"
|
||||
:done="attendanceSaved"
|
||||
:date="courseDueDate"
|
||||
/>
|
||||
|
||||
<div class="mt-4 flex flex-col bg-white p-6">
|
||||
<div
|
||||
v-for="(csu, index) in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
>
|
||||
<AttendanceCheck
|
||||
:attendance-saved="attendanceSaved"
|
||||
:disclaimer-confirmed="disclaimerConfirmed"
|
||||
@reopen="editAgain"
|
||||
@toggle="toggleDisclaimer"
|
||||
@confirm="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 border-t border-gray-500 px-6">
|
||||
<ItPersonRow
|
||||
v-for="(csu, index) in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
:class="0 === index ? 'border-none' : ''"
|
||||
|
|
@ -233,16 +203,13 @@ watch(
|
|||
>
|
||||
<template #leading>
|
||||
<ItCheckbox
|
||||
:disabled="state.attendanceSaved"
|
||||
:disabled="attendanceSaved"
|
||||
:checkbox-item="{
|
||||
value: true,
|
||||
checked: state.userPresence.get(csu.user_id) as boolean,
|
||||
checked: userPresence.get(csu.user_id) as boolean,
|
||||
}"
|
||||
@toggle="
|
||||
state.userPresence.set(
|
||||
csu.user_id,
|
||||
!state.userPresence.get(csu.user_id)
|
||||
)
|
||||
userPresence.set(csu.user_id, !userPresence.get(csu.user_id))
|
||||
"
|
||||
></ItCheckbox>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<script setup lang="ts">
|
||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||
export interface Props {
|
||||
attendanceSaved: boolean;
|
||||
disclaimerConfirmed: boolean;
|
||||
}
|
||||
defineProps<Props>();
|
||||
defineEmits(["toggle", "reopen", "confirm"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!attendanceSaved" class="flex flex-col gap-4">
|
||||
<div class="flex flex-row content-center items-center">
|
||||
<ItCheckbox
|
||||
:checkbox-item="{
|
||||
value: true,
|
||||
checked: disclaimerConfirmed,
|
||||
}"
|
||||
@toggle="$emit('toggle', !disclaimerConfirmed)"
|
||||
></ItCheckbox>
|
||||
<p class="text-sm">
|
||||
{{
|
||||
$t(
|
||||
"Ich will die Anwesenheit der untenstehenden Personen definitiv bestätigen."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="btn-primary w-64"
|
||||
:disabled="!disclaimerConfirmed"
|
||||
@click="$emit('confirm')"
|
||||
>
|
||||
{{ $t("Anwesenheit bestätigen") }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="flex-inline">
|
||||
<button class="btn-link link" @click="$emit('reopen')">
|
||||
{{ $t("a.Erneut bearbeiten") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<script lang="ts" setup>
|
||||
import type { CourseSessionAttendanceCourseObjectType } from "@/gql/graphql";
|
||||
import { ATTENDANCE_CHECK_QUERY } from "@/graphql/queries";
|
||||
import { ATTENDANCE_ROUTE } from "@/router/names";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
import { getStatus } from "@/utils/attendance";
|
||||
import { useQuery } from "@urql/vue";
|
||||
import { useDateFormat } from "@vueuse/core";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { computed } from "vue";
|
||||
import AttendanceStatus from "./AttendanceStatus.vue";
|
||||
|
||||
const attendanceRoute = {
|
||||
name: ATTENDANCE_ROUTE,
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const expertCockpitStore = useExpertCockpitStore();
|
||||
const currentCourse = computed(() => expertCockpitStore.currentCourse);
|
||||
|
||||
const shouldPause = computed(() => !currentCourse.value);
|
||||
const result = useQuery({
|
||||
query: ATTENDANCE_CHECK_QUERY,
|
||||
variables: () => ({
|
||||
courseSessionId: (
|
||||
currentCourse.value as CourseSessionAttendanceCourseObjectType
|
||||
).id.toString(),
|
||||
}),
|
||||
pause: shouldPause,
|
||||
});
|
||||
|
||||
// todo: maybe we can move these next 3 computed values somewhere else, as they are also used in the AttendanceCheckPage component
|
||||
const courseDueDate = computed(() => {
|
||||
if (currentCourse.value && currentCourse.value.due_date?.start) {
|
||||
return currentCourse.value.due_date.start;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
const attendanceSaved = computed(() => {
|
||||
const attendanceUserList =
|
||||
result.data?.value?.course_session_attendance_course?.attendance_user_list ?? [];
|
||||
return attendanceUserList.length !== 0;
|
||||
});
|
||||
|
||||
const formattedCourseDueDate = computed(() => {
|
||||
if (courseDueDate.value) {
|
||||
return useDateFormat(courseDueDate.value, "D. MMMM YYYY", {
|
||||
locales: "de-CH",
|
||||
});
|
||||
}
|
||||
return "";
|
||||
});
|
||||
const status = computed(() => {
|
||||
return getStatus(attendanceSaved.value, courseDueDate.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="my-4 flex flex-col items-start justify-between gap-4 bg-white p-6 lg:my-0 lg:flex-row lg:items-center lg:gap-0"
|
||||
>
|
||||
<div>
|
||||
<h2 class="text-base font-bold">{{ t("a.Präsenzkurs") }}</h2>
|
||||
<p class="text-sm text-gray-800">{{ formattedCourseDueDate }}</p>
|
||||
</div>
|
||||
<AttendanceStatus :date="courseDueDate" :done="attendanceSaved" />
|
||||
<router-link
|
||||
:to="attendanceRoute"
|
||||
:class="
|
||||
status === 'now' ? 'bg-blue-900 px-4 py-2 font-bold text-white' : 'underline'
|
||||
"
|
||||
>
|
||||
<template v-if="status === 'now'">
|
||||
{{ $t("Anwesenheit prüfen") }}
|
||||
</template>
|
||||
<template v-else>{{ $t("a.Anwesenheit anschauen") }}</template>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<script setup lang="ts">
|
||||
import { howManyDaysInFuture } from "@/components/dueDates/dueDatesUtils";
|
||||
import { getStatus } from "@/utils/attendance";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
export interface Props {
|
||||
done: boolean;
|
||||
date: string;
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const status = computed(() => {
|
||||
return getStatus(props.done, props.date);
|
||||
});
|
||||
|
||||
const style = computed(() => {
|
||||
switch (status.value) {
|
||||
case "done":
|
||||
return "bg-green-200";
|
||||
case "soon":
|
||||
return "bg-gray-200";
|
||||
case "now":
|
||||
default:
|
||||
return "bg-sky-200";
|
||||
}
|
||||
});
|
||||
|
||||
const icon = computed(() => {
|
||||
switch (status.value) {
|
||||
case "done":
|
||||
return "it-icon-check";
|
||||
case "soon":
|
||||
case "now":
|
||||
default:
|
||||
return "it-icon-info";
|
||||
}
|
||||
});
|
||||
|
||||
const days = computed(() => {
|
||||
return howManyDaysInFuture(props.date);
|
||||
});
|
||||
|
||||
const text = computed(() => {
|
||||
switch (status.value) {
|
||||
case "done":
|
||||
return t("a.Du hast die Anwesenheit bestätigt.");
|
||||
case "soon":
|
||||
return t("a.Der Präsenzkurs findet in {{days}} Tagen statt.", {
|
||||
count: days.value,
|
||||
});
|
||||
case "now":
|
||||
default:
|
||||
return t("a.Überprüfe jetzt die Anwesenheit.");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="space-between inline-flex flex-row items-center gap-1 rounded py-1 pl-2 pr-4"
|
||||
:class="style"
|
||||
>
|
||||
<component :is="icon" class="h-7 w-7" />
|
||||
<p>{{ text }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
<script setup lang="ts">
|
||||
import SubmissionsOverview from "@/components/cockpit/SubmissionsOverview.vue";
|
||||
import UserStatusCount from "@/components/cockpit/UserStatusCount.vue";
|
||||
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
||||
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
import AttendanceOverview from "./AttendanceOverview.vue";
|
||||
|
||||
const expertCockpitStore = useExpertCockpitStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="expertCockpitStore.circles?.length">
|
||||
<div v-if="expertCockpitStore.currentCircle" class="container-large pt-10">
|
||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<ItDropdownSelect
|
||||
:as-heading="true"
|
||||
:model-value="expertCockpitStore.currentCircle"
|
||||
type-name="Circle:"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-auto"
|
||||
:items="expertCockpitStore.circles"
|
||||
@update:model-value="expertCockpitStore.setCurrentCourseCircleFromEvent"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<!-- Status -->
|
||||
<div class="mb-4 gap-4">
|
||||
<AttendanceOverview />
|
||||
</div>
|
||||
|
||||
<div class="mb-4 bg-white p-6">
|
||||
<CourseSessionDueDatesList
|
||||
:course-session-id="courseSession.id"
|
||||
:circle-id="expertCockpitStore.currentCircle.id"
|
||||
:max-count="4"
|
||||
></CourseSessionDueDatesList>
|
||||
</div>
|
||||
<SubmissionsOverview
|
||||
:course-session="courseSession"
|
||||
:selected-circle="expertCockpitStore.currentCircle.id"
|
||||
></SubmissionsOverview>
|
||||
<div class="pt-4">
|
||||
<!-- progress -->
|
||||
<div
|
||||
v-if="courseSessionDetailResult.filterMembers().length > 0"
|
||||
class="bg-white p-6"
|
||||
>
|
||||
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
||||
<ul>
|
||||
<ItPersonRow
|
||||
v-for="csu in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
>
|
||||
<template #center>
|
||||
<div
|
||||
class="mt-2 flex w-full flex-col items-center justify-start lg:mt-0 lg:flex-row"
|
||||
>
|
||||
<LearningPathDiagram
|
||||
:course-session-id="courseSession.id"
|
||||
:course-slug="props.courseSlug"
|
||||
:user-id="csu.user_id"
|
||||
:show-circle-slugs="[expertCockpitStore.currentCircle.slug]"
|
||||
diagram-type="singleSmall"
|
||||
class="mr-4"
|
||||
></LearningPathDiagram>
|
||||
<p class="lg:min-w-[150px]">
|
||||
{{ expertCockpitStore.currentCircle.title }}
|
||||
</p>
|
||||
<UserStatusCount
|
||||
:course-slug="props.courseSlug"
|
||||
:user-id="csu.user_id"
|
||||
></UserStatusCount>
|
||||
</div>
|
||||
</template>
|
||||
<template #link>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'profileLearningPath',
|
||||
params: { userId: csu.user_id, courseSlug: props.courseSlug },
|
||||
}"
|
||||
class="link w-full lg:text-right"
|
||||
>
|
||||
{{ $t("general.profileLink") }}
|
||||
</router-link>
|
||||
</template>
|
||||
</ItPersonRow>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container-large mt-4">
|
||||
<!-- No circle selected -->
|
||||
<span class="text-lg text-orange-600">
|
||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container-large mt-4">
|
||||
<span class="text-lg text-orange-600">
|
||||
<!-- No circle at all (should never happen, mostly
|
||||
for us to reduce confusion why the cockpit is just empty...) -->
|
||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||
|
||||
import SubmissionsOverview from "@/components/cockpit/SubmissionsOverview.vue";
|
||||
import UserStatusCount from "@/components/cockpit/UserStatusCount.vue";
|
||||
import CourseSessionDueDatesList from "@/components/dueDates/CourseSessionDueDatesList.vue";
|
||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||
import log from "loglevel";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
log.debug("CockpitIndexPage created", props.courseSlug);
|
||||
|
||||
const { loading } = useExpertCockpitPageData(props.courseSlug);
|
||||
|
||||
const expertCockpitStore = useExpertCockpitStore();
|
||||
const courseSession = useCurrentCourseSession();
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!loading" class="bg-gray-200">
|
||||
<div v-if="expertCockpitStore.circles?.length">
|
||||
<div v-if="expertCockpitStore.currentCircle" class="container-large">
|
||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h1>Cockpit</h1>
|
||||
<ItDropdownSelect
|
||||
:model-value="expertCockpitStore.currentCircle"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
:items="expertCockpitStore.circles"
|
||||
@update:model-value="expertCockpitStore.setCurrentCourseCircleFromEvent"
|
||||
></ItDropdownSelect>
|
||||
</div>
|
||||
<!-- Status -->
|
||||
<div class="mb-4 gap-4 lg:grid lg:grid-cols-3 lg:grid-rows-none">
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("Trainerunterlagen") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{ $t("cockpit.trainerFilesText") }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://vbvbern.sharepoint.com/sites/myVBV-AFA_K-CI"
|
||||
class="btn-secondary min-w-min"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t("MS Teams öffnen") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="courseSession.course.configuration.enable_circle_documents"
|
||||
class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0"
|
||||
data-cy="circle-documents"
|
||||
>
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("a.Unterlagen für Teilnehmenden") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{ $t("a.Stelle deinen Lernenden zusätzliche Inhalte zur Verfügung.") }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/documents`"
|
||||
class="btn-secondary min-w-min"
|
||||
>
|
||||
{{ $t("a.Zum Unterlagen-Upload") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-4 flex flex-col justify-between bg-white p-6 lg:my-0">
|
||||
<div>
|
||||
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||
{{ $t("a.Anwesenheitskontrolle Präsenzkurse") }}
|
||||
</h3>
|
||||
<div class="mb-4">
|
||||
{{
|
||||
$t(
|
||||
"Hier überprüfst und bestätigst du die Anwesenheit deiner Teilnehmenden."
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<router-link
|
||||
:to="`/course/${props.courseSlug}/cockpit/attendance`"
|
||||
class="btn-secondary min-w-min"
|
||||
>
|
||||
{{ $t("Anwesenheit prüfen") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 bg-white p-6">
|
||||
<CourseSessionDueDatesList
|
||||
:course-session-id="courseSession.id"
|
||||
:circle-id="expertCockpitStore.currentCircle.id"
|
||||
:max-count="4"
|
||||
></CourseSessionDueDatesList>
|
||||
</div>
|
||||
<SubmissionsOverview
|
||||
:course-session="courseSession"
|
||||
:selected-circle="expertCockpitStore.currentCircle.id"
|
||||
></SubmissionsOverview>
|
||||
<div class="pt-4">
|
||||
<!-- progress -->
|
||||
<div
|
||||
v-if="courseSessionDetailResult.filterMembers().length > 0"
|
||||
class="bg-white p-6"
|
||||
>
|
||||
<h1 class="heading-3 mb-5">{{ $t("cockpit.progress") }}</h1>
|
||||
<ul>
|
||||
<ItPersonRow
|
||||
v-for="csu in courseSessionDetailResult.filterMembers()"
|
||||
:key="csu.user_id"
|
||||
:name="`${csu.first_name} ${csu.last_name}`"
|
||||
:avatar-url="csu.avatar_url"
|
||||
>
|
||||
<template #center>
|
||||
<div
|
||||
class="mt-2 flex w-full flex-col items-center justify-start lg:mt-0 lg:flex-row"
|
||||
>
|
||||
<LearningPathDiagram
|
||||
:course-session-id="courseSession.id"
|
||||
:course-slug="props.courseSlug"
|
||||
:user-id="csu.user_id"
|
||||
:show-circle-slugs="[expertCockpitStore.currentCircle.slug]"
|
||||
diagram-type="singleSmall"
|
||||
class="mr-4"
|
||||
></LearningPathDiagram>
|
||||
<p class="lg:min-w-[150px]">
|
||||
{{ expertCockpitStore.currentCircle.title }}
|
||||
</p>
|
||||
<UserStatusCount
|
||||
:course-slug="props.courseSlug"
|
||||
:user-id="csu.user_id"
|
||||
></UserStatusCount>
|
||||
</div>
|
||||
</template>
|
||||
<template #link>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'profileLearningPath',
|
||||
params: { userId: csu.user_id, courseSlug: props.courseSlug },
|
||||
}"
|
||||
class="link w-full lg:text-right"
|
||||
>
|
||||
{{ $t("general.profileLink") }}
|
||||
</router-link>
|
||||
</template>
|
||||
</ItPersonRow>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container-large mt-4">
|
||||
<!-- No circle selected -->
|
||||
<span class="text-lg text-orange-600">
|
||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="container-large mt-4">
|
||||
<span class="text-lg text-orange-600">
|
||||
<!-- No circle at all (should never happen, mostly
|
||||
for us to reduce confusion why the cockpit is just empty...) -->
|
||||
{{ $t("a.Kein Circle verfügbar oder ausgewählt.") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<script setup lang="ts">
|
||||
import SubNavigation from "@/components/header/SubNavigation.vue";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||
import { COCKPIT_ROUTE, DOCUMENTS_ROUTE } from "@/router/names";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseSlug: string;
|
||||
}>();
|
||||
|
||||
log.debug("CockpitIndexPage created", props.courseSlug);
|
||||
|
||||
const { loading } = useExpertCockpitPageData(props.courseSlug);
|
||||
|
||||
const defaultRoute = {
|
||||
name: COCKPIT_ROUTE,
|
||||
};
|
||||
// const attendanceRoute = {
|
||||
// name: ATTENDANCE_ROUTE,
|
||||
// };
|
||||
const documentsRoute = {
|
||||
name: DOCUMENTS_ROUTE,
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
const enableDocuments = computed(() => {
|
||||
return !!courseSession.value.course.configuration.enable_circle_documents;
|
||||
});
|
||||
|
||||
const items = computed(() => [
|
||||
{ id: 1, name: t("a.Übersicht"), route: defaultRoute },
|
||||
// { id: 2, name: t("a.Teilnehmer"), route: attendanceRoute }, // todo: re-enable with correct route in a later issue
|
||||
...(enableDocuments.value
|
||||
? [
|
||||
{
|
||||
id: 3,
|
||||
name: t("a.Unterlagen"),
|
||||
route: documentsRoute,
|
||||
dataCy: "circle-documents",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
id: 4,
|
||||
name: "Vorschau Teilnehmer",
|
||||
route: "https://iterativ.ch",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "MS Teams",
|
||||
route: "https://vbvbern.sharepoint.com/sites/myVBV-AFA_K-CI",
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!loading" class="bg-gray-200">
|
||||
<SubNavigation :items="items" />
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -136,21 +136,13 @@ async function uploadDocument(data: DocumentUploadData) {
|
|||
<template>
|
||||
<div class="bg-gray-200">
|
||||
<div v-if="courseSession" class="container-large">
|
||||
<nav class="py-4 pb-4">
|
||||
<router-link
|
||||
class="btn-text inline-flex items-center pl-0"
|
||||
:to="`/course/${courseSession.course.slug}/cockpit`"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>{{ t("general.back") }}</span>
|
||||
</router-link>
|
||||
</nav>
|
||||
<main>
|
||||
<main class="py-4">
|
||||
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
||||
<h2>{{ t("a.Unterlagen für Teilnehmenden") }}</h2>
|
||||
<ItDropdownSelect
|
||||
:model-value="cockpitStore.currentCircle"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-96"
|
||||
class="mt-4 w-full lg:mt-0 lg:w-auto"
|
||||
:as-heading="true"
|
||||
type-name="Circle:"
|
||||
:items="cockpitStore.circles"
|
||||
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
||||
></ItDropdownSelect>
|
||||
|
|
|
|||
|
|
@ -56,17 +56,6 @@ const items: SubNavEntry[] = [
|
|||
route: selfEvaluationRoute,
|
||||
},
|
||||
{ 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>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,13 @@ import { addToHistory, setLastNavigationWasPush } from "@/router/history";
|
|||
import { onboardingRedirect } from "@/router/onboarding";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import {
|
||||
ATTENDANCE_ROUTE,
|
||||
CERTIFICATES_ROUTE,
|
||||
COCKPIT_ROUTE,
|
||||
COMPETENCE_ROUTE,
|
||||
COMPETENCES_ROUTE,
|
||||
DOCUMENTS_ROUTE,
|
||||
PERSONAL_PROFILE_ROUTE,
|
||||
SELF_EVALUATION_ROUTE,
|
||||
SETTINGS_ROUTE,
|
||||
} from "./names";
|
||||
|
|
@ -86,88 +90,269 @@ const router = createRouter({
|
|||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/media",
|
||||
path: "/course/:courseSlug",
|
||||
props: true,
|
||||
component: () => import("@/pages/mediaLibrary/MediaLibraryParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/pages/mediaLibrary/MediaLibraryIndexPage.vue"),
|
||||
path: "media",
|
||||
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",
|
||||
props: true,
|
||||
component: () => import("@/pages/mediaLibrary/MediaLibraryCategoryPage.vue"),
|
||||
path: "competence",
|
||||
component: () => import("@/pages/competence/CompetenceParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
props: true,
|
||||
name: COMPETENCE_ROUTE,
|
||||
component: () => import("@/pages/competence/CompetenceIndexPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "certificates",
|
||||
name: CERTIFICATES_ROUTE,
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateListPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "certificates/:certificateSlug",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||
},
|
||||
{
|
||||
name: SELF_EVALUATION_ROUTE,
|
||||
path: "self-evaluation-and-feedback",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/SelfEvaluationAndFeedbackPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "competences",
|
||||
name: COMPETENCES_ROUTE,
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/ActionCompetenceListPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ":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,
|
||||
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",
|
||||
component: () => import("@/pages/learningMentor/InvitationAcceptPage.vue"),
|
||||
|
|
@ -178,158 +363,6 @@ const router = createRouter({
|
|||
public: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/profile/:userId",
|
||||
component: () => import("@/pages/userProfile/UserProfilePage.vue"),
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: "learning-path",
|
||||
component: () => import("@/pages/userProfile/LearningPathProfilePage.vue"),
|
||||
props: true,
|
||||
name: "profileLearningPath",
|
||||
meta: {
|
||||
hideChrome: true,
|
||||
showCloseButton: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "competence",
|
||||
component: () => import("@/pages/userProfile/CompetenceProfilePage.vue"),
|
||||
props: true,
|
||||
name: "profileCompetence",
|
||||
meta: {
|
||||
hideChrome: true,
|
||||
showCloseButton: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "competenceMain",
|
||||
component: () =>
|
||||
import(
|
||||
"@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "evaluations",
|
||||
name: "competenceEvaluations",
|
||||
component: () =>
|
||||
import(
|
||||
"@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackList.vue"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "certificates/:certificateSlug",
|
||||
name: "competenceCertificateDetail",
|
||||
props: true,
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "certificates",
|
||||
name: "competenceCertificates",
|
||||
component: () =>
|
||||
import("@/pages/competence/CompetenceCertificateListPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/learning-mentor",
|
||||
component: () => import("@/pages/learningMentor/mentor/MentorIndexPage.vue"),
|
||||
props: true,
|
||||
name: "learningMentor",
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorParticipantsPage.vue"),
|
||||
name: "mentorsAndParticipants",
|
||||
},
|
||||
{
|
||||
path: "tasks",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorOverviewPage.vue"),
|
||||
name: "learningMentorOverview",
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback/:learningUnitId",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/SelfEvaluationFeedbackPage.vue"),
|
||||
name: "mentorSelfEvaluationFeedback",
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "details",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorDetailParentPage.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "praxis-assignments/:praxisAssignmentId",
|
||||
component: () =>
|
||||
import("@/pages/learningMentor/mentor/MentorPraxisAssignmentPage.vue"),
|
||||
name: "learningMentorPraxisAssignments",
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "self-evaluation-feedback-assignments/:learningUnitId",
|
||||
component: () =>
|
||||
import(
|
||||
"@/pages/learningMentor/mentor/MentorSelfEvaluationFeedbackAssignmentPage.vue"
|
||||
),
|
||||
name: "learningMentorSelfEvaluationFeedbackAssignments",
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/assignment-evaluation/:assignmentId/:userId",
|
||||
component: () =>
|
||||
import("@/pages/assignmentEvaluation/AssignmentEvaluationPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "/course/:courseSlug/cockpit",
|
||||
name: "cockpit",
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/pages/cockpit/cockpitPage/CockpitExpertPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "profile/:userId/:circleSlug",
|
||||
component: () => import("@/pages/cockpit/CockpitUserCirclePage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "feedback/:circleId",
|
||||
component: () => import("@/pages/cockpit/FeedbackPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "assignment/:assignmentId",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/assignmentsPage/AssignmentsPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "attendance",
|
||||
component: () =>
|
||||
import("@/pages/cockpit/attendanceCheckPage/AttendanceCheckPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: "documents",
|
||||
component: () => import("@/pages/cockpit/documentPage/DocumentPage.vue"),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/statistic/:courseSlug",
|
||||
props: true,
|
||||
|
|
@ -399,7 +432,7 @@ const router = createRouter({
|
|||
{
|
||||
path: "/profile",
|
||||
component: () => import("@/pages/personalProfile/PersonalProfilePage.vue"),
|
||||
name: "personalProfile",
|
||||
name: PERSONAL_PROFILE_ROUTE,
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
|
|
|
|||
|
|
@ -3,3 +3,7 @@ export const CERTIFICATES_ROUTE = "certificates";
|
|||
export const SELF_EVALUATION_ROUTE = "selfEvaluationAndFeedback";
|
||||
export const COMPETENCES_ROUTE = "competences";
|
||||
export const SETTINGS_ROUTE = "settings";
|
||||
export const COCKPIT_ROUTE = "cockpit-home";
|
||||
export const ATTENDANCE_ROUTE = "attendance";
|
||||
export const DOCUMENTS_ROUTE = "documents";
|
||||
export const PERSONAL_PROFILE_ROUTE = "personalProfile";
|
||||
|
|
|
|||
|
|
@ -1,50 +1,14 @@
|
|||
import { useCourseData } from "@/composables";
|
||||
import { useCourseData, useCourseSessionDetailQuery } from "@/composables";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { CircleLight, CourseSessionUser, ExpertSessionUser } from "@/types";
|
||||
import log from "loglevel";
|
||||
import { defineStore } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
type CircleExpertCockpit = CircleLight & {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ExpertCockpitStoreState = {
|
||||
courseSessionMembers: CourseSessionUser[] | undefined;
|
||||
circles: CircleExpertCockpit[] | undefined;
|
||||
currentCircle: CircleExpertCockpit | undefined;
|
||||
};
|
||||
|
||||
export const useExpertCockpitStore = defineStore({
|
||||
id: "expertCockpit",
|
||||
state: () => {
|
||||
return {
|
||||
courseSessionMembers: undefined,
|
||||
circles: [],
|
||||
currentCircle: undefined,
|
||||
} as ExpertCockpitStoreState;
|
||||
},
|
||||
actions: {
|
||||
async loadCircles(
|
||||
courseSlug: string,
|
||||
currentCourseSessionUser: CourseSessionUser | undefined
|
||||
) {
|
||||
log.debug("loadCircles called", courseSlug);
|
||||
|
||||
this.circles = await courseCircles(courseSlug, currentCourseSessionUser);
|
||||
|
||||
if (this.circles?.length) {
|
||||
await this.setCurrentCourseCircle(this.circles[0].slug);
|
||||
}
|
||||
},
|
||||
async setCurrentCourseCircle(circleSlug: string) {
|
||||
this.currentCircle = this.circles?.find((c) => c.slug === circleSlug);
|
||||
},
|
||||
async setCurrentCourseCircleFromEvent(event: CircleLight) {
|
||||
await this.setCurrentCourseCircle(event.slug);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function courseCircles(
|
||||
courseSlug: string,
|
||||
currentCourseSessionUser: CourseSessionUser | undefined
|
||||
|
|
@ -74,3 +38,51 @@ async function courseCircles(
|
|||
|
||||
return [];
|
||||
}
|
||||
|
||||
export const useExpertCockpitStore = defineStore("expertCockpit", () => {
|
||||
const courseSessionMembers = ref<CourseSessionUser[] | undefined>(undefined);
|
||||
const circles = ref<CircleExpertCockpit[] | undefined>([]);
|
||||
const currentCircle = ref<CircleExpertCockpit | undefined>(undefined);
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const attendanceCourses = computed(() => {
|
||||
return (
|
||||
courseSessionDetailResult.courseSessionDetail.value?.attendance_courses ?? []
|
||||
);
|
||||
});
|
||||
|
||||
const currentCourse = computed(() => {
|
||||
return attendanceCourses.value.find(
|
||||
(i) => i.learning_content.circle?.id == currentCircle.value?.id
|
||||
);
|
||||
});
|
||||
|
||||
const loadCircles = async (
|
||||
courseSlug: string,
|
||||
currentCourseSessionUser: CourseSessionUser | undefined
|
||||
) => {
|
||||
log.debug("loadCircles called", courseSlug);
|
||||
|
||||
circles.value = await courseCircles(courseSlug, currentCourseSessionUser);
|
||||
|
||||
if (circles.value?.length) {
|
||||
await setCurrentCourseCircle(circles.value[0].slug);
|
||||
}
|
||||
};
|
||||
|
||||
const setCurrentCourseCircle = async (circleSlug: string) => {
|
||||
currentCircle.value = circles.value?.find((c) => c.slug === circleSlug);
|
||||
};
|
||||
const setCurrentCourseCircleFromEvent = async (event: CircleLight) => {
|
||||
await setCurrentCourseCircle(event.slug);
|
||||
};
|
||||
|
||||
return {
|
||||
courseSessionMembers,
|
||||
circles,
|
||||
currentCircle,
|
||||
loadCircles,
|
||||
currentCourse,
|
||||
setCurrentCourseCircleFromEvent,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
beforeEach(() => {
|
||||
cy.manageCommand("cypress_reset");
|
||||
cy.intercept("/server/graphql").as("graphql");
|
||||
});
|
||||
|
||||
describe("with circle documents enabled", () => {
|
||||
it("student can see circle documents", () => {
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
||||
cy.wait(["@graphql", "@graphql"]);
|
||||
cy.get('[data-cy="circle-document-section"]').should("exist");
|
||||
});
|
||||
|
||||
it("trainer can see circle documents", () => {
|
||||
login("test-trainer1@example.com", "test");
|
||||
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.wait(["@graphql", "@graphql"]);
|
||||
cy.get('[data-cy="circle-documents"]').should("exist");
|
||||
});
|
||||
});
|
||||
|
|
@ -27,6 +31,7 @@ describe("settings.cy.js", () => {
|
|||
it("student cannot see circle documents", () => {
|
||||
login("test-student1@example.com", "test");
|
||||
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
||||
cy.wait(["@graphql", "@graphql"]);
|
||||
cy.get('[data-cy="circle-title"]').should("contain", "Fahrzeug");
|
||||
cy.get('[data-cy="circle-document-section"]').should("not.exist");
|
||||
});
|
||||
|
|
@ -34,6 +39,7 @@ describe("settings.cy.js", () => {
|
|||
it("trainer cannot see circle documents", () => {
|
||||
login("test-trainer1@example.com", "test");
|
||||
cy.visit(EXPERT_COCKPIT_URL);
|
||||
cy.wait(["@graphql", "@graphql"]);
|
||||
cy.get('[data-cy="circle-documents"]').should("not.exist");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue