From 3df55b4d1cd4533dde6271fd789f702893472559 Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Tue, 12 May 2026 00:46:03 +0200 Subject: [PATCH] feat(appshell): topbar breadcrumb, notification bell, and help icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Left side gains a desktop-only breadcrumb "Organisation / Page title" using the current organisation from useAuthStore and a page title resolved by: 1. route.meta.title (if a page sets it explicitly), then 2. matching the active route name against the navItems prop, then 3. humanizing the route name as a last-resort fallback. The chevron separator is suppressed when either side is empty, so portal and pre-org users see just the page title. Mobile preserves the existing hamburger + title text (the breadcrumb is hidden on --- apps/app/src/layouts/components/AppShell.vue | 102 ++++++++++++++++++- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/apps/app/src/layouts/components/AppShell.vue b/apps/app/src/layouts/components/AppShell.vue index 1e9e8ac1..81c9f574 100644 --- a/apps/app/src/layouts/components/AppShell.vue +++ b/apps/app/src/layouts/components/AppShell.vue @@ -16,11 +16,13 @@ // PrimeVue Drawer overlay. Content area renders the default slot // (a RouterView from the wrapping layout file). -import { ref } from 'vue' -import { useRouter } from 'vue-router' +import { computed, ref } from 'vue' +import { useRoute, useRouter } from 'vue-router' import Drawer from 'primevue/drawer' import Button from 'primevue/button' +import { useToast } from 'primevue/usetoast' import Icon from '@/components/Icon.vue' +import { useAuthStore } from '@/stores/useAuthStore' interface NavHeading { heading: string @@ -37,11 +39,14 @@ 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) @@ -53,6 +58,54 @@ function navigate(item: NavLink) { mobileNavOpen.value = false router.push(item.to) } + +// 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, + }) +}