diff --git a/apps/app/src/layouts/components/AppShell.vue b/apps/app/src/layouts/components/AppShell.vue index 9761e1d2..d1224e95 100644 --- a/apps/app/src/layouts/components/AppShell.vue +++ b/apps/app/src/layouts/components/AppShell.vue @@ -17,12 +17,13 @@ // (a RouterView from the wrapping layout file). import { computed, ref } from 'vue' -import { useRouter } from 'vue-router' +import { useRoute, useRouter } from 'vue-router' import Drawer from 'primevue/drawer' import Button from 'primevue/button' -import Avatar from 'primevue/avatar' -import Menu from 'primevue/menu' +import { useToast } from 'primevue/usetoast' import Icon from '@/components/Icon.vue' +import SidebarHeader from '@/layouts/components/SidebarHeader.vue' +import SidebarUserCard from '@/layouts/components/SidebarUserCard.vue' import { useAuthStore } from '@/stores/useAuthStore' interface NavHeading { @@ -40,42 +41,23 @@ interface Props { title?: string } -withDefaults(defineProps(), { +const props = withDefaults(defineProps(), { title: 'Crewli', }) const router = useRouter() +const route = useRoute() +const toast = useToast() const authStore = useAuthStore() const mobileNavOpen = ref(false) -const userMenuRef = ref | null>(null) -const userInitial = computed(() => { - const name = authStore.user?.full_name ?? '' - - return name.charAt(0).toUpperCase() || '?' -}) - -const userMenuItems = computed(() => [ - { - label: authStore.user?.full_name ?? 'Gebruiker', - items: [ - { - label: 'Mijn Profiel', - icon: 'tabler-user', - command: () => router.push({ name: 'account-settings' }), - }, - { - label: 'Uitloggen', - icon: 'tabler-logout', - command: async () => { - await authStore.logout() - await router.push('/login') - }, - }, - ], - }, -]) +// Tailwind's lg breakpoint, mirrored in script so Vue can own the +// visibility of PrimeVue elements that would otherwise lose a CSS +// specificity duel to .p-button / .p-drawer / etc. See the wrapper +// `
` around the topbar mobile cluster and the +// `v-if="!isLg"` on the Drawer. +const isLg = useMediaQuery('(min-width: 1024px)') function isHeading(item: NavItem): item is NavHeading { return 'heading' in item @@ -86,26 +68,68 @@ function navigate(item: NavLink) { router.push(item.to) } -function toggleUserMenu(event: Event) { - userMenuRef.value?.toggle(event) +// Breadcrumb: "Organisation / Page title". Page title resolves from +// route.meta.title → matching navItems entry → humanized route name. +// Org name is omitted on portal / pre-org users (currentOrganisation +// is null) so the breadcrumb reads as just the page title there. +const orgName = computed(() => authStore.currentOrganisation?.name ?? '') + +const pageTitle = computed(() => { + const metaTitle = route.meta.title + if (typeof metaTitle === 'string' && metaTitle.length > 0) + return metaTitle + + const match = props.navItems.find( + (item): item is NavLink => 'to' in item && item.to.name === route.name, + ) + + if (match) + return match.title + + const raw = String(route.name ?? '') + if (!raw) + return '' + + return raw.charAt(0).toUpperCase() + raw.slice(1).replace(/-/g, ' ') +}) + +function onNotificationsClick() { + toast.add({ + severity: 'info', + summary: 'Notificaties', + detail: 'Notificaties komen binnenkort beschikbaar.', + life: 3000, + }) +} + +// TODO(docs-url): https://docs.crewli.app currently serves with a TLS +// cert that does not cover the host (ERR_TLS_CERT_ALTNAME_INVALID), +// so a browser hit shows a security warning instead of the docs. +// When the cert is fixed, switch this handler to: +// window.open('https://docs.crewli.app', '_blank', 'noopener,noreferrer') +function onHelpClick() { + toast.add({ + severity: 'info', + summary: 'Help', + detail: 'Documentatie komt binnenkort beschikbaar.', + life: 3000, + }) }