Compare commits

...

4 Commits

10 changed files with 132 additions and 44 deletions

View File

@ -10,11 +10,12 @@ import {
} from "@/utils/utils"; } from "@/utils/utils";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
import { computed } from "vue"; import { computed } from "vue";
import SelectedCourseSession from "./SelectedCourseSession.vue";
const { t } = useTranslation(); const { t } = useTranslation();
const courseSessionsStore = useCourseSessionsStore(); const courseSessionsStore = useCourseSessionsStore();
const { inCockpit, inCompetenceProfile, inLearningMentor, inLearningPath } = const { isInCockpit, inCompetenceProfile, inLearningMentor, inLearningPath } =
useRouteLookups(); useRouteLookups();
const { const {
hasCompetenceNaviMenu, hasCompetenceNaviMenu,
@ -30,13 +31,17 @@ const mentorTabTitle = computed(() =>
); );
</script> </script>
<template> <template>
<div v-if="courseSessionsStore.currentCourseSession" class="hidden space-x-8 lg:flex"> <div
v-if="courseSessionsStore.currentCourseSession"
class="hidden space-x-8 px-10 lg:flex"
>
<SelectedCourseSession />
<router-link <router-link
v-if="hasCockpitMenu" v-if="hasCockpitMenu"
data-cy="navigation-cockpit-link" data-cy="navigation-cockpit-link"
:to="getCockpitUrl(courseSessionsStore.currentCourseSession.course.slug)" :to="getCockpitUrl(courseSessionsStore.currentCourseSession.course.slug)"
class="nav-item" class="nav-item"
:class="{ 'nav-item--active': inCockpit() }" :class="{ 'nav-item--active': isInCockpit }"
> >
{{ t("cockpit.title") }} {{ t("cockpit.title") }}
</router-link> </router-link>

View File

@ -1,12 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRouteLookups } from "@/utils/route";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
const { isInCourse } = useRouteLookups();
const { t } = useTranslation(); const { t } = useTranslation();
</script> </script>
<template> <template>
<div class="hidden flex-shrink-0 items-center lg:flex"> <div class="flex flex-shrink-0 items-center">
<div class="flex items-center"> <template v-if="isInCourse">
<div class="flex items-center">
<router-link to="/" class="flex items-center border-r border-white pr-3">
<it-icon-arrow-left />
<span class="hidden text-slate-500 lg:inline">Dashboard</span>
</router-link>
</div>
</template>
<template v-else>
<router-link to="/" class="flex"> <router-link to="/" class="flex">
<it-icon-vbv class="-ml-3 -mt-6 mr-3 h-8 w-16" /> <it-icon-vbv class="-ml-3 -mt-6 mr-3 h-8 w-16" />
</router-link> </router-link>
@ -15,6 +25,6 @@ const { t } = useTranslation();
{{ t("general.title") }} {{ t("general.title") }}
</div> </div>
</router-link> </router-link>
</div> </template>
</div> </div>
</template> </template>

View File

@ -15,12 +15,7 @@ log.debug("MainNavigationBar created");
const courseSessionsStore = useCourseSessionsStore(); const courseSessionsStore = useCourseSessionsStore();
const { inMediaLibrary, inAppointments } = useRouteLookups(); const { inMediaLibrary, inAppointments } = useRouteLookups();
const { hasMediaLibraryMenu, hasAppointmentsMenu, hasSessionTitle } = const { hasMediaLibraryMenu, hasAppointmentsMenu } = useNavigationAttributes();
useNavigationAttributes();
const selectedCourseSessionTitle = computed(() => {
return courseSessionsStore.currentCourseSession?.title;
});
const appointmentsUrl = computed(() => { const appointmentsUrl = computed(() => {
const currentCourseSession = courseSessionsStore.currentCourseSession; const currentCourseSession = courseSessionsStore.currentCourseSession;
@ -40,7 +35,6 @@ onMounted(() => {
<nav class="bg-blue-900 text-white"> <nav class="bg-blue-900 text-white">
<div class="mx-auto px-4 lg:px-8"> <div class="mx-auto px-4 lg:px-8">
<div class="relative flex h-16 justify-between"> <div class="relative flex h-16 justify-between">
<MobileMenuButton />
<div class="flex flex-1 items-stretch justify-start"> <div class="flex flex-1 items-stretch justify-start">
<HomeNavigation /> <HomeNavigation />
<CourseSessionNavigation /> <CourseSessionNavigation />
@ -62,7 +56,7 @@ onMounted(() => {
v-if="hasAppointmentsMenu" v-if="hasAppointmentsMenu"
:to="appointmentsUrl" :to="appointmentsUrl"
data-cy="all-duedates-link" data-cy="all-duedates-link"
class="nav-item" class="nav-item-no-mobile"
:class="{ 'nav-item--active': inAppointments() }" :class="{ 'nav-item--active': inAppointments() }"
> >
<it-icon-calendar-light class="h-8 w-8" /> <it-icon-calendar-light class="h-8 w-8" />
@ -71,22 +65,12 @@ onMounted(() => {
<!-- Notification Bell & Menu --> <!-- Notification Bell & Menu -->
<NotificationButton /> <NotificationButton />
<div
v-if="hasSessionTitle"
class="nav-item hidden items-center lg:inline-flex"
>
<div class="" data-cy="current-course-session-title">
{{ selectedCourseSessionTitle }}
</div>
</div>
<div class="nav-item"> <div class="nav-item">
<ProfileMenuButton /> <ProfileMenuButton />
</div> </div>
</div> </div>
<MobileMenuButton />
</div> </div>
</div> </div>
</nav> </nav>
</template> </template>
<style lang="postcss"></style>

View File

@ -45,7 +45,7 @@ const showMenu = ref(false);
@logout="userStore.handleLogout()" @logout="userStore.handleLogout()"
/> />
</Teleport> </Teleport>
<div class="absolute inset-y-0 left-0 flex items-center lg:hidden"> <div class="inset-y-0 flex items-center lg:hidden">
<!-- Mobile menu button --> <!-- Mobile menu button -->
<div data-cy="navigation-mobile-menu-button" class="flex" @click="showMenu = true"> <div data-cy="navigation-mobile-menu-button" class="flex" @click="showMenu = true">
<button <button

View File

@ -29,7 +29,11 @@ function popoverClick(event: Event) {
<AccountMenu @close="showMenu = false" /> <AccountMenu @close="showMenu = false" />
</ItFullScreenModal> </ItFullScreenModal>
</Teleport> </Teleport>
<div v-if="userStore.loggedIn" class="flex items-center" data-cy="header-profile"> <div
v-if="userStore.loggedIn"
class="hidden items-center lg:flex"
data-cy="header-profile"
>
<Popover class="relative"> <Popover class="relative">
<PopoverButton @click="popoverClick($event)"> <PopoverButton @click="popoverClick($event)">
<div v-if="userStore.avatar_url"> <div v-if="userStore.avatar_url">

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useNavigationAttributes } from "@/utils/navigation";
import { computed } from "vue";
const courseSessionsStore = useCourseSessionsStore();
const { hasSessionTitle } = useNavigationAttributes();
const selectedCourseSessionTitle = computed(() => {
return courseSessionsStore.currentCourseSession?.title;
});
</script>
<template>
<div v-if="hasSessionTitle" class="nav-item hidden items-center lg:inline-flex">
<div class="" data-cy="current-course-session-title">
{{ selectedCourseSessionTitle }}
</div>
</div>
</template>

View File

@ -1,15 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCurrentCourseSession, useEvaluationWithFeedback } from "@/composables"; import { useCurrentCourseSession, useEvaluationWithFeedback } from "@/composables";
import {
CERTIFICATES_ROUTE,
COMPETENCE_ROUTE,
COMPETENCES_ROUTE,
SELF_EVALUATION_ROUTE,
} from "@/router/names";
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from "@headlessui/vue";
import * as log from "loglevel"; import * as log from "loglevel";
import { onMounted } from "vue"; import { onMounted } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
log.debug("CompetenceParentPage created"); log.debug("CompetenceParentPage created");
const props = defineProps<{
courseSlug: string;
}>();
const route = useRoute(); const route = useRoute();
function routeInOverview() { function routeInOverview() {
@ -32,19 +35,68 @@ const currentCourseSession = useCurrentCourseSession();
const hasEvaluationFeedback = useEvaluationWithFeedback().hasFeedback; const hasEvaluationFeedback = useEvaluationWithFeedback().hasFeedback;
onMounted(async () => { onMounted(async () => {
log.debug("CompetenceParentPage mounted", props.courseSlug); log.debug("CompetenceParentPage mounted");
}); });
const items = [
{ id: 0, name: "Übersicht" },
{ id: 1, name: "Teilnehmer" },
{ id: 2, name: "Unterlagen" },
{ id: 3, name: "MS Teams", iconName: "it-icon-external-link" },
{ id: 4, name: "Vorschau Teilnehmer", iconName: "it-icon-external-link" },
];
const competenceRoute = {
name: COMPETENCE_ROUTE,
};
const certificatesRoute = {
name: CERTIFICATES_ROUTE,
};
const selfEvaluationRoute = {
name: SELF_EVALUATION_ROUTE,
};
const competencesRoute = {
name: COMPETENCES_ROUTE,
};
</script> </script>
<template> <template>
<div class="bg-gray-200"> <div class="bg-gray-200">
<nav class="border-b bg-white px-4 lg:px-8"> <nav class="border-b bg-white px-4 lg:px-8">
<ul class="flex flex-col lg:flex-row"> <Listbox as="div">
<div class="relative w-full">
<ListboxButton
class="relative flex w-full cursor-default flex-row items-center border bg-white py-3 pl-5 pr-10 text-left"
>
Übersicht
<span
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
>
<it-icon-arrow-down class="h-5 w-5" aria-hidden="true" />
</span>
</ListboxButton>
<ListboxOptions
class="absolute top-14 flex w-full cursor-default flex-col rounded-xl border-0 bg-white text-left shadow-lg"
>
<ListboxOption
v-for="item in items"
:key="item.id"
as="router-link"
class="flex w-full items-center gap-2 border-b py-3 pl-5 pr-10 last:border-b-0"
>
<router-link to="/">
{{ item.name }}
</router-link>
<component :is="item.iconName" v-if="item.iconName"></component>
</ListboxOption>
</ListboxOptions>
</div>
</Listbox>
<ul class="hidden flex-col lg:flex lg:flex-row">
<li <li
class="border-t-2 border-t-transparent" class="border-t-2 border-t-transparent"
:class="{ 'border-b-2 border-b-blue-900': routeInOverview() }" :class="{ 'border-b-2 border-b-blue-900': routeInOverview() }"
> >
<router-link :to="`/course/${courseSlug}/competence`" class="block py-3"> <router-link :to="competenceRoute" class="block py-3">
{{ $t("a.Übersicht") }} {{ $t("a.Übersicht") }}
</router-link> </router-link>
</li> </li>
@ -55,10 +107,7 @@ onMounted(async () => {
class="border-t-2 border-t-transparent lg:ml-12" class="border-t-2 border-t-transparent lg:ml-12"
:class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }" :class="{ 'border-b-2 border-b-blue-900': routeInCompetenceCertificate() }"
> >
<router-link <router-link :to="certificatesRoute" class="block py-3">
:to="`/course/${courseSlug}/competence/certificates`"
class="block py-3"
>
{{ $t("a.Kompetenznachweise") }} {{ $t("a.Kompetenznachweise") }}
</router-link> </router-link>
</li> </li>
@ -69,7 +118,7 @@ onMounted(async () => {
}" }"
> >
<router-link <router-link
:to="`/course/${courseSlug}/competence/self-evaluation-and-feedback`" :to="selfEvaluationRoute"
class="block py-3" class="block py-3"
data-cy="self-evaluation-and-feedback-navigation-link" data-cy="self-evaluation-and-feedback-navigation-link"
> >
@ -84,10 +133,7 @@ onMounted(async () => {
class="border-t-2 border-t-transparent lg:ml-12" class="border-t-2 border-t-transparent lg:ml-12"
:class="{ 'border-b-2 border-b-blue-900': routeInActionCompetences() }" :class="{ 'border-b-2 border-b-blue-900': routeInActionCompetences() }"
> >
<router-link <router-link :to="competencesRoute" class="block py-3">
:to="`/course/${courseSlug}/competence/competences`"
class="block py-3"
>
{{ $t("a.Handlungskompetenzen") }} {{ $t("a.Handlungskompetenzen") }}
</router-link> </router-link>
</li> </li>

View File

@ -13,6 +13,12 @@ import {
import { addToHistory, setLastNavigationWasPush } from "@/router/history"; 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 {
CERTIFICATES_ROUTE,
COMPETENCE_ROUTE,
COMPETENCES_ROUTE,
SELF_EVALUATION_ROUTE,
} from "./names";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -107,10 +113,12 @@ const router = createRouter({
{ {
path: "", path: "",
props: true, props: true,
name: COMPETENCE_ROUTE,
component: () => import("@/pages/competence/CompetenceIndexPage.vue"), component: () => import("@/pages/competence/CompetenceIndexPage.vue"),
}, },
{ {
path: "certificates", path: "certificates",
name: CERTIFICATES_ROUTE,
props: true, props: true,
component: () => component: () =>
import("@/pages/competence/CompetenceCertificateListPage.vue"), import("@/pages/competence/CompetenceCertificateListPage.vue"),
@ -122,7 +130,7 @@ const router = createRouter({
import("@/pages/competence/CompetenceCertificateDetailPage.vue"), import("@/pages/competence/CompetenceCertificateDetailPage.vue"),
}, },
{ {
name: "selfEvaluationAndFeedback", name: SELF_EVALUATION_ROUTE,
path: "self-evaluation-and-feedback", path: "self-evaluation-and-feedback",
props: true, props: true,
component: () => component: () =>
@ -130,6 +138,7 @@ const router = createRouter({
}, },
{ {
path: "competences", path: "competences",
name: COMPETENCES_ROUTE,
props: true, props: true,
component: () => import("@/pages/competence/ActionCompetenceListPage.vue"), component: () => import("@/pages/competence/ActionCompetenceListPage.vue"),
}, },

View File

@ -0,0 +1,4 @@
export const COMPETENCE_ROUTE = "competence";
export const CERTIFICATES_ROUTE = "certificates";
export const SELF_EVALUATION_ROUTE = "selfEvaluationAndFeedback";
export const COMPETENCES_ROUTE = "competences";

View File

@ -1,3 +1,4 @@
import { computed, ref } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
export function useRouteLookups() { export function useRouteLookups() {
@ -7,11 +8,15 @@ export function useRouteLookups() {
return route.path.startsWith("/course/"); return route.path.startsWith("/course/");
} }
const isInCourse = computed(() => inCourse());
function inCockpit() { function inCockpit() {
const regex = new RegExp("/course/[^/]+/cockpit($|/)"); const regex = new RegExp("/course/[^/]+/cockpit($|/)");
return regex.test(route.path); return regex.test(route.path);
} }
const isInCockpit = computed(() => inCockpit());
function inLearningPath() { function inLearningPath() {
const regex = new RegExp("/course/[^/]+/learn($|/)"); const regex = new RegExp("/course/[^/]+/learn($|/)");
return regex.test(route.path); return regex.test(route.path);
@ -39,7 +44,9 @@ export function useRouteLookups() {
return { return {
inMediaLibrary, inMediaLibrary,
isInCourse,
inCockpit, inCockpit,
isInCockpit,
inLearningPath, inLearningPath,
inCompetenceProfile, inCompetenceProfile,
inLearningMentor, inLearningMentor,