isAgentPortal() ? collect() : User::query()->orderBy('username')->get(['id', 'username']); return view('admin.visits.index', [ 'portal' => $this->portal(), 'agents' => $agents, ]); } public function data(Request $request) { $q = $this->scopedQuery($request); $limit = min(100, max(1, (int) $request->query('limit', 20))); $page = max(1, (int) $request->query('page', 1)); $total = (clone $q)->count(); $rows = (clone $q) ->orderByDesc('id') ->forPage($page, $limit) ->get([ 'id', 'channel_id', 'client_uid', 'os', 'os_version', 'browser', 'browser_version', 'user_agent', 'ip', 'path', 'created_at', ]); return response()->json([ 'code' => 0, 'msg' => '', 'count' => $total, 'data' => $rows->map(static fn (PageVisit $v) => [ 'id' => $v->id, 'channel_id' => $v->channel_id, 'client_uid' => $v->client_uid, 'os' => $v->os ?: '', 'os_version' => $v->os_version ?: '', 'browser' => $v->browser ?: '', 'browser_version' => $v->browser_version ?: '', 'user_agent' => $v->user_agent ?: '', 'ip' => $v->ip ?: '', 'path' => $v->path ?: '', 'created_at' => optional($v->created_at)?->toDateTimeString(), ])->values(), ]); } public function groups(Request $request) { $group = (string) $request->query('group', 'os'); $base = $this->scopedQuery($request); $builder = match ($group) { 'os_version' => $base ->select('os', 'os_version') ->selectRaw('COUNT(*) as pv') ->selectRaw('COUNT(DISTINCT client_uid) as uv') ->groupBy('os', 'os_version'), 'browser' => $base ->select('browser') ->selectRaw('COUNT(*) as pv') ->selectRaw('COUNT(DISTINCT client_uid) as uv') ->groupBy('browser'), 'browser_version' => $base ->select('browser', 'browser_version') ->selectRaw('COUNT(*) as pv') ->selectRaw('COUNT(DISTINCT client_uid) as uv') ->groupBy('browser', 'browser_version'), default => $base ->select('os') ->selectRaw('COUNT(*) as pv') ->selectRaw('COUNT(DISTINCT client_uid) as uv') ->groupBy('os'), }; $rows = $builder ->orderByDesc('pv') ->limit(50) ->get() ->map(static function ($row) use ($group) { $label = match ($group) { 'os_version' => trim(((string) ($row->os ?? '')).' '.((string) ($row->os_version ?? ''))), 'browser' => (string) ($row->browser ?? ''), 'browser_version' => trim(((string) ($row->browser ?? '')).' '.((string) ($row->browser_version ?? ''))), default => (string) ($row->os ?? ''), }; return [ 'label' => $label !== '' ? $label : 'Unknown', 'pv' => (int) $row->pv, 'uv' => (int) $row->uv, ]; }) ->values(); return response()->json([ 'code' => 0, 'msg' => '', 'data' => [ 'group' => $group, 'rows' => $rows, ], ]); } private function scopedQuery(Request $request) { $q = PageVisit::query(); AgentScope::applyChannelIdScope($q, $this->agent()); $agentUserId = $this->isAgentPortal() ? (int) ($this->agent()?->id ?? 0) : (int) $request->query('agent_user_id', 0); if (! $this->isAgentPortal() && $agentUserId > 0) { AgentScope::applyChannelIdAgentUserFilter($q, $agentUserId); } $channelId = trim((string) $request->query('channel_id', '')); if ($channelId !== '') { $q->where('channel_id', 'like', '%'.$channelId.'%'); } $os = trim((string) $request->query('os', '')); if ($os !== '') { $q->where('os', $os); } $osVersion = trim((string) $request->query('os_version', '')); if ($osVersion !== '') { $q->where('os_version', $osVersion); } $browser = trim((string) $request->query('browser', '')); if ($browser !== '') { $q->where('browser', $browser); } $browserVersion = trim((string) $request->query('browser_version', '')); if ($browserVersion !== '') { $q->where('browser_version', $browserVersion); } [$from, $to] = $this->rangeBounds((string) $request->query('range', '30d')); $q->whereBetween('created_at', [$from, $to]); return $q; } /** * @return array{0: Carbon, 1: Carbon} */ private function rangeBounds(string $range): array { $now = Carbon::now(); return match ($range) { 'today' => [$now->copy()->startOfDay(), $now->copy()->endOfDay()], 'yesterday' => [ $now->copy()->subDay()->startOfDay(), $now->copy()->subDay()->endOfDay(), ], '7d' => [$now->copy()->subDays(6)->startOfDay(), $now->copy()->endOfDay()], default => [$now->copy()->subDays(29)->startOfDay(), $now->copy()->endOfDay()], }; } }