Snapshot of main at 9f39a6e1e (2026-11-02)

This commit is contained in:
Ronald Huynen committed 2026-11-02 10:41:47 +01:00
commit 6ccf1792d7
2580 files changed
+1040202

No files matched your search

+205
View File
@@ -0,0 +1,205 @@
<?php
namespace App\Http\Livewire\Admin;
use App\Helpers\SearchOptimizationHelper;
use App\Models\Locations\City;
use App\Models\Locations\Country;
use App\Models\Locations\District;
use App\Models\Locations\Division;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class SearchMetrics extends Component
{
public array $metrics = [];
public array $recentSearches = [];
public string $period = 'last_year';
public array $periods = [
'last_week' => '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');
}
}