@php /** * Shared dashboard layout (vendor + admin). * Expects: $nav (array of ['label','url','icon','match']), $roleLabel, $heading. */ use App\Support\App; use App\Support\Auth; $u = Auth::user(); $cur = App::path(); // "View site": a vendor and everyone on their team — the floor staff AND the driver, all // staff-scope roles now — jump to their own storefront (the restaurant detail page); admins // (and any non-restaurant role) go to the marketplace home. Vendors/staff already carry // $restaurant; a driver resolves the slug from users.restaurant_id (the rider dashboard // isn't scoped to a single restaurant view). $viewSiteRole = (string) ($u['role'] ?? ''); $viewSiteBound = $viewSiteRole === 'vendor' || \App\Models\Role::isStaff($viewSiteRole); $viewSiteSlug = (string) ($restaurant['slug'] ?? ''); if ($viewSiteSlug === '' && $viewSiteBound && !empty($u['restaurant_id'])) { $viewSiteSlug = (string) \App\Models\Restaurant::query()->whereKey((int) $u['restaurant_id'])->value('slug'); } // With the front end switched off there is no site to view: every destination this link could // take (a restaurant page, or the home page) is one the router now refuses. An empty string means // "offer no link at all" — the three places that render it check before drawing, so a services // install never shows a door that leads nowhere. $viewSiteUrl = \App\Services\Capability::has(\App\Services\Capability::STOREFRONT) ? (($viewSiteBound && $viewSiteSlug !== '') ? url('/restaurant/' . $viewSiteSlug) : route('home')) : ''; @endphp @php // The dashboard is a self-localized management UI (its own language switcher: English / Español / // العربية). Tell Chrome NOT to offer machine translation of it — otherwise data-driven controls // (e.g. a "Featured product" select listing a restaurant's whole Italian menu) make Chrome detect // the page as that language and pop the Translate bar. The public storefront (public.php) keeps // translation enabled, so a diner CAN still translate a foreign menu. @endphp @partial('head', ['title' => $heading ?? t_raw('nav.dashboard'), 'pageStyles' => $pageStyles ?? []]) @php // Pre-hide notices dismissed earlier this session BEFORE the body paints, so a dismissed // banner never flashes in then out on the next page. The click-to-dismiss behaviour lives in // qm-dismiss.js (end of body); this only suppresses the initial paint. @endphp @php // Restore the collapsed-sidebar choice before first paint (desktop only) to avoid a layout flash. @endphp
@php // Sticky chrome group: the top bar and the subheader band stick to the viewport top // together, so the breadcrumb (now in the subheader) stays pinned on scroll as before. // Chrome layout: the page header lives in the SUBHEADER band — the H1 on the start // side with the breadcrumb hanging under it, the primary action on the end; the top // bar keeps only the sidebar toggle + global actions. This is the default for every // dashboard page; a page can opt back into the old top-bar title with // View::slot('chromeVariant','title-in-topbar'). @endphp @php $__chromeVariant = \App\Support\View::slot('chromeVariant'); $__titleInSubhead = $__chromeVariant !== 'title-in-topbar'; @endphp
@partial('top-bar', ['u' => $u, 'heading' => $heading ?? null, 'roleLabel' => $roleLabel ?? '', 'viewSiteUrl' => $viewSiteUrl, '__titleInSubhead' => $__titleInSubhead]) @php // Subheader band — a slim strip under the top bar. The breadcrumb is pulled up so it // still straddles the line between the top bar and this band (identical to before); // the page's primary action (the 'subhead' slot) sits normally in the band, right side // under the avatar. Shown whenever a page supplies a breadcrumb OR a slot action, so // immersive screens (kiosk / POS / kitchen) that supply neither keep no band. @endphp @php $__subAction = \App\Support\View::slot('subhead'); // Build the breadcrumb nav once — it is placed inside the title block in the titled // variant, or on its own (floating pill) in the default chrome. $__crumbHtml = ''; // A single-node trail in the titled chrome would only repeat the H1 beside // it, so the pill renders from two nodes up (always in the topbar variant, // where the trail is the page's only name). if (!empty($breadcrumbs) && (count($breadcrumbs) > 1 || !$__titleInSubhead)) { $__cLast = count($breadcrumbs) - 1; ob_start(); @endphp @php $__crumbHtml = ob_get_clean(); } @endphp @if ($__titleInSubhead || $__crumbHtml !== '' || $__subAction !== '')
@if ($__titleInSubhead)

{{ $heading ?? t_raw('nav.dashboard') }}

{{-- breadcrumb hangs on the subheader's lower edge, under the title (CSS) --}}{!! $__crumbHtml !!} @else {!! $__crumbHtml !!} @endif @if ($__subAction !== '')
{!! $__subAction !!}
@endif
@endif
@php // /.qm-dash-chrome @endphp
@partial('impersonation-bar') @partial('flash') @yield('content')
@partial('scripts', ['pageScripts' => $pageScripts ?? []]) @php // Rows-per-page picker + row overflow menus. Both are delegated listeners with no // start-up cost, so loading globally keeps every list table behaving identically. @endphp @php // Per-session dismissible notices (dashboard stock/pending banners): a close button hides the // notice for the rest of the session. Delegated + no-op when no [data-qm-dismiss] is present. @endphp @php // Sectioned tab panels (both Settings pages + every role's account page). Exits // immediately on pages that have no .qm-settings-nav, so it is safe to load globally. @endphp @php // Scroll-arrow reachability for tab strips (.qm-settings-nav + .qm-tabs) on tablet/mobile. // Global so every .qm-tabs page (taxonomy, comments, blog…) gets reachable tabs too. @endphp @if (!empty($useCharts)) @endif @php // KPI snapshot strips are drag-to-scroll sliders (swipe on touch, click-drag on desktop); // loaded on every dashboard page since KPI grids appear with or without charts. @endphp @php // Per-page dashboard scripts (e.g. floor-plan editor, kitchen board, AI chat). @endphp @foreach (($dashScripts ?? []) as $__s) @endforeach @php // Idle-lock for shared staff terminals: overlay + PIN pad, loaded after app.js // (window.QM.post). Every vendor-dashboard staff role gets it, including any created on the // Roles screen — the terminal they share does not care what the role is called. The driver // is excluded (worksVendorDashboard): its rider dashboard reuses this layout but is not a // shared terminal, so no lock scaffolding is injected there. @endphp @if (\App\Models\Role::worksVendorDashboard((string) ($u['role'] ?? ''))) @partial('staff-lock', ['lockSeconds' => (int) ($restaurant['staff_lock_seconds'] ?? 0)]) @endif @php // Header AI assistant off-canvas + its multi-instance chat script. Rendered for // whoever holds the assistant privilege, plus super-admin; the script also drives the // full-page vendor assistant widget when present. Loaded after app.js (window.QM.post). // ai_vendor_enabled is the platform's separate say on whether its restaurants may use the // assistant it pays for; super-admin keeps it either way. @endphp @if (($u['role'] ?? '') === 'super_admin' || (\App\Support\Auth::can('ai.use') && setting('ai_vendor_enabled', '1') === '1')) @partial('ai-offcanvas') @endif