@props(['title' => 'Dashboard', 'subtitle' => null]) @php $__siteName = \App\Helpers\BrandingHelper::name(); $__favicon = \App\Models\SystemSetting::get('favicon'); @endphp {{ $title }} — {{ $__siteName }} Admin {{-- Favicon --}} @if($__favicon) @else @endif {{-- Dynamic brand colors from admin settings --}} @php $__brandColor = \App\Models\SystemSetting::get('primary_color'); $__brandStrong = \App\Models\SystemSetting::get('secondary_color'); $__accentColor = \App\Models\SystemSetting::get('accent_color'); @endphp @if($__brandColor || $__brandStrong || $__accentColor) @endif {{-- Body font (admin-configurable via Settings → General → Body Font) --}} {!! \App\Helpers\FontHelper::renderHead() !!} {{-- Vite --}} @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles {{-- Theme Studio runtime variables (light + dark). Loaded AFTER Vite so these override app.css defaults but BEFORE custom_css so admins can still override individual properties. --}} {!! \App\Helpers\ThemeHelper::renderCss() !!} {{-- Admin-defined custom CSS + head HTML (analytics, custom meta, etc.) --}} @php $__customCss = \App\Models\SystemSetting::get('custom_css', ''); @endphp @if(trim($__customCss) !== '') @endif {!! \App\Models\SystemSetting::get('head_code', '') !!} {{-- Progress Bar --}}
{{-- Mobile backdrop — dims content when sidebar is open --}} {{-- ═══════════════════════════════════════════════ SIDEBAR ═══════════════════════════════════════════════ --}} {{-- Mobile sidebar overlay --}} {{-- ═══════════════════════════════════════════════ MAIN CONTENT ═══════════════════════════════════════════════ --}}
{{-- Header --}}
{{-- Left side --}}
{{-- Mobile hamburger --}}

{{ $title }}

@if($subtitle) @endif
{{-- Right side --}}
{{-- Live Search --}} {{-- Fullscreen toggle (pointless on phone, hidden below sm) --}} {{-- Language switcher (hidden on mobile — available in sidebar/profile on small screens) --}} {{-- Notification bell. Counts cached for 30s — these run on EVERY admin page render and were the root cause of "Maximum execution time exceeded" and the "Add Ticket continues loading" hang on installs with large tickets / users / payments / sessions tables. 30s is short enough that a fresh ticket / signup still shows up almost immediately. --}} @php $__notifCounts = \Illuminate\Support\Facades\Cache::remember( 'admin.notification_counts', 30, function () { return [ 'openTickets' => (int) \Illuminate\Support\Facades\DB::table('tickets')->where('status', 'open')->count(), 'recentUsers' => (int) \App\Models\User::where('created_at', '>=', now()->subDay())->count(), 'failedPayments' => (int) \Illuminate\Support\Facades\DB::table('payments')->where('status', 'failed')->where('created_at', '>=', now()->subDays(7))->count(), 'pendingJobs' => (int) \Illuminate\Support\Facades\DB::table('failed_jobs')->count(), 'newWorkspaces' => (int) \Illuminate\Support\Facades\DB::table('workspaces')->where('created_at', '>=', now()->subDay())->count(), 'activeNow' => (int) \Illuminate\Support\Facades\DB::table('sessions')->where('last_activity', '>=', now()->subMinutes(5)->timestamp)->count(), ]; } ); $openTickets = $__notifCounts['openTickets']; $recentUsers = $__notifCounts['recentUsers']; $failedPayments = $__notifCounts['failedPayments']; $pendingJobs = $__notifCounts['pendingJobs']; $newWorkspaces = $__notifCounts['newWorkspaces']; $activeNow = $__notifCounts['activeNow']; $notifCount = $openTickets + $recentUsers + $failedPayments + $pendingJobs; @endphp
{{-- Admin user --}}
{{-- Page content --}}
{{-- Flash messages --}} @if(session('success')) @endif @if(session('error')) @endif {{ $slot }}
{{-- max-w-7xl --}}
@livewireScripts {{-- Admin-defined raw HTML rendered before (analytics, chat widgets, etc.) --}} {!! \App\Models\SystemSetting::get('footer_code', '') !!}