VBV-302: Refactor Header for course session switching
This commit is contained in:
parent
724f31f4a8
commit
18f7728793
|
|
@ -14,7 +14,7 @@
|
|||
import log from "loglevel";
|
||||
|
||||
import AppFooter from "@/components/AppFooter.vue";
|
||||
import MainNavigationBar from "@/components/MainNavigationBar.vue";
|
||||
import MainNavigationBar from "@/components/header/MainNavigationBar.vue";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
log.debug("App created");
|
||||
|
|
|
|||
|
|
@ -1,324 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import log from "loglevel";
|
||||
|
||||
import IconLogout from "@/components/icons/IconLogout.vue";
|
||||
import IconSettings from "@/components/icons/IconSettings.vue";
|
||||
import MobileMenu from "@/components/MobileMenu.vue";
|
||||
import NotificationPopover from "@/components/notifications/NotificationPopover.vue";
|
||||
import NotificationPopoverContent from "@/components/notifications/NotificationPopoverContent.vue";
|
||||
import ItDropdown from "@/components/ui/ItDropdown.vue";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useNotificationsStore } from "@/stores/notifications";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import type { DropdownListItem } from "@/types";
|
||||
import type { Component } from "vue";
|
||||
import { computed, onMounted, reactive } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
type DropdownActions = "logout" | "settings" | "profile";
|
||||
|
||||
interface DropdownData {
|
||||
action: DropdownActions;
|
||||
}
|
||||
|
||||
log.debug("MainNavigationBar created");
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const notificationsStore = useNotificationsStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const state = reactive({ showMenu: false });
|
||||
|
||||
function toggleNav() {
|
||||
state.showMenu = !state.showMenu;
|
||||
}
|
||||
|
||||
function inCourse() {
|
||||
return route.path.startsWith("/course/");
|
||||
}
|
||||
|
||||
function inCockpit() {
|
||||
const regex = new RegExp("/course/[^/]+/cockpit");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inLearningPath() {
|
||||
const regex = new RegExp("/course/[^/]+/learn");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inCompetenceProfile() {
|
||||
const regex = new RegExp("/course/[^/]+/competence");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inMediaLibrary() {
|
||||
return route.path.startsWith("/media/");
|
||||
}
|
||||
|
||||
function handleDropdownSelect(data: DropdownData) {
|
||||
switch (data.action) {
|
||||
case "profile":
|
||||
router.push("/profile");
|
||||
break;
|
||||
case "settings":
|
||||
router.push("/settings");
|
||||
break;
|
||||
case "logout":
|
||||
userStore.handleLogout();
|
||||
break;
|
||||
default:
|
||||
console.log("No action");
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
userStore.handleLogout();
|
||||
}
|
||||
|
||||
const selectedCourseSessionTitle = computed(() => {
|
||||
return courseSessionsStore.courseSessionForRoute?.title;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
log.debug("MainNavigationBar mounted");
|
||||
if (userStore.loggedIn) {
|
||||
// fixme: only when i'm logged in? should this be handled in the store?
|
||||
// courseSessionsStore.loadCourseSessionsData();
|
||||
}
|
||||
});
|
||||
|
||||
const profileDropdownData: DropdownListItem[] = [
|
||||
{
|
||||
title: t("mainNavigation.profile"),
|
||||
icon: IconSettings as Component,
|
||||
data: {
|
||||
action: "profile",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("general.settings"),
|
||||
icon: IconSettings as Component,
|
||||
data: {
|
||||
action: "settings",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("mainNavigation.logout"),
|
||||
icon: IconLogout as Component,
|
||||
data: {
|
||||
action: "logout",
|
||||
},
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Teleport to="body">
|
||||
<MobileMenu
|
||||
v-if="userStore.loggedIn"
|
||||
:show="state.showMenu"
|
||||
:course-session="courseSessionsStore.courseSessionForRoute"
|
||||
:media-url="courseSessionsStore.courseSessionForRoute?.media_library_url"
|
||||
:user="userStore"
|
||||
@closemodal="state.showMenu = false"
|
||||
@logout="userStore.handleLogout()"
|
||||
/>
|
||||
</Teleport>
|
||||
<Transition name="nav">
|
||||
<div v-if="appStore.showMainNavigationBar" class="navigation bg-blue-900">
|
||||
<nav class="mx-auto px-8 py-2 lg:flex lg:items-center lg:justify-start lg:py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<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 class="flex items-center lg:hidden">
|
||||
<div v-if="userStore.loggedIn" class="mr-6 flex flex-row items-center">
|
||||
<NotificationPopover>
|
||||
<template #toggleButtonContent>
|
||||
<div class="nav-item flex">
|
||||
<it-icon-notification class="h-6 w-6" />
|
||||
<div
|
||||
v-if="notificationsStore.hasUnread"
|
||||
aria-label="unread notifications"
|
||||
class="mt-1 h-1.5 w-1.5 rounded-full bg-sky-500"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #popoverContent>
|
||||
<NotificationPopoverContent />
|
||||
</template>
|
||||
</NotificationPopover>
|
||||
</div>
|
||||
<router-link
|
||||
v-if="userStore.loggedIn"
|
||||
to="/messages"
|
||||
class="nav-item flex flex-row items-center"
|
||||
data-cy="messages-link"
|
||||
>
|
||||
<it-icon-persons class="mr-6 h-6 w-6" />
|
||||
</router-link>
|
||||
<!-- Mobile menu button -->
|
||||
<div class="flex" @click="toggleNav">
|
||||
<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>
|
||||
|
||||
<!-- Mobile Menu open: "block", Menu closed: "hidden" -->
|
||||
<div
|
||||
v-if="appStore.userLoaded && appStore.routingFinished && userStore.loggedIn"
|
||||
:class="state.showMenu ? 'flex' : 'hidden'"
|
||||
class="mt-8 flex-auto lg:mt-0 lg:flex lg:flex-row lg:items-center lg:space-x-10 lg:space-y-0"
|
||||
>
|
||||
<!-- <router-link-->
|
||||
<!-- v-if="inCourse() && courseSessionsStore.courseSessionForRoute"-->
|
||||
<!-- :to="`${courseSessionsStore.courseSessionForRoute.course_url}/cockpit`"-->
|
||||
<!-- class="nav-item"-->
|
||||
<!-- :class="{ 'nav-item--active': inCockpit() }"-->
|
||||
<!-- >-->
|
||||
<!-- Cockpit-->
|
||||
<!-- </router-link>-->
|
||||
|
||||
<router-link
|
||||
v-if="
|
||||
inCourse() &&
|
||||
courseSessionsStore.courseSessionForRoute &&
|
||||
courseSessionsStore.hasCockpit
|
||||
"
|
||||
:to="`${courseSessionsStore.courseSessionForRoute.course_url}/cockpit`"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCockpit() }"
|
||||
>
|
||||
{{ $t("cockpit.title") }}
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
|
||||
:to="courseSessionsStore.courseSessionForRoute.learning_path_url"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inLearningPath() }"
|
||||
>
|
||||
{{ $t("general.learningPath") }}
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
|
||||
:to="courseSessionsStore.courseSessionForRoute.competence_url"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
||||
>
|
||||
{{ $t("competences.title") }}
|
||||
</router-link>
|
||||
|
||||
<div class="hidden flex-auto lg:block"></div>
|
||||
<a
|
||||
class="nav-item"
|
||||
target="_blank"
|
||||
href="https://bildung.vbv.ch/ilp/pages/catalogsearch.jsf"
|
||||
>
|
||||
{{ $t("general.shop") }}
|
||||
</a>
|
||||
<router-link
|
||||
v-if="courseSessionsStore.courseSessionForRoute"
|
||||
:to="courseSessionsStore.courseSessionForRoute.media_library_url"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inMediaLibrary() }"
|
||||
data-cy="medialibrary-link"
|
||||
>
|
||||
{{ $t("mediaLibrary.title") }}
|
||||
</router-link>
|
||||
<div v-if="userStore.loggedIn" class="mr-6 flex items-center">
|
||||
<NotificationPopover>
|
||||
<template #toggleButtonContent>
|
||||
<div class="nav-item flex">
|
||||
<it-icon-notification class="h-6 w-6" />
|
||||
<div
|
||||
v-if="notificationsStore.hasUnread"
|
||||
aria-label="unread notifications"
|
||||
class="mt-1 h-1.5 w-1.5 rounded-full bg-sky-500"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #popoverContent>
|
||||
<NotificationPopoverContent />
|
||||
</template>
|
||||
</NotificationPopover>
|
||||
</div>
|
||||
<router-link
|
||||
to="/messages"
|
||||
class="nav-item flex flex-row items-center"
|
||||
data-cy="messages-link"
|
||||
>
|
||||
<it-icon-persons class="mr-6 h-6 w-6" />
|
||||
</router-link>
|
||||
<div v-if="userStore.loggedIn" class="nav-item flex items-center">
|
||||
<div v-if="selectedCourseSessionTitle">
|
||||
{{ selectedCourseSessionTitle }}
|
||||
</div>
|
||||
<ItDropdown
|
||||
:button-classes="[]"
|
||||
:list-items="profileDropdownData"
|
||||
:align="'right'"
|
||||
@select="handleDropdownSelect"
|
||||
>
|
||||
<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>
|
||||
</ItDropdown>
|
||||
</div>
|
||||
<div v-else><a class="" href="/login">Login</a></div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.nav-item {
|
||||
@apply text-2xl font-bold text-white hover:text-sky-500 lg:text-base lg:font-normal;
|
||||
}
|
||||
|
||||
.nav-item--active {
|
||||
@apply underline decoration-sky-500 decoration-4 underline-offset-[21px];
|
||||
}
|
||||
|
||||
.nav-enter-active,
|
||||
.nav-leave-active {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-enter-from,
|
||||
.nav-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-80px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// @ts-nocheck
|
||||
import avatar from "../../../.storybook/uk1.patrizia.huggel.jpg";
|
||||
|
||||
import AccountMenuContent from "@/components/header/AccountMenuContent.vue";
|
||||
import type { Meta, StoryObj } from "@storybook/vue3";
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/vue/writing-stories/introduction
|
||||
const meta: Meta<typeof AccountMenuContent> = {
|
||||
title: "VBV/Header/AccountMenuContent",
|
||||
component: AccountMenuContent,
|
||||
tags: ["autodocs"],
|
||||
argTypes: { onClosemodal: { action: "closeModal" } },
|
||||
parameters: {
|
||||
viewport: {
|
||||
defaultViewport: "mobile1",
|
||||
},
|
||||
},
|
||||
decorators: [() => ({ template: '<div class=""><story /></div>' })],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AccountMenuContent>;
|
||||
|
||||
export const DefaultStory: Story = {
|
||||
args: {
|
||||
show: true,
|
||||
courseSessions: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Bern 2023 a",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Zürich 2023 a",
|
||||
},
|
||||
],
|
||||
user: {
|
||||
first_name: "Vreni",
|
||||
last_name: "Schmid",
|
||||
email: "vreni.schmid@example.com",
|
||||
avatar_url: avatar,
|
||||
loggedIn: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<script setup lang="ts">
|
||||
import IconLogout from "@/components/icons/IconLogout.vue";
|
||||
import type { UserState } from "@/stores/user";
|
||||
import type { CourseSession } from "@/types";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const props = defineProps<{
|
||||
courseSessions: CourseSession[];
|
||||
user: UserState | undefined;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits(["closemodal", "logout"]);
|
||||
|
||||
const clickLink = (to: string | undefined) => {
|
||||
if (to) {
|
||||
router.push(to);
|
||||
emits("closemodal");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-black">
|
||||
<div v-if="user?.loggedIn" class="border-b py-4">
|
||||
<div class="flex justify-start">
|
||||
<div v-if="user?.avatar_url">
|
||||
<img
|
||||
class="inline-block h-20 w-20 rounded-full"
|
||||
:src="user?.avatar_url"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-6">
|
||||
<h3>{{ user?.first_name }} {{ user?.last_name }}</h3>
|
||||
<div class="text-sm text-gray-800">{{ user?.email }}</div>
|
||||
<div class="text-sm text-gray-800">
|
||||
<router-link class="link" to="/profile">Profil anzeigen</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="props.courseSessions.length" class="border-b py-4">
|
||||
<div v-for="cs in props.courseSessions" :key="cs.id">
|
||||
{{ cs.title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="user?.loggedIn"
|
||||
type="button"
|
||||
class="mt-6 flex items-center"
|
||||
@click="$emit('logout')"
|
||||
>
|
||||
<IconLogout class="inline-block" />
|
||||
<span class="ml-1">{{ $t("mainNavigation.logout") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
<script setup lang="ts">
|
||||
import log from "loglevel";
|
||||
|
||||
import AccountMenuContent from "@/components/header/AccountMenuContent.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 { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||
import { useNotificationsStore } from "@/stores/notifications";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
|
||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
|
||||
import { computed, onMounted, reactive } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
log.debug("MainNavigationBar created");
|
||||
|
||||
const route = useRoute();
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind);
|
||||
const userStore = useUserStore();
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
const notificationsStore = useNotificationsStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const state = reactive({
|
||||
showMobileNavigationMenu: false,
|
||||
showMobileProfileMenu: false,
|
||||
});
|
||||
|
||||
function inCourse() {
|
||||
return route.path.startsWith("/course/");
|
||||
}
|
||||
|
||||
function inCockpit() {
|
||||
const regex = new RegExp("/course/[^/]+/cockpit");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inLearningPath() {
|
||||
const regex = new RegExp("/course/[^/]+/learn");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inCompetenceProfile() {
|
||||
const regex = new RegExp("/course/[^/]+/competence");
|
||||
return regex.test(route.path);
|
||||
}
|
||||
|
||||
function inMediaLibrary() {
|
||||
return route.path.startsWith("/media/");
|
||||
}
|
||||
|
||||
function logout() {
|
||||
userStore.handleLogout();
|
||||
}
|
||||
|
||||
function popoverClick(event) {
|
||||
if (breakpoints.smaller("lg").value) {
|
||||
event.preventDefault();
|
||||
state.showMobileProfileMenu = true;
|
||||
}
|
||||
}
|
||||
|
||||
const selectedCourseSessionTitle = computed(() => {
|
||||
return courseSessionsStore.courseSessionForRoute?.title;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
log.debug("MainNavigationBar mounted");
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Teleport to="body">
|
||||
<MobileMenu
|
||||
v-if="userStore.loggedIn"
|
||||
:show="state.showMobileNavigationMenu"
|
||||
:course-session="courseSessionsStore.courseSessionForRoute"
|
||||
:media-url="courseSessionsStore.courseSessionForRoute?.media_library_url"
|
||||
: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"
|
||||
>
|
||||
<AccountMenuContent
|
||||
:course-sessions="courseSessionsStore.courseSessionsForRoute"
|
||||
:user="userStore"
|
||||
@logout="logout"
|
||||
/>
|
||||
</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 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="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>
|
||||
|
||||
<div class="hidden space-x-8 lg:flex">
|
||||
<router-link
|
||||
v-if="
|
||||
inCourse() &&
|
||||
courseSessionsStore.courseSessionForRoute &&
|
||||
courseSessionsStore.hasCockpit
|
||||
"
|
||||
:to="`${courseSessionsStore.courseSessionForRoute.course_url}/cockpit`"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCockpit() }"
|
||||
>
|
||||
{{ $t("cockpit.title") }}
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
|
||||
:to="courseSessionsStore.courseSessionForRoute.learning_path_url"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inLearningPath() }"
|
||||
>
|
||||
{{ $t("general.learningPath") }}
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
|
||||
:to="courseSessionsStore.courseSessionForRoute.competence_url"
|
||||
class="nav-item"
|
||||
:class="{ 'nav-item--active': inCompetenceProfile() }"
|
||||
>
|
||||
{{ $t("competences.title") }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-stretch justify-start space-x-8">
|
||||
<div v-if="userStore.loggedIn" class="nav-item">
|
||||
<NotificationPopover>
|
||||
<template #toggleButtonContent>
|
||||
<div class="flex">
|
||||
<it-icon-notification class="h-6 w-6" />
|
||||
<div
|
||||
v-if="notificationsStore.hasUnread"
|
||||
aria-label="unread notifications"
|
||||
class="mt-1 h-1.5 w-1.5 rounded-full bg-sky-500"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #popoverContent>
|
||||
<NotificationPopoverContent />
|
||||
</template>
|
||||
</NotificationPopover>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="selectedCourseSessionTitle"
|
||||
class="hidden items-center lg:inline-flex"
|
||||
>
|
||||
<div class="">
|
||||
{{ selectedCourseSessionTitle }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav-item">
|
||||
<div v-if="userStore.loggedIn" class="flex items-center">
|
||||
<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
|
||||
class="absolute -right-2 top-8 z-50 w-[500px] bg-white shadow-lg"
|
||||
>
|
||||
<div class="p-4">
|
||||
<AccountMenuContent
|
||||
:course-sessions="courseSessionsStore.courseSessionsForRoute"
|
||||
:user="userStore"
|
||||
@logout="logout"
|
||||
/>
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
</div>
|
||||
<div v-else><a class="" href="/login">Login</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.nav-item {
|
||||
@apply inline-flex items-center border-b-4 border-transparent px-1 pt-1 text-white hover:text-sky-500;
|
||||
}
|
||||
|
||||
.nav-item--active {
|
||||
@apply border-sky-500;
|
||||
}
|
||||
|
||||
.nav-enter-active,
|
||||
.nav-leave-active {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-enter-from,
|
||||
.nav-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-80px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
// @ts-nocheck
|
||||
import avatar from "../../.storybook/uk1.patrizia.huggel.jpg";
|
||||
import avatar from "../../../.storybook/uk1.patrizia.huggel.jpg";
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/vue3";
|
||||
import MobileMenu from "./MobileMenu.vue";
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/vue/writing-stories/introduction
|
||||
const meta: Meta<typeof MobileMenu> = {
|
||||
title: "VBV/MobileMenu",
|
||||
title: "VBV/Header/MobileMenu",
|
||||
component: MobileMenu,
|
||||
tags: ["autodocs"],
|
||||
argTypes: { onClosemodal: { action: "closeModal" } },
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import IconLogout from "@/components/icons/IconLogout.vue";
|
||||
import IconSettings from "@/components/icons/IconSettings.vue";
|
||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||
import type { UserState } from "@/stores/user";
|
||||
import type { CourseSession } from "@/types";
|
||||
|
|
@ -44,7 +43,7 @@ const clickLink = (to: string | undefined) => {
|
|||
class="mt-2 inline-block flex items-center"
|
||||
@click="clickLink('/settings')"
|
||||
>
|
||||
<IconSettings class="inline-block" />
|
||||
<it-icon-settings class="inline-block" />
|
||||
<span class="ml-3">{{ $t("general.settings") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -33,7 +33,7 @@ onMounted(async () => {
|
|||
|
||||
<div class="grid auto-rows-fr grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div
|
||||
v-for="courseSession in courseSessionsStore.coursesFromCourseSessions"
|
||||
v-for="courseSession in courseSessionsStore.userCourses"
|
||||
:key="courseSession.id"
|
||||
>
|
||||
<div class="bg-white p-6 md:h-full">
|
||||
|
|
|
|||
|
|
@ -189,6 +189,11 @@ function log(data: any) {
|
|||
menu
|
||||
<it-icon-menu />
|
||||
</div>
|
||||
|
||||
<div class="inline-flex flex-col">
|
||||
settings
|
||||
<it-icon-settings />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-8 mt-8 flex flex-col flex-wrap gap-4 lg:flex-row">
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
const { courseSessions } = loadCourseSessionsData();
|
||||
|
||||
// these will become getters
|
||||
const coursesFromCourseSessions = computed(() =>
|
||||
const userCourses = computed(() =>
|
||||
// TODO: refactor after implementing of Klassenkonzept
|
||||
// @ts-ignore
|
||||
uniqBy(courseSessions.value, "course.id")
|
||||
|
|
@ -129,6 +129,11 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
return courseSessionForCourse(courseSlug);
|
||||
});
|
||||
|
||||
const courseSessionsForRoute = computed(() => {
|
||||
const courseSlug = courseSlugForRoute.value;
|
||||
return courseSessionsForCourse(courseSlug);
|
||||
});
|
||||
|
||||
const hasCockpit = computed(() => {
|
||||
if (courseSessionForRoute.value) {
|
||||
const userStore = useUserStore();
|
||||
|
|
@ -210,8 +215,10 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
|
||||
return {
|
||||
courseSessions,
|
||||
coursesFromCourseSessions,
|
||||
userCourses,
|
||||
courseSessionForRoute,
|
||||
courseSessionsForRoute,
|
||||
courseSessionsForCourse,
|
||||
hasCockpit,
|
||||
canUploadCircleDocuments,
|
||||
circleDocuments,
|
||||
|
|
@ -219,6 +226,5 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
|||
addDocument,
|
||||
startUpload,
|
||||
removeDocument,
|
||||
courseSessionsForCourse,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -234,6 +234,10 @@ def create_course_uk_de():
|
|||
course_session=cs,
|
||||
user=User.objects.get(username="student-uk1-zurich@eiger-versicherungen.ch"),
|
||||
)
|
||||
_csu = CourseSessionUser.objects.create(
|
||||
course_session=cs,
|
||||
user=User.objects.get(username="michael.meier@example.com"),
|
||||
)
|
||||
|
||||
|
||||
def create_course_uk_fr():
|
||||
|
|
|
|||
Loading…
Reference in New Issue