Merged in feature/new-navigation-structure (pull request #407)
Feature/new navigation structure Approved-by: Elia Bieri
This commit is contained in:
commit
345e935655
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex min-h-full flex-col">
|
<div class="flex min-h-full flex-col">
|
||||||
<MainNavigationBar v-if="!route.meta.hideChrome" class="flex-none" />
|
<HeaderBar v-if="!route.meta.hideChrome" class="flex-none" />
|
||||||
<CloseButton v-if="route.meta.showCloseButton" class="flex-none" />
|
<CloseButton v-if="route.meta.showCloseButton" class="flex-none" />
|
||||||
<RouterView v-slot="{ Component }" class="flex-auto">
|
<RouterView v-slot="{ Component }" class="flex-auto">
|
||||||
<Transition mode="out-in" name="app">
|
<Transition mode="out-in" name="app">
|
||||||
|
|
@ -15,13 +15,14 @@
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
|
|
||||||
import AppFooter from "@/components/AppFooter.vue";
|
import AppFooter from "@/components/AppFooter.vue";
|
||||||
import MainNavigationBar from "@/components/header/MainNavigationBar.vue";
|
// import MainNavigationBar from "@/components/header/MainNavigationBar.vue";
|
||||||
import { graphqlClient } from "@/graphql/client";
|
import { graphqlClient } from "@/graphql/client";
|
||||||
import eventBus from "@/utils/eventBus";
|
import eventBus from "@/utils/eventBus";
|
||||||
import { provideClient } from "@urql/vue";
|
import { provideClient } from "@urql/vue";
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import CloseButton from "./components/header/CloseButton.vue";
|
import CloseButton from "./components/header/CloseButton.vue";
|
||||||
|
import HeaderBar from "./components/header/HeaderBar.vue";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
import { useNavigationAttributes } from "@/utils/navigation";
|
||||||
|
import { useRouteLookups } from "@/utils/route";
|
||||||
|
import {
|
||||||
|
getCockpitUrl,
|
||||||
|
getCompetenceNaviUrl,
|
||||||
|
getLearningMentorUrl,
|
||||||
|
getLearningPathUrl,
|
||||||
|
} from "@/utils/utils";
|
||||||
|
import { useTranslation } from "i18next-vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
|
||||||
|
const { inCockpit, inCompetenceProfile, inLearningMentor, inLearningPath } =
|
||||||
|
useRouteLookups();
|
||||||
|
const {
|
||||||
|
hasCompetenceNaviMenu,
|
||||||
|
hasLearningPathMenu,
|
||||||
|
hasCockpitMenu,
|
||||||
|
hasPreviewMenu,
|
||||||
|
hasLearningMentor,
|
||||||
|
} = useNavigationAttributes();
|
||||||
|
const mentorTabTitle = computed(() =>
|
||||||
|
courseSessionsStore.currentCourseSession?.course.configuration.is_uk
|
||||||
|
? "a.Praxisbildner"
|
||||||
|
: "a.Lernbegleitung"
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div v-if="courseSessionsStore.currentCourseSession" class="hidden space-x-8 lg:flex">
|
||||||
|
<router-link
|
||||||
|
v-if="hasCockpitMenu"
|
||||||
|
data-cy="navigation-cockpit-link"
|
||||||
|
:to="getCockpitUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||||
|
class="nav-item"
|
||||||
|
:class="{ 'nav-item--active': inCockpit() }"
|
||||||
|
>
|
||||||
|
{{ t("cockpit.title") }}
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
v-if="hasPreviewMenu"
|
||||||
|
data-cy="navigation-preview-link"
|
||||||
|
:to="getLearningPathUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||||
|
target="_blank"
|
||||||
|
class="nav-item"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span>{{ t("a.Vorschau Teilnehmer") }}</span>
|
||||||
|
<it-icon-external-link class="ml-2" />
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
<router-link
|
||||||
|
v-if="hasLearningPathMenu"
|
||||||
|
data-cy="navigation-learning-path-link"
|
||||||
|
:to="getLearningPathUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||||
|
class="nav-item"
|
||||||
|
:class="{ 'nav-item--active': inLearningPath() }"
|
||||||
|
>
|
||||||
|
{{ t("general.learningPath") }}
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
v-if="hasCompetenceNaviMenu"
|
||||||
|
data-cy="navigation-competence-profile-link"
|
||||||
|
:to="getCompetenceNaviUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||||
|
class="nav-item"
|
||||||
|
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
||||||
|
>
|
||||||
|
{{ t("competences.title") }}
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
v-if="hasLearningMentor"
|
||||||
|
data-cy="navigation-learning-mentor-link"
|
||||||
|
:to="getLearningMentorUrl(courseSessionsStore.currentCourseSession.course.slug)"
|
||||||
|
class="nav-item"
|
||||||
|
:class="{ 'nav-item--active': inLearningMentor() }"
|
||||||
|
>
|
||||||
|
{{ t(mentorTabTitle) }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
import MainNavigationBar from "./MainNavigationBar.vue";
|
||||||
|
|
||||||
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CoursePreviewBar v-if="courseSessionsStore.isCourseSessionPreviewActive" />
|
||||||
|
<template v-else>
|
||||||
|
<MainNavigationBar />
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTranslation } from "i18next-vue";
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="hidden flex-shrink-0 items-center lg:flex">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<router-link to="/" class="flex">
|
||||||
|
<it-icon-vbv class="-ml-3 -mt-6 mr-3 h-8 w-16" />
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/" class="flex">
|
||||||
|
<div class="ml-1 border-l border-white pl-3 pr-10 text-2xl text-white">
|
||||||
|
{{ t("general.title") }}
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
- nav
|
||||||
|
- default
|
||||||
|
- backnav
|
||||||
|
- cockpitnav
|
||||||
|
- course selector
|
||||||
|
- subnav
|
||||||
|
|
||||||
|
- mediathek
|
||||||
|
- calendar
|
||||||
|
- notifications
|
||||||
|
- profile
|
||||||
|
- links
|
||||||
|
|
@ -1,56 +1,22 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AccountMenu from "@/components/header/AccountMenu.vue";
|
|
||||||
import CoursePreviewBar from "@/components/header/CoursePreviewBar.vue";
|
|
||||||
import MobileMenu from "@/components/header/MobileMenu.vue";
|
|
||||||
import NotificationPopover from "@/components/notifications/NotificationPopover.vue";
|
|
||||||
import NotificationPopoverContent from "@/components/notifications/NotificationPopoverContent.vue";
|
|
||||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
|
||||||
import { getLoginURL } from "@/router/utils";
|
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useNotificationsStore } from "@/stores/notifications";
|
import { useNavigationAttributes } from "@/utils/navigation";
|
||||||
import { useUserStore } from "@/stores/user";
|
|
||||||
import { useRouteLookups } from "@/utils/route";
|
import { useRouteLookups } from "@/utils/route";
|
||||||
import {
|
import { getMediaCenterUrl } from "@/utils/utils";
|
||||||
getCockpitUrl,
|
|
||||||
getCompetenceNaviUrl,
|
|
||||||
getLearningMentorUrl,
|
|
||||||
getLearningPathUrl,
|
|
||||||
getMediaCenterUrl,
|
|
||||||
} from "@/utils/utils";
|
|
||||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
|
|
||||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
|
||||||
import { useTranslation } from "i18next-vue";
|
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed, onMounted, reactive } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
|
import CourseSessionNavigation from "./CourseSessionNavigation.vue";
|
||||||
|
import HomeNavigation from "./HomeNavigation.vue";
|
||||||
|
import MobileMenuButton from "./MobileMenuButton.vue";
|
||||||
|
import NotificationButton from "./NotificationButton.vue";
|
||||||
|
import ProfileMenuButton from "./ProfileMenuButton.vue";
|
||||||
|
|
||||||
log.debug("MainNavigationBar created");
|
log.debug("MainNavigationBar created");
|
||||||
|
|
||||||
const breakpoints = useBreakpoints(breakpointsTailwind);
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
const notificationsStore = useNotificationsStore();
|
const { inMediaLibrary, inAppointments } = useRouteLookups();
|
||||||
const {
|
const { hasMediaLibraryMenu, hasAppointmentsMenu, hasSessionTitle } =
|
||||||
inCockpit,
|
useNavigationAttributes();
|
||||||
inCompetenceProfile,
|
|
||||||
inLearningMentor,
|
|
||||||
inCourse,
|
|
||||||
inLearningPath,
|
|
||||||
inMediaLibrary,
|
|
||||||
inAppointments,
|
|
||||||
} = useRouteLookups();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const state = reactive({
|
|
||||||
showMobileNavigationMenu: false,
|
|
||||||
showMobileProfileMenu: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
function popoverClick(event: Event) {
|
|
||||||
if (breakpoints.smaller("lg").value) {
|
|
||||||
event.preventDefault();
|
|
||||||
state.showMobileProfileMenu = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedCourseSessionTitle = computed(() => {
|
const selectedCourseSessionTitle = computed(() => {
|
||||||
return courseSessionsStore.currentCourseSession?.title;
|
return courseSessionsStore.currentCourseSession?.title;
|
||||||
|
|
@ -68,333 +34,59 @@ const appointmentsUrl = computed(() => {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
log.debug("MainNavigationBar mounted");
|
log.debug("MainNavigationBar mounted");
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasLearningPathMenu = computed(() =>
|
|
||||||
Boolean(
|
|
||||||
courseSessionsStore.currentCourseSession?.actions.includes("learning-path") &&
|
|
||||||
inCourse()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasCompetenceNaviMenu = computed(() =>
|
|
||||||
Boolean(
|
|
||||||
courseSessionsStore.currentCourseSession?.actions.includes("competence-navi") &&
|
|
||||||
inCourse()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasMediaLibraryMenu = computed(() =>
|
|
||||||
Boolean(
|
|
||||||
courseSessionsStore.currentCourseSession?.actions.includes("media-library") &&
|
|
||||||
inCourse()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasCockpitMenu = computed(
|
|
||||||
() =>
|
|
||||||
Boolean(
|
|
||||||
courseSessionsStore.currentCourseSession?.actions.includes("expert-cockpit")
|
|
||||||
) && inCourse()
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasPreviewMenu = computed(
|
|
||||||
() =>
|
|
||||||
Boolean(courseSessionsStore.currentCourseSession?.actions.includes("preview")) &&
|
|
||||||
inCourse()
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasAppointmentsMenu = computed(() =>
|
|
||||||
Boolean(
|
|
||||||
courseSessionsStore.currentCourseSession?.actions.includes("appointments") &&
|
|
||||||
userStore.loggedIn &&
|
|
||||||
inCourse()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasNotificationsMenu = computed(() => {
|
|
||||||
return userStore.loggedIn;
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasLearningMentor = computed(() => {
|
|
||||||
if (!inCourse()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!courseSessionsStore.currentCourseSession) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const courseSession = courseSessionsStore.currentCourseSession;
|
|
||||||
return courseSession.actions.includes("learning-mentor");
|
|
||||||
});
|
|
||||||
|
|
||||||
const mentorTabTitle = computed(() =>
|
|
||||||
courseSessionsStore.currentCourseSession?.course.configuration.is_uk
|
|
||||||
? "a.Praxisbildner"
|
|
||||||
: "a.Lernbegleitung"
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasSessionTitle = computed(() => {
|
|
||||||
return courseSessionsStore.currentCourseSession?.title && inCourse();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CoursePreviewBar v-if="courseSessionsStore.isCourseSessionPreviewActive" />
|
<nav class="bg-blue-900 text-white">
|
||||||
<div v-else>
|
<div class="mx-auto px-4 lg:px-8">
|
||||||
<Teleport to="body">
|
<div class="relative flex h-16 justify-between">
|
||||||
<MobileMenu
|
<MobileMenuButton />
|
||||||
v-if="userStore.loggedIn"
|
<div class="flex flex-1 items-stretch justify-start">
|
||||||
:show="state.showMobileNavigationMenu"
|
<HomeNavigation />
|
||||||
:course-session="courseSessionsStore.currentCourseSession"
|
<CourseSessionNavigation />
|
||||||
:has-media-library-menu="hasMediaLibraryMenu"
|
</div>
|
||||||
:has-cockpit-menu="hasCockpitMenu"
|
|
||||||
:has-preview-menu="hasPreviewMenu"
|
|
||||||
:has-learning-path-menu="hasLearningPathMenu"
|
|
||||||
:has-competence-navi-menu="hasCompetenceNaviMenu"
|
|
||||||
:has-learning-mentor="hasLearningMentor"
|
|
||||||
:has-notifications-menu="hasNotificationsMenu"
|
|
||||||
:has-appointments-menu="hasAppointmentsMenu"
|
|
||||||
:media-url="
|
|
||||||
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course?.slug)
|
|
||||||
"
|
|
||||||
:user="userStore"
|
|
||||||
@closemodal="state.showMobileNavigationMenu = false"
|
|
||||||
@logout="userStore.handleLogout()"
|
|
||||||
/>
|
|
||||||
</Teleport>
|
|
||||||
<Teleport to="body">
|
|
||||||
<ItFullScreenModal
|
|
||||||
v-if="userStore.loggedIn"
|
|
||||||
:show="state.showMobileProfileMenu"
|
|
||||||
@closemodal="state.showMobileProfileMenu = false"
|
|
||||||
>
|
|
||||||
<AccountMenu @close="state.showMobileProfileMenu = false" />
|
|
||||||
</ItFullScreenModal>
|
|
||||||
</Teleport>
|
|
||||||
<Transition name="nav">
|
|
||||||
<nav class="bg-blue-900 text-white">
|
|
||||||
<div class="mx-auto px-4 lg:px-8">
|
|
||||||
<div class="relative flex h-16 justify-between">
|
|
||||||
<div class="absolute inset-y-0 left-0 flex items-center lg:hidden">
|
|
||||||
<!-- Mobile menu button -->
|
|
||||||
<div
|
|
||||||
data-cy="navigation-mobile-menu-button"
|
|
||||||
class="flex"
|
|
||||||
@click="state.showMobileNavigationMenu = true"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="h-8 w-8 text-white hover:text-sky-500 focus:text-sky-500 focus:outline-none"
|
|
||||||
>
|
|
||||||
<it-icon-menu class="h-8 w-8" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-1 items-stretch justify-start">
|
<div class="flex items-stretch justify-start space-x-8">
|
||||||
<div class="hidden flex-shrink-0 items-center lg:flex">
|
<router-link
|
||||||
<div class="flex items-center">
|
v-if="hasMediaLibraryMenu"
|
||||||
<router-link to="/" class="flex">
|
:to="
|
||||||
<it-icon-vbv class="-ml-3 -mt-6 mr-3 h-8 w-16" />
|
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course.slug)
|
||||||
</router-link>
|
"
|
||||||
<router-link to="/" class="flex">
|
data-cy="medialibrary-link"
|
||||||
<div
|
class="nav-item-no-mobile"
|
||||||
class="ml-1 border-l border-white pl-3 pr-10 text-2xl text-white"
|
:class="{ 'nav-item--active': inMediaLibrary() }"
|
||||||
>
|
>
|
||||||
{{ t("general.title") }}
|
<it-icon-media-library class="h-8 w-8" />
|
||||||
</div>
|
</router-link>
|
||||||
</router-link>
|
<router-link
|
||||||
</div>
|
v-if="hasAppointmentsMenu"
|
||||||
</div>
|
:to="appointmentsUrl"
|
||||||
|
data-cy="all-duedates-link"
|
||||||
|
class="nav-item"
|
||||||
|
:class="{ 'nav-item--active': inAppointments() }"
|
||||||
|
>
|
||||||
|
<it-icon-calendar-light class="h-8 w-8" />
|
||||||
|
</router-link>
|
||||||
|
|
||||||
<!-- Satisfy the type checker; these menu items are
|
<!-- Notification Bell & Menu -->
|
||||||
only relevant if there is a current course session -->
|
<NotificationButton />
|
||||||
<template v-if="courseSessionsStore.currentCourseSession">
|
|
||||||
<div class="hidden space-x-8 lg:flex">
|
|
||||||
<router-link
|
|
||||||
v-if="hasCockpitMenu"
|
|
||||||
data-cy="navigation-cockpit-link"
|
|
||||||
:to="
|
|
||||||
getCockpitUrl(
|
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="nav-item"
|
|
||||||
:class="{ 'nav-item--active': inCockpit() }"
|
|
||||||
>
|
|
||||||
{{ t("cockpit.title") }}
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<router-link
|
<div
|
||||||
v-if="hasPreviewMenu"
|
v-if="hasSessionTitle"
|
||||||
data-cy="navigation-preview-link"
|
class="nav-item hidden items-center lg:inline-flex"
|
||||||
:to="
|
>
|
||||||
getLearningPathUrl(
|
<div class="" data-cy="current-course-session-title">
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
{{ selectedCourseSessionTitle }}
|
||||||
)
|
|
||||||
"
|
|
||||||
target="_blank"
|
|
||||||
class="nav-item"
|
|
||||||
>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span>{{ t("a.Vorschau Teilnehmer") }}</span>
|
|
||||||
<it-icon-external-link class="ml-2" />
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
<router-link
|
|
||||||
v-if="hasLearningPathMenu"
|
|
||||||
data-cy="navigation-learning-path-link"
|
|
||||||
:to="
|
|
||||||
getLearningPathUrl(
|
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="nav-item"
|
|
||||||
:class="{ 'nav-item--active': inLearningPath() }"
|
|
||||||
>
|
|
||||||
{{ t("general.learningPath") }}
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<router-link
|
|
||||||
v-if="hasCompetenceNaviMenu"
|
|
||||||
data-cy="navigation-competence-profile-link"
|
|
||||||
:to="
|
|
||||||
getCompetenceNaviUrl(
|
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="nav-item"
|
|
||||||
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
|
||||||
>
|
|
||||||
{{ t("competences.title") }}
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<router-link
|
|
||||||
v-if="hasLearningMentor"
|
|
||||||
data-cy="navigation-learning-mentor-link"
|
|
||||||
:to="
|
|
||||||
getLearningMentorUrl(
|
|
||||||
courseSessionsStore.currentCourseSession.course.slug
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="nav-item"
|
|
||||||
:class="{ 'nav-item--active': inLearningMentor() }"
|
|
||||||
>
|
|
||||||
{{ t(mentorTabTitle) }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-stretch justify-start space-x-8">
|
|
||||||
<router-link
|
|
||||||
v-if="hasMediaLibraryMenu"
|
|
||||||
:to="
|
|
||||||
getMediaCenterUrl(
|
|
||||||
courseSessionsStore.currentCourseSession?.course.slug
|
|
||||||
)
|
|
||||||
"
|
|
||||||
data-cy="medialibrary-link"
|
|
||||||
class="nav-item-no-mobile"
|
|
||||||
:class="{ 'nav-item--active': inMediaLibrary() }"
|
|
||||||
>
|
|
||||||
<it-icon-media-library class="h-8 w-8" />
|
|
||||||
</router-link>
|
|
||||||
<router-link
|
|
||||||
v-if="hasAppointmentsMenu"
|
|
||||||
:to="appointmentsUrl"
|
|
||||||
data-cy="all-duedates-link"
|
|
||||||
class="nav-item"
|
|
||||||
:class="{ 'nav-item--active': inAppointments() }"
|
|
||||||
>
|
|
||||||
<it-icon-calendar-light class="h-8 w-8" />
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<!-- Notification Bell & Menu -->
|
|
||||||
<div v-if="hasNotificationsMenu" class="nav-item leading-none">
|
|
||||||
<NotificationPopover>
|
|
||||||
<template #toggleButtonContent>
|
|
||||||
<div class="relative h-8 w-8">
|
|
||||||
<div>
|
|
||||||
<it-icon-notification class="h-8 w-8" />
|
|
||||||
<div
|
|
||||||
v-if="notificationsStore.hasUnread"
|
|
||||||
aria-label="unread notifications"
|
|
||||||
class="absolute inset-y-0 right-0 h-1.5 w-1.5 rounded-full bg-sky-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #popoverContent>
|
|
||||||
<NotificationPopoverContent />
|
|
||||||
</template>
|
|
||||||
</NotificationPopover>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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
|
|
||||||
v-if="userStore.loggedIn"
|
|
||||||
class="flex items-center"
|
|
||||||
data-cy="header-profile"
|
|
||||||
>
|
|
||||||
<Popover class="relative">
|
|
||||||
<PopoverButton @click="popoverClick($event)">
|
|
||||||
<div v-if="userStore.avatar_url">
|
|
||||||
<img
|
|
||||||
class="inline-block h-8 w-8 rounded-full"
|
|
||||||
:src="userStore.avatar_url"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
{{ userStore.getFullName }}
|
|
||||||
</div>
|
|
||||||
</PopoverButton>
|
|
||||||
|
|
||||||
<PopoverPanel
|
|
||||||
v-slot="{ close }"
|
|
||||||
class="absolute -right-2 top-8 z-50 w-[500px] bg-white shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="p-4">
|
|
||||||
<AccountMenu @close="close" />
|
|
||||||
</div>
|
|
||||||
</PopoverPanel>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<a class="" :href="getLoginURL({ lang: userStore.language })">
|
|
||||||
Login
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-item">
|
||||||
|
<ProfileMenuButton />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</div>
|
||||||
</Transition>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
<style lang="postcss"></style>
|
||||||
.nav-item {
|
|
||||||
@apply inline-flex items-center border-b-4 border-transparent px-1 pt-1 text-white hover:text-sky-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item-no-mobile {
|
|
||||||
@apply hidden items-center border-b-4 border-transparent px-1 pt-1 text-white hover:text-sky-500 lg:inline-flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item--active {
|
|
||||||
@apply border-sky-500;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { useNavigationAttributes } from "@/utils/navigation";
|
||||||
|
import { getMediaCenterUrl } from "@/utils/utils";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import MobileMenu from "./MobileMenu.vue";
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
|
||||||
|
const {
|
||||||
|
hasCompetenceNaviMenu,
|
||||||
|
hasLearningPathMenu,
|
||||||
|
hasMediaLibraryMenu,
|
||||||
|
hasCockpitMenu,
|
||||||
|
hasPreviewMenu,
|
||||||
|
hasAppointmentsMenu,
|
||||||
|
hasNotificationsMenu,
|
||||||
|
hasLearningMentor,
|
||||||
|
} = useNavigationAttributes();
|
||||||
|
|
||||||
|
const showMenu = ref(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<MobileMenu
|
||||||
|
v-if="userStore.loggedIn"
|
||||||
|
:show="showMenu"
|
||||||
|
:course-session="courseSessionsStore.currentCourseSession"
|
||||||
|
:has-media-library-menu="hasMediaLibraryMenu"
|
||||||
|
:has-cockpit-menu="hasCockpitMenu"
|
||||||
|
:has-preview-menu="hasPreviewMenu"
|
||||||
|
:has-learning-path-menu="hasLearningPathMenu"
|
||||||
|
:has-competence-navi-menu="hasCompetenceNaviMenu"
|
||||||
|
:has-learning-mentor="hasLearningMentor"
|
||||||
|
:has-notifications-menu="hasNotificationsMenu"
|
||||||
|
:has-appointments-menu="hasAppointmentsMenu"
|
||||||
|
:media-url="
|
||||||
|
getMediaCenterUrl(courseSessionsStore.currentCourseSession?.course?.slug)
|
||||||
|
"
|
||||||
|
:user="userStore"
|
||||||
|
@closemodal="showMenu = false"
|
||||||
|
@logout="userStore.handleLogout()"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<div class="absolute inset-y-0 left-0 flex items-center lg:hidden">
|
||||||
|
<!-- Mobile menu button -->
|
||||||
|
<div data-cy="navigation-mobile-menu-button" class="flex" @click="showMenu = true">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="h-8 w-8 text-white hover:text-sky-500 focus:text-sky-500 focus:outline-none"
|
||||||
|
>
|
||||||
|
<it-icon-menu class="h-8 w-8" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NotificationPopover from "@/components/notifications/NotificationPopover.vue";
|
||||||
|
import NotificationPopoverContent from "@/components/notifications/NotificationPopoverContent.vue";
|
||||||
|
import { useNotificationsStore } from "@/stores/notifications";
|
||||||
|
import { useNavigationAttributes } from "@/utils/navigation";
|
||||||
|
|
||||||
|
const notificationsStore = useNotificationsStore();
|
||||||
|
const { hasNotificationsMenu } = useNavigationAttributes();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="hasNotificationsMenu" class="nav-item leading-none">
|
||||||
|
<NotificationPopover>
|
||||||
|
<template #toggleButtonContent>
|
||||||
|
<div class="relative h-8 w-8">
|
||||||
|
<div>
|
||||||
|
<it-icon-notification class="h-8 w-8" />
|
||||||
|
<div
|
||||||
|
v-if="notificationsStore.hasUnread"
|
||||||
|
aria-label="unread notifications"
|
||||||
|
class="absolute inset-y-0 right-0 h-1.5 w-1.5 rounded-full bg-sky-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #popoverContent>
|
||||||
|
<NotificationPopoverContent />
|
||||||
|
</template>
|
||||||
|
</NotificationPopover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import AccountMenu from "@/components/header/AccountMenu.vue";
|
||||||
|
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||||
|
import { getLoginURL } from "@/router/utils";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
|
||||||
|
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const breakpoints = useBreakpoints(breakpointsTailwind);
|
||||||
|
|
||||||
|
const showMenu = ref(false);
|
||||||
|
|
||||||
|
function popoverClick(event: Event) {
|
||||||
|
if (breakpoints.smaller("lg").value) {
|
||||||
|
event.preventDefault();
|
||||||
|
showMenu.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<ItFullScreenModal
|
||||||
|
v-if="userStore.loggedIn"
|
||||||
|
:show="showMenu"
|
||||||
|
@closemodal="showMenu = false"
|
||||||
|
>
|
||||||
|
<AccountMenu @close="showMenu = false" />
|
||||||
|
</ItFullScreenModal>
|
||||||
|
</Teleport>
|
||||||
|
<div v-if="userStore.loggedIn" class="flex items-center" data-cy="header-profile">
|
||||||
|
<Popover class="relative">
|
||||||
|
<PopoverButton @click="popoverClick($event)">
|
||||||
|
<div v-if="userStore.avatar_url">
|
||||||
|
<img
|
||||||
|
class="inline-block h-8 w-8 rounded-full"
|
||||||
|
:src="userStore.avatar_url"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{{ userStore.getFullName }}
|
||||||
|
</div>
|
||||||
|
</PopoverButton>
|
||||||
|
|
||||||
|
<PopoverPanel
|
||||||
|
v-slot="{ close }"
|
||||||
|
class="absolute -right-2 top-8 z-50 w-[500px] bg-white shadow-lg"
|
||||||
|
>
|
||||||
|
<div class="p-4">
|
||||||
|
<AccountMenu @close="close" />
|
||||||
|
</div>
|
||||||
|
</PopoverPanel>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<a class="" :href="getLoginURL({ lang: userStore.language })">Login</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useRouteLookups } from "./route";
|
||||||
|
|
||||||
|
export function useNavigationAttributes() {
|
||||||
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { inCourse } = useRouteLookups();
|
||||||
|
|
||||||
|
const hasCompetenceNaviMenu = computed(() =>
|
||||||
|
Boolean(
|
||||||
|
courseSessionsStore.currentCourseSession?.actions.includes("competence-navi") &&
|
||||||
|
inCourse()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const hasLearningPathMenu = computed(() =>
|
||||||
|
Boolean(
|
||||||
|
courseSessionsStore.currentCourseSession?.actions.includes("learning-path") &&
|
||||||
|
inCourse()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const hasMediaLibraryMenu = computed(() =>
|
||||||
|
Boolean(
|
||||||
|
courseSessionsStore.currentCourseSession?.actions.includes("media-library") &&
|
||||||
|
inCourse()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const hasCockpitMenu = computed(
|
||||||
|
() =>
|
||||||
|
Boolean(
|
||||||
|
courseSessionsStore.currentCourseSession?.actions.includes("expert-cockpit")
|
||||||
|
) && inCourse()
|
||||||
|
);
|
||||||
|
const hasPreviewMenu = computed(
|
||||||
|
() =>
|
||||||
|
Boolean(courseSessionsStore.currentCourseSession?.actions.includes("preview")) &&
|
||||||
|
inCourse()
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasAppointmentsMenu = computed(() =>
|
||||||
|
Boolean(
|
||||||
|
courseSessionsStore.currentCourseSession?.actions.includes("appointments") &&
|
||||||
|
userStore.loggedIn &&
|
||||||
|
inCourse()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasNotificationsMenu = computed(() => {
|
||||||
|
return userStore.loggedIn;
|
||||||
|
});
|
||||||
|
const hasLearningMentor = computed(() => {
|
||||||
|
if (!inCourse()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!courseSessionsStore.currentCourseSession) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseSession = courseSessionsStore.currentCourseSession;
|
||||||
|
return courseSession.actions.includes("learning-mentor");
|
||||||
|
});
|
||||||
|
const hasSessionTitle = computed(() => {
|
||||||
|
return courseSessionsStore.currentCourseSession?.title && inCourse();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasCompetenceNaviMenu,
|
||||||
|
hasLearningPathMenu,
|
||||||
|
hasMediaLibraryMenu,
|
||||||
|
hasCockpitMenu,
|
||||||
|
hasPreviewMenu,
|
||||||
|
hasAppointmentsMenu,
|
||||||
|
hasNotificationsMenu,
|
||||||
|
hasLearningMentor,
|
||||||
|
hasSessionTitle,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -175,6 +175,18 @@ textarea {
|
||||||
.tag-active {
|
.tag-active {
|
||||||
@apply rounded-full bg-blue-900 px-4 py-2 font-semibold text-white;
|
@apply rounded-full bg-blue-900 px-4 py-2 font-semibold text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
@apply inline-flex items-center border-b-4 border-transparent px-1 pt-1 text-white hover:text-sky-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-no-mobile {
|
||||||
|
@apply hidden items-center border-b-4 border-transparent px-1 pt-1 text-white hover:text-sky-500 lg:inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item--active {
|
||||||
|
@apply border-sky-500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue