'Last week', 'last_month' => 'Last month', 'last_6months' => 'Last 6 months', 'last_year' => 'Last year', ]; public function mount() { if (!Auth::check()) { abort(403, 'Unauthorized'); } if (getActiveProfileType() !== 'Admin') { abort(403, 'Unauthorized'); } $activeProfile = getActiveProfile(); if (!$activeProfile) { abort(403, 'Unauthorized'); } \App\Helpers\ProfileAuthorizationHelper::authorize($activeProfile); if (!$this->hasLogsPermission()) { abort(403, 'Unauthorized'); } $this->loadMetrics(); } protected function hasLogsPermission(): bool { $webUser = Auth::guard('web')->user(); $activeId = session('activeProfileId'); if (!$webUser || !$activeId) return false; $prefix = 'Admin\\' . $activeId . '\\'; $scopedRoles = $webUser->roles->filter(fn($r) => str_starts_with($r->name, $prefix)); foreach ($scopedRoles as $scopedRole) { $roleName = str_replace($prefix, '', $scopedRole->name); $adminRole = \App\Models\AdminRole::where('name', $roleName)->first(); if ($adminRole && in_array('view search-metrics', $adminRole->permissions ?? [])) { return true; } } return false; } public function refresh() { $this->loadMetrics(); } public function updatedPeriod() { $this->loadMetrics(); } private function periodSince(): \Carbon\Carbon { return match ($this->period) { 'last_week' => now()->subWeek(), 'last_month' => now()->subMonth(), 'last_6months' => now()->subMonths(6), default => now()->subMonths(12)->startOfMonth(), }; } public function downloadLog() { $csv = implode(',', ['time', 'query', 'categories', 'results', 'exec_s', 'lang', 'type', 'location']) . "\n"; foreach ($this->recentSearches as $row) { $categories = implode('|', array_column($row['category_tags'] ?? [], 'name')); $csv .= implode(',', [ \Carbon\Carbon::parse($row['timestamp'])->format('Y-m-d'), '"' . str_replace('"', '""', $row['search_string'] ?? '') . '"', '"' . str_replace('"', '""', $categories) . '"', $row['total_results'] ?? 0, number_format($row['execution_time'] ?? 0, 4), $row['locale'] ?? '', $row['user_type'] ?? '', '"' . str_replace('"', '""', $row['location_label'] ?? '') . '"', ]) . "\n"; } $filename = 'search-metrics-' . now()->format('Y-m') . '.csv'; return response()->streamDownload(fn () => print($csv), $filename, [ 'Content-Type' => 'text/csv', ]); } private function loadMetrics(): void { $since = $this->periodSince(); $this->metrics = SearchOptimizationHelper::getSearchMetrics($since); $raw = SearchOptimizationHelper::readCurrentMonthEntries(50, $since); // Collect all unique category IDs across all entries, then resolve names once $allIds = array_values(array_unique(array_merge(...array_map( fn($s) => $s['category_ids'] ?? [], $raw )))); $tagMap = []; if (!empty($allIds)) { $locale = app()->getLocale(); $categories = \App\Models\Category::whereIn('id', $allIds) ->with(['translations' => fn($q) => $q->where('locale', $locale)]) ->get() ->keyBy('id'); foreach ($allIds as $id) { $cat = $categories->get($id); $tagMap[$id] = [ 'name' => $cat?->translation?->name ?? $cat?->name ?? $id, 'color' => $cat?->relatedColor ?? 'gray', ]; } } // Collect all unique location IDs and resolve names in one query per type $locale = app()->getLocale(); $locationNameMap = $this->resolveLocationNames($raw, $locale); // Replace category_ids with resolved name+color tags, and add location label per entry $this->recentSearches = array_map(function ($search) use ($tagMap, $locationNameMap) { $search['category_tags'] = array_map( fn($id) => $tagMap[$id] ?? ['name' => $id, 'color' => 'gray'], $search['category_ids'] ?? [] ); // Build location short label matching LocationTrait::name_short logic: // city + (district or division), or country if no city $city = $locationNameMap['city'][$search['city_id'] ?? null] ?? null; $district = $locationNameMap['district'][$search['district_id'] ?? null] ?? null; $division = $locationNameMap['division'][$search['division_id'] ?? null] ?? null; $country = $locationNameMap['country'][$search['country_id'] ?? null] ?? null; $parts = []; if ($city) $parts[] = $city; if ($district && count($parts) < 2) $parts[] = $district; if ($division && count($parts) < 2) $parts[] = $division; if (!$city && $country) $parts[] = $country; $search['location_label'] = preg_replace('/\s+,/', ',', implode(' ', $parts)) ?: null; return $search; }, $raw); } private function resolveLocationNames(array $entries, string $locale): array { $ids = ['country' => [], 'division' => [], 'city' => [], 'district' => []]; foreach ($entries as $entry) { if (!empty($entry['country_id'])) $ids['country'][] = $entry['country_id']; if (!empty($entry['division_id'])) $ids['division'][] = $entry['division_id']; if (!empty($entry['city_id'])) $ids['city'][] = $entry['city_id']; if (!empty($entry['district_id'])) $ids['district'][] = $entry['district_id']; } $resolve = function ($model, array $uniqueIds) use ($locale): array { if (empty($uniqueIds)) return []; return $model::whereIn('id', array_unique($uniqueIds)) ->with(['translations' => fn($q) => $q->where('locale', $locale)]) ->get() ->keyBy('id') ->map(fn($r) => $r->translations->first()?->name ?? $r->name ?? null) ->toArray(); }; return [ 'country' => $resolve(Country::class, $ids['country']), 'division' => $resolve(Division::class, $ids['division']), 'city' => $resolve(City::class, $ids['city']), 'district' => $resolve(District::class, $ids['district']), ]; } public function render() { return view('livewire.admin.search-metrics'); } }