startOfDay(); $cacheMinutes = 5; // Active clients count - cached $activeClientsCount = Cache::remember('dashboard:active_clients:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () { return Client::where('active', true)->count(); }); // Active contracts count - cached $activeContractsCount = Cache::remember('dashboard:active_contracts:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () { return Contract::whereNull('deleted_at')->count(); }); // Sum of active contracts' account balance - cached $totalBalance = Cache::remember('dashboard:total_balance:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () { return Account::whereHas('contract', function ($q) { $q->whereNull('deleted_at'); })->sum('balance_amount') ?? 0; }); // Active promises count (not expired or expires today) - cached $activePromisesCount = Cache::remember('dashboard:active_promises:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () use ($today) { return Account::whereHas('contract', function ($q) { $q->whereNull('deleted_at'); }) ->whereNotNull('promise_date') ->whereDate('promise_date', '>=', $today) ->count(); }); // Activities (limit 10) - cached $activities = Cache::remember('dashboard:activities', $cacheMinutes * 60, function () { return Activity::query() ->with(['clientCase:id,uuid']) ->latest() ->limit(10) ->get(['id', 'note', 'created_at', 'client_case_id', 'contract_id', 'action_id', 'decision_id']) ->map(fn ($a) => [ 'id' => $a->id, 'note' => $a->note, 'created_at' => $a->created_at, 'client_case_id' => $a->client_case_id, 'client_case_uuid' => $a->clientCase?->uuid, 'contract_id' => $a->contract_id, 'action_id' => $a->action_id, 'decision_id' => $a->decision_id, ]); }); // 7-day trends for field jobs - cached $trends = Cache::remember('dashboard:field_jobs_trends:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () { $start = now()->subDays(6)->startOfDay(); $end = now()->endOfDay(); $dateKeys = collect(range(0, 6)) ->map(fn ($i) => now()->subDays(6 - $i)->format('Y-m-d')); $fieldJobTrendRaw = FieldJob::whereBetween(DB::raw('COALESCE(assigned_at, created_at)'), [$start, $end]) ->selectRaw('DATE(COALESCE(assigned_at, created_at)) as d, COUNT(*) as c') ->groupBy('d') ->pluck('c', 'd'); // Completed field jobs last 7 days $fieldJobCompletedRaw = FieldJob::whereNotNull('completed_at') ->whereBetween('completed_at', [$start, $end]) ->selectRaw('DATE(completed_at) as d, COUNT(*) as c') ->groupBy('d') ->pluck('c', 'd'); return [ 'field_jobs' => $dateKeys->map(fn ($d) => (int) ($fieldJobTrendRaw[$d] ?? 0))->values(), 'field_jobs_completed' => $dateKeys->map(fn ($d) => (int) ($fieldJobCompletedRaw[$d] ?? 0))->values(), 'labels' => $dateKeys, ]; }); // Field jobs assigned today - cached $fieldJobsAssignedToday = Cache::remember('dashboard:field_jobs_assigned_today:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () use ($today) { return FieldJob::query() ->whereDate(DB::raw('COALESCE(assigned_at, created_at)'), $today) ->select(['id', 'assigned_user_id', 'priority', 'assigned_at', 'created_at', 'contract_id']) ->with(['contract' => function ($q) { $q->select('id', 'uuid', 'reference', 'client_case_id') ->with(['clientCase:id,uuid,person_id', 'clientCase.person:id,full_name', 'segments:id,name']); }]) ->latest(DB::raw('COALESCE(assigned_at, created_at)')) ->limit(15) ->get() ->map(function ($fj) { $contract = $fj->contract; $segmentId = null; if ($contract && method_exists($contract, 'segments')) { $activeSeg = $contract->segments->first(); if ($activeSeg && isset($activeSeg->pivot) && ($activeSeg->pivot->active ?? true)) { $segmentId = $activeSeg->id; } } return [ 'id' => $fj->id, 'priority' => $fj->priority, 'assigned_at' => $fj->assigned_at?->toIso8601String(), 'created_at' => $fj->created_at?->toIso8601String(), 'contract' => $contract ? [ 'uuid' => $contract->uuid, 'reference' => $contract->reference, 'client_case_uuid' => optional($contract->clientCase)->uuid, 'person_full_name' => optional(optional($contract->clientCase)->person)->full_name, 'segment_id' => $segmentId, ] : null, ]; }); }); // System health for timestamp $recentActivity = Activity::query()->latest('created_at')->value('created_at'); $lastActivityMinutes = null; if ($recentActivity) { $lastActivityMinutes = (int) max(0, now()->diffInMinutes($recentActivity)); } $systemHealth = [ 'last_activity_minutes' => $lastActivityMinutes, 'last_activity_iso' => $recentActivity?->toIso8601String(), 'generated_at' => now()->toIso8601String(), ]; return Inertia::render('Dashboard/Index', [ 'kpis' => [ 'active_clients' => $activeClientsCount, 'active_contracts' => $activeContractsCount, 'total_balance' => $totalBalance, 'active_promises' => $activePromisesCount, ], 'trends' => $trends, ])->with([ 'activities' => fn () => $activities, 'systemHealth' => fn () => $systemHealth, 'fieldJobsAssignedToday' => fn () => $fieldJobsAssignedToday, 'smsStats' => function () use ($sms, $today, $cacheMinutes) { // SMS stats - cached return Cache::remember('dashboard:sms_stats:'.now()->format('Y-m-d'), $cacheMinutes * 60, function () use ($sms, $today) { $counts = SmsLog::query() ->whereDate('created_at', $today) ->selectRaw('profile_id, status, COUNT(*) as c') ->groupBy('profile_id', 'status') ->get() ->groupBy('profile_id') ->map(function ($rows) { $map = [ 'queued' => 0, 'sent' => 0, 'delivered' => 0, 'failed' => 0, ]; foreach ($rows as $r) { $map[$r->status] = (int) $r->c; } $map['total'] = array_sum($map); return $map; }); $profiles = SmsProfile::query() ->orderBy('name') ->get(['id', 'name', 'active', 'api_username', 'encrypted_api_password']); return $profiles->map(function (SmsProfile $p) use ($sms, $counts) { try { $balance = $sms->getCreditBalance($p); } catch (\Throwable $e) { $balance = '—'; } $c = $counts->get($p->id) ?? ['queued' => 0, 'sent' => 0, 'delivered' => 0, 'failed' => 0, 'total' => 0]; return [ 'id' => $p->id, 'name' => $p->name, 'active' => (bool) $p->active, 'balance' => $balance, 'today' => $c, ]; })->values(); }); }, ]); } }