Initial commit: Animexe Laravel platform
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,802 @@
|
||||
@extends('admin.layouts.app')
|
||||
@section('title', 'Analitik')
|
||||
@section('page-title', 'Kullanıcı Analitikleri')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.period-btn { padding: 6px 18px; border-radius: 99px; border: 1px solid var(--border2); background: transparent; color: var(--text2); font-size: 0.82rem; cursor: pointer; transition: all .15s; text-decoration: none; display: inline-block; font-weight:600; }
|
||||
.period-btn.active, .period-btn:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||||
|
||||
.an-card { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px 22px; }
|
||||
.an-card .val { font-size: 2rem; font-weight: 800; line-height: 1.1; color: var(--text); letter-spacing:-0.5px; }
|
||||
.an-card .lbl { font-size: 0.78rem; color: var(--text2); margin-top: 4px; font-weight:600; }
|
||||
.an-card .ico { font-size: 1.8rem; opacity: 0.5; }
|
||||
.an-card .delta { font-size: 0.72rem; font-weight: 600; margin-top: 6px; }
|
||||
.delta-up { color: var(--success); }
|
||||
.delta-down { color: var(--danger); }
|
||||
|
||||
.chart-card { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px 20px; }
|
||||
.chart-card .chart-title { font-size: 0.88rem; font-weight: 700; color: var(--text); margin-bottom: 14px; }
|
||||
.chart-wrap { position: relative; }
|
||||
|
||||
.geo-flag { font-size: 1.1rem; }
|
||||
.pbar-wrap { height: 6px; background: var(--surface); border-radius: 3px; flex: 1; }
|
||||
.pbar-fill { height: 100%; background: linear-gradient(90deg,var(--accent),var(--accent-lt)); border-radius: 3px; transition: width .4s; }
|
||||
|
||||
.live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--success); display: inline-block; animation: blink 1.2s infinite; }
|
||||
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:.3} }
|
||||
|
||||
.tbl-sm th { font-size: 0.73rem; color: var(--text2); text-transform: uppercase; letter-spacing: .5px; padding: 8px 10px; }
|
||||
.tbl-sm td { font-size: 0.84rem; padding: 8px 10px; vertical-align: middle; border-top: 1px solid var(--border); color: var(--text); }
|
||||
.tbl-sm tr:first-child td { border-top: none; }
|
||||
|
||||
.badge-type { font-size: 0.7rem; padding: 2px 8px; border-radius: 20px; }
|
||||
.badge-home { background: rgba(6,182,212,0.12); color: var(--info); }
|
||||
.badge-anime { background: rgba(16,185,129,0.12); color: var(--success); }
|
||||
.badge-player { background: rgba(124,58,237,0.12); color: var(--accent-lt); }
|
||||
.badge-search { background: rgba(245,158,11,0.12); color: var(--warning); }
|
||||
.badge-ai { background: rgba(124,58,237,0.12); color: var(--accent-lt); }
|
||||
.badge-genre { background: rgba(6,182,212,0.12); color: var(--info); }
|
||||
.badge-other { background: rgba(255,255,255,0.06); color: var(--text2); }
|
||||
|
||||
.pct-pill { font-size: 0.7rem; padding: 1px 7px; border-radius: 10px; }
|
||||
.pct-high { background: var(--success); color: #fff; }
|
||||
.pct-mid { background: var(--warning); color: #000; }
|
||||
.pct-low { background: var(--surface); color: var(--text2); }
|
||||
|
||||
.ai-q-text { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.82rem; color: var(--text2); }
|
||||
|
||||
.section-head { font-size: 0.78rem; text-transform: uppercase; letter-spacing: .7px; color: var(--text2); font-weight: 700; margin-bottom: 14px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="content-wrap">
|
||||
|
||||
{{-- Period seçici --}}
|
||||
<div class="d-flex align-items-center gap-2 mb-4 flex-wrap">
|
||||
<span class="text-muted me-1" style="font-size:.82rem">Dönem:</span>
|
||||
@foreach(['today'=>'Bugün','7d'=>'7 Gün','30d'=>'30 Gün','90d'=>'90 Gün'] as $key=>$lbl)
|
||||
<a href="{{ route('admin.analytics.index', ['period'=>$key]) }}"
|
||||
class="period-btn {{ $period===$key ? 'active' : '' }}">{{ $lbl }}</a>
|
||||
@endforeach
|
||||
<span class="ms-auto text-muted" style="font-size:.75rem">
|
||||
<span class="live-dot me-1"></span> Canlı — Son güncelleme: {{ now()->format('H:i') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{-- Özet kartlar --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-6 col-sm-4 col-lg">
|
||||
<div class="an-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="val">{{ number_format($totalViews) }}</div>
|
||||
<div class="lbl">Sayfa Görüntüleme</div>
|
||||
@if($period === '7d' || $period === 'today')
|
||||
<div class="delta {{ $viewsDelta >= 0 ? 'delta-up' : 'delta-down' }}">
|
||||
<i class="bi bi-arrow-{{ $viewsDelta >= 0 ? 'up' : 'down' }}"></i>
|
||||
{{ abs($viewsDelta) }}% dünden
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<i class="bi bi-eye ico text-info"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 col-lg">
|
||||
<div class="an-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="val">{{ number_format($uniqueVisitors) }}</div>
|
||||
<div class="lbl">Tekil Ziyaretçi</div>
|
||||
</div>
|
||||
<i class="bi bi-people ico text-success"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 col-lg">
|
||||
<div class="an-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
@php
|
||||
$wh = floor($watchSeconds / 3600);
|
||||
$wm = floor(($watchSeconds % 3600) / 60);
|
||||
@endphp
|
||||
<div class="val">{{ $wh > 0 ? $wh.'s' : $wm.'d' }}</div>
|
||||
<div class="lbl">Toplam İzleme Süresi</div>
|
||||
<div class="delta" style="color:var(--text2)">{{ number_format($watchSeconds/3600, 1) }} saat</div>
|
||||
</div>
|
||||
<i class="bi bi-play-circle ico" style="color:#a371f7"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 col-lg">
|
||||
<div class="an-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="val">{{ number_format($aiTotal) }}</div>
|
||||
<div class="lbl">AI Sorgusu</div>
|
||||
</div>
|
||||
<i class="bi bi-stars ico" style="color:#e8393c"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-sm-4 col-lg">
|
||||
<div class="an-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="val">{{ number_format($newUsers) }}</div>
|
||||
<div class="lbl">Yeni Kayıt</div>
|
||||
</div>
|
||||
<i class="bi bi-person-plus ico" style="color:#f0883e"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Görüntüleme trendi + Cihaz dağılımı --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-8">
|
||||
<div class="chart-card h-100">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<div class="chart-title mb-0">Görüntüleme ve İzleme Trendi</div>
|
||||
<div class="d-flex gap-3" style="font-size:.72rem;color:var(--text2)">
|
||||
<span><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:var(--accent);margin-right:4px"></span>Görüntüleme</span>
|
||||
<span><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#a371f7;margin-right:4px"></span>İzleme (saat)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-wrap" style="height:220px">
|
||||
<canvas id="trendChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="chart-card h-100">
|
||||
<div class="chart-title">Cihaz Dağılımı</div>
|
||||
<div class="chart-wrap" style="height:160px;display:flex;align-items:center;justify-content:center">
|
||||
<canvas id="deviceChart"></canvas>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
@php $deviceTotal = $deviceStats->sum(); @endphp
|
||||
@foreach(['mobile'=>['bi-phone','var(--accent)'],'desktop'=>['bi-laptop','var(--success)'],'tablet'=>['bi-tablet','#79c0ff']] as $dv=>[$ico,$col])
|
||||
@if(($cnt=$deviceStats[$dv]??0) > 0)
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<i class="bi {{ $ico }}" style="color:{{ $col }};width:16px"></i>
|
||||
<span style="font-size:.82rem;width:60px">{{ ucfirst($dv) }}</span>
|
||||
<div class="pbar-wrap"><div class="pbar-fill" style="width:{{ $deviceTotal>0?round($cnt/$deviceTotal*100):0 }}%;background:{{ $col }}"></div></div>
|
||||
<span style="font-size:.78rem;color:var(--text2);width:40px;text-align:right">{{ number_format($cnt) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Top anime + AI sorgular --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-7">
|
||||
<div class="chart-card h-100">
|
||||
<div class="chart-title">En Çok İzlenen 10 Anime</div>
|
||||
@if($topAnimes->count())
|
||||
@php $maxV = $topAnimes->max('views') ?: 1; @endphp
|
||||
@foreach($topAnimes as $i => $a)
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<span style="font-size:.72rem;color:var(--text2);width:16px;text-align:right">{{ $i+1 }}</span>
|
||||
<span style="font-size:.84rem;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ $a['title'] }}</span>
|
||||
<div class="pbar-wrap" style="width:120px"><div class="pbar-fill" style="width:{{ round($a['views']/$maxV*100) }}%"></div></div>
|
||||
<span style="font-size:.78rem;color:var(--text2);width:42px;text-align:right">{{ number_format($a['views']) }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="text-muted text-center py-4" style="font-size:.85rem">Bu dönemde veri yok</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="chart-card h-100">
|
||||
<div class="chart-title">AI Sorgu Türleri</div>
|
||||
<div class="chart-wrap" style="height:160px;display:flex;align-items:center;justify-content:center">
|
||||
<canvas id="aiChart"></canvas>
|
||||
</div>
|
||||
@php
|
||||
$aiLabels = ['chat'=>'Sohbet','recommend'=>'Öneri','search'=>'Arama','episode_info'=>'Bölüm Analiz','similar'=>'Benzer'];
|
||||
$aiColors = ['chat'=>'var(--accent)','recommend'=>'#a371f7','search'=>'var(--success)','episode_info'=>'#f0883e','similar'=>'#79c0ff'];
|
||||
$aiTotalQ = $aiByType->sum();
|
||||
@endphp
|
||||
<div class="mt-3">
|
||||
@foreach($aiByType->sortDesc() as $type => $cnt)
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:{{ $aiColors[$type]??'var(--text2)' }}"></span>
|
||||
<span style="font-size:.82rem;flex:1">{{ $aiLabels[$type]??$type }}</span>
|
||||
<span style="font-size:.78rem;color:var(--text2)">{{ number_format($cnt) }}</span>
|
||||
<span style="font-size:.72rem;color:#6e7681">{{ $aiTotalQ>0?round($cnt/$aiTotalQ*100):0 }}%</span>
|
||||
</div>
|
||||
@endforeach
|
||||
@if($aiByType->isEmpty())
|
||||
<div class="text-muted text-center py-2" style="font-size:.82rem">Bu dönemde AI sorgusu yok</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Saatlik dağılım --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="chart-card">
|
||||
<div class="chart-title">Bugünkü Saatlik Görüntüleme Dağılımı</div>
|
||||
<div class="chart-wrap" style="height:130px">
|
||||
<canvas id="hourlyChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Top bölümler + Coğrafi --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-7">
|
||||
<div class="chart-card h-100">
|
||||
<div class="section-head">En Çok İzlenen Bölümler (İzleme Süresi)</div>
|
||||
@if($topEpisodes->count())
|
||||
<table class="table mb-0 tbl-sm" style="color:#c9d1d9">
|
||||
<thead><tr>
|
||||
<th>Anime</th><th>Bölüm</th><th>Oynatma</th><th>Süre</th><th>Ort. %</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
@foreach($topEpisodes as $ep)
|
||||
<tr>
|
||||
<td style="max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ $ep['anime'] }}</td>
|
||||
<td><span class="badge" style="background:var(--border);color:#c9d1d9">{{ $ep['label'] }}</span></td>
|
||||
<td>{{ number_format($ep['plays']) }}</td>
|
||||
<td>{{ $ep['hours'] }} s</td>
|
||||
<td>
|
||||
@php $pct=$ep['avg_pct']??0; @endphp
|
||||
<span class="pct-pill {{ $pct>=70?'pct-high':($pct>=40?'pct-mid':'pct-low') }}">%{{ $pct }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="text-muted text-center py-4" style="font-size:.85rem">Bu dönemde izleme verisi yok</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="chart-card h-100">
|
||||
<div class="section-head">Coğrafi Dağılım (Şehir)</div>
|
||||
@if($geoStats->count())
|
||||
@php $maxGeo = $geoStats->max('cnt') ?: 1; @endphp
|
||||
@foreach($geoStats as $g)
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<span style="font-size:.82rem;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
||||
{{ $g->city }}
|
||||
<span style="color:var(--text2);font-size:.75rem"> · {{ $g->country }}</span>
|
||||
</span>
|
||||
<div class="pbar-wrap" style="width:80px"><div class="pbar-fill" style="width:{{ round($g->cnt/$maxGeo*100) }}%"></div></div>
|
||||
<span style="font-size:.75rem;color:var(--text2);width:32px;text-align:right">{{ $g->cnt }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="text-muted text-center py-4" style="font-size:.85rem">Coğrafi veri yok (IP geolocation bekliyor)</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- En aktif kullanıcılar + AI top sorular --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="chart-card h-100">
|
||||
<div class="section-head">En Aktif Kullanıcılar</div>
|
||||
@if($activeUsers->count())
|
||||
<table class="table mb-0 tbl-sm" style="color:#c9d1d9">
|
||||
<thead><tr>
|
||||
<th>#</th><th>Kullanıcı</th><th>Görüntüleme</th><th>Aktif Gün</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
@foreach($activeUsers as $i => $au)
|
||||
<tr>
|
||||
<td style="color:var(--text2)">{{ $i+1 }}</td>
|
||||
<td>
|
||||
<div style="font-size:.83rem">{{ $au['user']->name }}</div>
|
||||
<div style="font-size:.72rem;color:var(--text2)">{{ $au['user']->email }}</div>
|
||||
</td>
|
||||
<td>{{ number_format($au['views']) }}</td>
|
||||
<td>{{ $au['days'] }} gün</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="text-muted text-center py-4" style="font-size:.85rem">Bu dönemde giriş yapan kullanıcı yok</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="chart-card h-100">
|
||||
<div class="section-head">Popüler AI Soruları (Sohbet)</div>
|
||||
@if($aiTopQuestions->count())
|
||||
@foreach($aiTopQuestions as $q)
|
||||
<div class="d-flex align-items-start gap-2 mb-3">
|
||||
<i class="bi bi-chat-right-text" style="color:var(--accent);margin-top:2px;flex-shrink:0"></i>
|
||||
<div class="flex-1" style="flex:1">
|
||||
<div class="ai-q-text" title="{{ $q->query_text }}">{{ $q->query_text }}</div>
|
||||
<div style="font-size:.7rem;color:var(--text2);margin-top:2px">{{ $q->cnt }} kez soruldu</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="text-muted text-center py-4" style="font-size:.85rem">Bu dönemde chat sorusu yok</div>
|
||||
@endif
|
||||
|
||||
@if($aiTopUsers->count())
|
||||
<div class="section-head mt-3">AI'ı En Çok Kullananlar</div>
|
||||
@foreach($aiTopUsers as $au)
|
||||
<div class="d-flex align-items-center justify-content-between mb-2" style="font-size:.83rem">
|
||||
<span>{{ $au['name'] }}</span>
|
||||
<span class="badge" style="background:var(--border);color:#a371f7">{{ $au['cnt'] }} sorgu</span>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Sayfa türleri + Tarayıcılar --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="chart-card">
|
||||
<div class="section-head">Sayfa Türü Dağılımı</div>
|
||||
@php
|
||||
$ptLabels = ['home'=>'Ana Sayfa','anime'=>'Anime Detay','player'=>'Video Player','search'=>'Arama','ai'=>'AI Hub','genre'=>'Tür','other'=>'Diğer'];
|
||||
$ptColors = ['home'=>'#79c0ff','anime'=>'var(--success)','player'=>'#a371f7','search'=>'#f0883e','ai'=>'var(--accent)','genre'=>'#ffa657','other'=>'#6e7681'];
|
||||
$ptTotal = $pageTypeStats->sum();
|
||||
@endphp
|
||||
<div class="row">
|
||||
@foreach($pageTypeStats->sortDesc() as $pt => $cnt)
|
||||
<div class="col-6 mb-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:{{ $ptColors[$pt]??'var(--text2)' }};flex-shrink:0"></span>
|
||||
<span style="font-size:.82rem;flex:1">{{ $ptLabels[$pt]??$pt }}</span>
|
||||
<span style="font-size:.78rem;color:var(--text2)">{{ number_format($cnt) }}</span>
|
||||
</div>
|
||||
<div class="pbar-wrap mt-1"><div class="pbar-fill" style="width:{{ $ptTotal>0?round($cnt/$ptTotal*100):0 }}%;background:{{ $ptColors[$pt]??'var(--text2)' }}"></div></div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="chart-card">
|
||||
<div class="section-head">Tarayıcı Dağılımı</div>
|
||||
@php $brTotal = $browserStats->sum(); @endphp
|
||||
@foreach($browserStats->sortDesc()->take(6) as $br => $cnt)
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<span style="font-size:.82rem;width:80px">{{ $br ?: 'Diğer' }}</span>
|
||||
<div class="pbar-wrap"><div class="pbar-fill" style="width:{{ $brTotal>0?round($cnt/$brTotal*100):0 }}%;background:var(--accent)"></div></div>
|
||||
<span style="font-size:.78rem;color:var(--text2);width:40px;text-align:right">{{ number_format($cnt) }}</span>
|
||||
<span style="font-size:.72rem;color:#6e7681;width:32px;text-align:right">{{ $brTotal>0?round($cnt/$brTotal*100):0 }}%</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Trafik Kaynakları --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-7">
|
||||
<div class="chart-card h-100">
|
||||
<div class="section-head">Trafik Kaynakları (Referrer)</div>
|
||||
@php
|
||||
$refTotal = $referrerStats->sum() + $directTraffic;
|
||||
$allSources = collect(['Direkt / Boş' => $directTraffic])->merge($referrerStats)->sortDesc();
|
||||
$srcColors = ['Direkt / Boş'=>'var(--text2)'];
|
||||
$palette = ['var(--accent)','var(--success)','#79c0ff','#f0883e','#a371f7','#ffa657','#58a6ff','#7ee787','#ff7bc6','#d2a8ff','#ffa198','#56d364'];
|
||||
$pi = 0;
|
||||
foreach($referrerStats->keys() as $k) { $srcColors[$k] = $palette[$pi % count($palette)]; $pi++; }
|
||||
@endphp
|
||||
@if($allSources->sum() > 0)
|
||||
@foreach($allSources->take(12) as $src => $cnt)
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:{{ $srcColors[$src]??'var(--text2)' }};flex-shrink:0"></span>
|
||||
<span style="font-size:.82rem;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{ $src }}">
|
||||
@if($src === 'Direkt / Boş')
|
||||
<i class="bi bi-link-45deg me-1" style="color:var(--text2)"></i>{{ $src }}
|
||||
@else
|
||||
<i class="bi bi-globe2 me-1" style="color:{{ $srcColors[$src]??'var(--text2)' }}"></i>{{ $src }}
|
||||
@endif
|
||||
</span>
|
||||
<div class="pbar-wrap" style="width:100px"><div class="pbar-fill" style="width:{{ $refTotal>0?round($cnt/$refTotal*100):0 }}%;background:{{ $srcColors[$src]??'var(--text2)' }}"></div></div>
|
||||
<span style="font-size:.78rem;color:var(--text2);width:38px;text-align:right">{{ number_format($cnt) }}</span>
|
||||
<span style="font-size:.72rem;color:#6e7681;width:30px;text-align:right">{{ $refTotal>0?round($cnt/$refTotal*100):0 }}%</span>
|
||||
</div>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="text-muted text-center py-4" style="font-size:.85rem">Bu dönemde referrer verisi yok</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="chart-card h-100">
|
||||
<div class="section-head">Kaynak Dağılımı</div>
|
||||
<div class="chart-wrap" style="height:180px;display:flex;align-items:center;justify-content:center">
|
||||
<canvas id="referrerChart"></canvas>
|
||||
</div>
|
||||
<div class="mt-3 text-center">
|
||||
<div style="font-size:.78rem;color:var(--text2)">
|
||||
Toplam kayıtlı trafik: <strong style="color:var(--text)">{{ number_format($refTotal) }}</strong>
|
||||
</div>
|
||||
<div style="font-size:.75rem;color:#6e7681;margin-top:4px">
|
||||
Direkt: {{ $refTotal>0?round($directTraffic/$refTotal*100):0 }}% · Referral: {{ $refTotal>0?round($referrerStats->sum()/$refTotal*100):0 }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Son aktiviteler (canlı akış) --}}
|
||||
<div class="chart-card mb-4">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<div class="section-head mb-0">Son Sayfa Görüntülemeleri</div>
|
||||
<span class="live-dot"></span>
|
||||
</div>
|
||||
<table class="table mb-0 tbl-sm" style="color:#c9d1d9">
|
||||
<thead><tr>
|
||||
<th>Zaman</th><th>Kullanıcı</th><th>Sayfa Türü</th><th>URL</th><th>Şehir</th><th>Cihaz</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
@forelse($recentViews as $rv)
|
||||
<tr>
|
||||
<td style="white-space:nowrap;color:var(--text2);font-size:.75rem">{{ $rv->created_at->diffForHumans() }}</td>
|
||||
<td>
|
||||
@if($rv->user)
|
||||
<span style="font-size:.82rem">{{ $rv->user->name }}</span>
|
||||
@else
|
||||
<span style="color:#6e7681;font-size:.78rem">Misafir</span>
|
||||
@endif
|
||||
</td>
|
||||
<td><span class="badge-type badge-{{ $rv->page_type }}">{{ $rv->page_type }}</span></td>
|
||||
<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.75rem;color:var(--text2)" title="{{ $rv->url }}">{{ $rv->url }}</td>
|
||||
<td style="font-size:.78rem">{{ $rv->city ?: '—' }}</td>
|
||||
<td><i class="bi bi-{{ match($rv->device){'mobile'=>'phone','tablet'=>'tablet',default=>'laptop'} }}" style="color:var(--text2)"></i></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="text-center text-muted py-4" style="font-size:.85rem">Bu dönemde görüntüleme yok</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ── BOT TRAFİK PANELİ ─────────────────────────────────────────────────── --}}
|
||||
<div class="an-card mt-4">
|
||||
<div class="section-head" style="display:flex;align-items:center;justify-content:space-between">
|
||||
<span>🤖 Bot & Güvenlik</span>
|
||||
<span style="font-size:.78rem;color:var(--text2)">Son dönem</span>
|
||||
</div>
|
||||
|
||||
{{-- Bot özet kartları --}}
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:20px">
|
||||
<div class="an-card" style="text-align:center">
|
||||
<div style="font-size:1.6rem;font-weight:800;color:var(--danger)">{{ number_format($botViews) }}</div>
|
||||
<div style="font-size:.78rem;color:var(--text2)">Bot İsteği</div>
|
||||
</div>
|
||||
<div class="an-card" style="text-align:center">
|
||||
<div style="font-size:1.6rem;font-weight:800;color:var(--success)">{{ number_format($humanViews) }}</div>
|
||||
<div style="font-size:.78rem;color:var(--text2)">İnsan İsteği</div>
|
||||
</div>
|
||||
<div class="an-card" style="text-align:center">
|
||||
<div style="font-size:1.6rem;font-weight:800;color:{{ ($botRatio ?? 0) > 50 ? 'var(--danger)' : '#d29922' }}">{{ $botRatio ?? 0 }}%</div>
|
||||
<div style="font-size:.78rem;color:var(--text2)">Bot Oranı</div>
|
||||
</div>
|
||||
<div class="an-card" style="text-align:center">
|
||||
<div style="font-size:1.6rem;font-weight:800;color:var(--info)">{{ $blockedIps->count() }}</div>
|
||||
<div style="font-size:.78rem;color:var(--text2)">Engelli IP</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px">
|
||||
|
||||
{{-- En çok vuran botlar --}}
|
||||
<div>
|
||||
<div style="font-size:.82rem;font-weight:700;color:var(--text);margin-bottom:10px">Top Bot IP'leri</div>
|
||||
<table class="table table-sm" style="font-size:.78rem">
|
||||
<thead><tr><th>IP</th><th>İstek</th><th>Durum</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($botTopIps as $b)
|
||||
<tr>
|
||||
<td><code style="color:var(--info);font-size:.75rem">{{ $b->ip }}</code></td>
|
||||
<td>{{ number_format($b->cnt) }}</td>
|
||||
<td>
|
||||
<span style="font-size:.7rem;padding:2px 7px;border-radius:4px;background:{{ $b->action === 'blocked' ? 'rgba(248,81,73,.15)' : 'rgba(63,185,80,.15)' }};color:{{ $b->action === 'blocked' ? 'var(--danger)' : 'var(--success)' }}">
|
||||
{{ $b->action }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form method="POST" action="{{ route('admin.analytics.block-ip') }}" style="display:inline">
|
||||
@csrf
|
||||
<input type="hidden" name="ip" value="{{ $b->ip }}">
|
||||
<input type="hidden" name="reason" value="Admin: bot trafiği">
|
||||
<button type="submit" class="btn btn-sm" style="padding:1px 8px;font-size:.7rem;background:rgba(248,81,73,.15);border:1px solid rgba(248,81,73,.3);color:var(--danger);border-radius:4px">Engelle</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="4" class="text-muted text-center py-3">Bot logu yok</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- Bot türleri --}}
|
||||
<div>
|
||||
<div style="font-size:.82rem;font-weight:700;color:var(--text);margin-bottom:10px">Bot Türleri</div>
|
||||
<table class="table table-sm" style="font-size:.78rem">
|
||||
<thead><tr><th>Bot</th><th>İstek</th><th>Durum</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($botByName as $b)
|
||||
<tr>
|
||||
<td style="max-width:150px;overflow:hidden;text-overflow:ellipsis">{{ $b->bot_name ?: '—' }}</td>
|
||||
<td>{{ number_format($b->cnt) }}</td>
|
||||
<td>
|
||||
<span style="font-size:.7rem;padding:2px 7px;border-radius:4px;background:{{ $b->action === 'blocked' ? 'rgba(248,81,73,.15)' : 'rgba(210,153,34,.15)' }};color:{{ $b->action === 'blocked' ? 'var(--danger)' : '#d29922' }}">
|
||||
{{ $b->action }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="3" class="text-muted text-center py-3">Kayıt yok</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Manuel IP Engelleme --}}
|
||||
<div style="margin-top:20px;border-top:1px solid var(--border);padding-top:16px">
|
||||
<div style="font-size:.82rem;font-weight:700;color:var(--text);margin-bottom:10px">Manuel IP Engelle</div>
|
||||
<form method="POST" action="{{ route('admin.analytics.block-ip') }}" style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end">
|
||||
@csrf
|
||||
<div>
|
||||
<label style="font-size:.75rem;color:var(--text2);display:block;margin-bottom:4px">IP Adresi</label>
|
||||
<input type="text" name="ip" class="form-control form-control-sm" placeholder="1.2.3.4" style="width:160px">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:.75rem;color:var(--text2);display:block;margin-bottom:4px">Sebep</label>
|
||||
<input type="text" name="reason" class="form-control form-control-sm" placeholder="Manuel engel" style="width:200px">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:.75rem;color:var(--text2);display:block;margin-bottom:4px">Bitiş (boşsa kalıcı)</label>
|
||||
<input type="date" name="expires_at" class="form-control form-control-sm" style="width:150px">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-sm" style="background:rgba(248,81,73,.2);border:1px solid rgba(248,81,73,.4);color:var(--danger);border-radius:6px">Engelle</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Engelli IP listesi --}}
|
||||
@if($blockedIps->count())
|
||||
<div style="margin-top:16px">
|
||||
<div style="font-size:.82rem;font-weight:700;color:var(--text);margin-bottom:8px">Aktif Engeller</div>
|
||||
<table class="table table-sm" style="font-size:.78rem">
|
||||
<thead><tr><th>IP</th><th>Sebep</th><th>Tür</th><th>Bitiş</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
@foreach($blockedIps as $bi)
|
||||
<tr>
|
||||
<td><code style="color:var(--danger);font-size:.75rem">{{ $bi->ip }}</code></td>
|
||||
<td>{{ $bi->reason ?: '—' }}</td>
|
||||
<td><span style="font-size:.7rem;color:{{ $bi->auto_blocked ? '#d29922' : 'var(--text2)' }}">{{ $bi->auto_blocked ? 'Otomatik' : 'Manuel' }}</span></td>
|
||||
<td style="font-size:.75rem;color:var(--text2)">{{ $bi->expires_at ? \Carbon\Carbon::parse($bi->expires_at)->diffForHumans() : 'Kalıcı' }}</td>
|
||||
<td>
|
||||
<form method="POST" action="{{ route('admin.analytics.unblock-ip') }}" style="display:inline">
|
||||
@csrf
|
||||
<input type="hidden" name="ip" value="{{ $bi->ip }}">
|
||||
<button type="submit" class="btn btn-sm" style="padding:1px 8px;font-size:.7rem;background:rgba(63,185,80,.1);border:1px solid rgba(63,185,80,.3);color:var(--success);border-radius:4px">Kaldır</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- ── OTURUM DETAYLARI ────────────────────────────────────────────────────── --}}
|
||||
<div class="an-card mt-4">
|
||||
<div class="section-head" style="display:flex;align-items:center;justify-content:space-between">
|
||||
<span>📊 Oturum Analizi</span>
|
||||
<div style="display:flex;gap:16px;font-size:.82rem">
|
||||
<span>Ort. Süre: <strong style="color:var(--info)">{{ gmdate('i:s', $avgSessionTime) }}</strong></span>
|
||||
<span>Ort. Sayfa: <strong style="color:var(--info)">{{ $avgPages }}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-sm" style="font-size:.78rem">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP</th><th>Ülke</th><th>Cihaz</th><th>Tarayıcı</th>
|
||||
<th>Sayfalar</th><th>Süre</th><th>Kaynak</th><th>Bot?</th><th>Başlangıç</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($sessions as $s)
|
||||
<tr style="{{ $s->is_bot ? 'opacity:.5' : '' }}">
|
||||
<td><code style="font-size:.72rem;color:var(--info)">{{ $s->ip }}</code></td>
|
||||
<td>{{ $s->country ?: '—' }}</td>
|
||||
<td><i class="bi bi-{{ match($s->device ?? ''){ 'mobile'=>'phone','tablet'=>'tablet',default=>'laptop'} }}"></i></td>
|
||||
<td>{{ $s->browser ?: '—' }}</td>
|
||||
<td>{{ $s->pages_visited }}</td>
|
||||
<td>{{ gmdate('i:s', $s->total_seconds) }}</td>
|
||||
<td style="max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{ $s->referrer }}">{{ $s->referrer ? parse_url($s->referrer, PHP_URL_HOST) : 'Direkt' }}</td>
|
||||
<td>
|
||||
@if($s->is_bot)
|
||||
<span style="font-size:.7rem;color:var(--danger)">{{ $s->bot_type }}</span>
|
||||
@else
|
||||
<span style="font-size:.7rem;color:var(--success)">İnsan</span>
|
||||
@endif
|
||||
</td>
|
||||
<td style="color:var(--text2)">{{ \Carbon\Carbon::parse($s->started_at)->diffForHumans() }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="9" class="text-center text-muted py-4">Oturum kaydı yok</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
Chart.defaults.color = 'var(--text2)';
|
||||
Chart.defaults.borderColor = 'var(--border)';
|
||||
|
||||
// ── Trend grafiği ────────────────────────────────────────────────────────────
|
||||
new Chart(document.getElementById('trendChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: @json($trendLabels),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Görüntüleme',
|
||||
data: @json($trendData),
|
||||
borderColor: 'var(--accent)',
|
||||
backgroundColor: 'rgba(232,67,147,.08)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 3,
|
||||
pointBackgroundColor: 'var(--accent)',
|
||||
yAxisID: 'y',
|
||||
},
|
||||
{
|
||||
label: 'İzleme (saat)',
|
||||
data: @json($watchTrendData),
|
||||
borderColor: '#a371f7',
|
||||
backgroundColor: 'rgba(163,113,247,.08)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 3,
|
||||
pointBackgroundColor: '#a371f7',
|
||||
yAxisID: 'y1',
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: { grid: { color: 'var(--border)' }, ticks: { maxTicksLimit: 12, font: { size: 10 } } },
|
||||
y: { grid: { color: 'var(--border)' }, ticks: { font: { size: 10 } }, beginAtZero: true },
|
||||
y1: { position: 'right', grid: { display: false }, ticks: { font: { size: 10 } }, beginAtZero: true },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Cihaz pasta grafiği ──────────────────────────────────────────────────────
|
||||
@php
|
||||
$dvKeys = ['mobile','desktop','tablet','unknown'];
|
||||
$dvData = array_map(fn($k) => $deviceStats[$k] ?? 0, $dvKeys);
|
||||
@endphp
|
||||
new Chart(document.getElementById('deviceChart'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['Mobil', 'Masaüstü', 'Tablet', 'Diğer'],
|
||||
datasets: [{
|
||||
data: @json($dvData),
|
||||
backgroundColor: ['var(--accent)','var(--success)','#79c0ff','#6e7681'],
|
||||
borderWidth: 0,
|
||||
hoverOffset: 4,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false }, tooltip: { callbacks: {
|
||||
label: ctx => ` ${ctx.label}: ${ctx.parsed.toLocaleString()}`
|
||||
}}}
|
||||
}
|
||||
});
|
||||
|
||||
// ── AI sorgu türleri grafiği ─────────────────────────────────────────────────
|
||||
@php
|
||||
$aiK = $aiByType->keys()->toArray();
|
||||
$aiV = $aiByType->values()->toArray();
|
||||
$aiColorMap = ['chat'=>'var(--accent)','recommend'=>'#a371f7','search'=>'var(--success)','episode_info'=>'#f0883e','similar'=>'#79c0ff'];
|
||||
$aiC = array_map(fn($k) => $aiColorMap[$k] ?? 'var(--text2)', $aiK);
|
||||
$aiLabelMap = ['chat'=>'Sohbet','recommend'=>'Öneri','search'=>'Arama','episode_info'=>'Bölüm','similar'=>'Benzer'];
|
||||
$aiL = array_map(fn($k) => $aiLabelMap[$k] ?? $k, $aiK);
|
||||
@endphp
|
||||
new Chart(document.getElementById('aiChart'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: @json($aiL),
|
||||
datasets: [{
|
||||
data: @json($aiV),
|
||||
backgroundColor: @json($aiC),
|
||||
borderWidth: 0,
|
||||
hoverOffset: 4,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false } }
|
||||
}
|
||||
});
|
||||
|
||||
// ── Saatlik dağılım ──────────────────────────────────────────────────────────
|
||||
new Chart(document.getElementById('hourlyChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: Array.from({length:24}, (_,i) => `${String(i).padStart(2,'0')}:00`),
|
||||
datasets: [{
|
||||
label: 'Görüntüleme',
|
||||
data: @json($hourlyData),
|
||||
backgroundColor: 'rgba(232,67,147,.5)',
|
||||
borderColor: 'var(--accent)',
|
||||
borderWidth: 1,
|
||||
borderRadius: 3,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: { grid: { display: false }, ticks: { font: { size: 9 } } },
|
||||
y: { grid: { color: 'var(--border)' }, ticks: { font: { size: 10 } }, beginAtZero: true },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Referrer pasta grafiği ────────────────────────────────────────────────────
|
||||
@php
|
||||
$refChartLabels = $allSources->take(8)->keys()->map(fn($k) => strlen($k)>22 ? substr($k,0,20).'…' : $k)->values()->toArray();
|
||||
$refChartData = $allSources->take(8)->values()->toArray();
|
||||
$refChartColors = $allSources->take(8)->keys()->map(fn($k) => $srcColors[$k] ?? 'var(--text2)')->values()->toArray();
|
||||
@endphp
|
||||
new Chart(document.getElementById('referrerChart'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: @json($refChartLabels),
|
||||
datasets: [{
|
||||
data: @json($refChartData),
|
||||
backgroundColor: @json($refChartColors),
|
||||
borderWidth: 0,
|
||||
hoverOffset: 4,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false }, tooltip: { callbacks: {
|
||||
label: ctx => ` ${ctx.label}: ${ctx.parsed.toLocaleString()}`
|
||||
}}}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Otomatik yenileme (60 saniye) ────────────────────────────────────────────
|
||||
setTimeout(() => location.reload(), 60000);
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
@extends('admin.layouts.app')
|
||||
@section('title', 'Kullanıcı Aktivitesi — ' . $user->name)
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<a href="{{ route('admin.analytics.users', ['tab'=>'activity']) }}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i></a>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
@if($user->avatar)
|
||||
<img src="{{ $user->avatar }}" class="rounded-circle" width="48" height="48" style="object-fit:cover">
|
||||
@else
|
||||
<div class="rounded-circle bg-info d-flex align-items-center justify-content-center text-dark fw-bold" style="width:48px;height:48px;font-size:18px">{{ strtoupper(substr($user->name,0,1)) }}</div>
|
||||
@endif
|
||||
<div>
|
||||
<h4 class="mb-0">{{ $user->name }} <span class="badge {{ $user->role==='admin'?'bg-danger':($user->role==='moderator'?'bg-warning text-dark':'bg-secondary') }}">{{ $user->role }}</span></h4>
|
||||
<small class="text-muted">{{ $user->email }} · Üyelik: {{ $user->created_at->format('d.m.Y') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-auto d-flex gap-2">
|
||||
<form class="d-flex gap-2">
|
||||
<input type="hidden" name="period" value="{{ $period }}">
|
||||
<select name="period" class="form-select form-select-sm" onchange="this.form.submit()">
|
||||
@foreach([7=>'Son 7 Gün',14=>'Son 14 Gün',30=>'Son 30 Gün',90=>'Son 90 Gün'] as $d=>$l)
|
||||
<option value="{{ $d }}" {{ $period==$d?'selected':'' }}>{{ $l }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</form>
|
||||
<a href="{{ route('admin.users.show', $user) }}" class="btn btn-sm btn-outline-info">
|
||||
<i class="bi bi-person me-1"></i>Profili Gör
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
{{-- Aksiyon Özeti --}}
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong>Aksiyon Özeti</strong></div>
|
||||
<div class="card-body">
|
||||
@forelse($actBreakdown as $ab)
|
||||
@php $labels = \App\Models\UserActivityLog::$actionLabels; @endphp
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span style="font-size:12px">{{ $labels[$ab->action] ?? $ab->action }}</span>
|
||||
<span class="badge bg-info text-dark">{{ $ab->cnt }}</span>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-muted text-center small">Bu dönemde aktivite yok</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Son izleme --}}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header"><strong><i class="bi bi-play-circle me-1"></i>Son İzlemeler</strong></div>
|
||||
<div class="list-group list-group-flush">
|
||||
@forelse($watchEvents as $w)
|
||||
<div class="list-group-item py-2 px-3">
|
||||
<div class="fw-semibold" style="font-size:12px">{{ $w->anime_title }}</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<small class="text-muted">Bölüm {{ $w->episode_number }}</small>
|
||||
<small class="text-{{ $w->percent_complete >= 80 ? 'success' : 'warning' }}">%{{ $w->percent_complete }}</small>
|
||||
</div>
|
||||
<div style="height:3px;background:#333;border-radius:2px;margin-top:4px;overflow:hidden">
|
||||
<div style="width:{{ $w->percent_complete }}%;height:100%;background:var(--bs-info)"></div>
|
||||
</div>
|
||||
<small class="text-muted" style="font-size:10px">{{ \Carbon\Carbon::parse($w->created_at)->diffForHumans() }}</small>
|
||||
</div>
|
||||
@empty
|
||||
<div class="list-group-item text-muted small text-center py-3">İzleme verisi yok</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Aktivite Log Tablosu --}}
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong>Detaylı Aktivite Günlüğü</strong></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Aksiyon</th><th>Konu</th><th>IP</th><th>Ülke</th><th>Cihaz</th><th>Zaman</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($logs as $log)
|
||||
@php $labels = \App\Models\UserActivityLog::$actionLabels; @endphp
|
||||
<tr>
|
||||
<td><span class="badge bg-info text-dark" style="font-size:10px">{{ $labels[$log->action] ?? $log->action }}</span></td>
|
||||
<td><small class="text-muted">{{ $log->subject_type ? class_basename($log->subject_type).'#'.$log->subject_id : '—' }}</small></td>
|
||||
<td><code style="font-size:10px">{{ $log->ip }}</code></td>
|
||||
<td><small class="text-muted">{{ $log->country ?: '—' }}</small></td>
|
||||
<td><small class="text-muted text-capitalize">{{ $log->device ?: '—' }}</small></td>
|
||||
<td><small class="text-muted">{{ \Carbon\Carbon::parse($log->created_at)->format('d.m.Y H:i') }}</small></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="text-center text-muted py-4">Aktivite bulunamadı</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer">{{ $logs->links() }}</div>
|
||||
</div>
|
||||
|
||||
{{-- Son Sayfa Görüntülemeleri --}}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header"><strong><i class="bi bi-eye me-1"></i>Son Sayfa Görüntülemeleri</strong></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-dark"><tr><th>URL</th><th>Sayfa Tipi</th><th>Süre</th><th>Zaman</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($pageviews as $pv)
|
||||
<tr>
|
||||
<td><small class="text-muted" style="max-width:200px;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{ $pv->url }}">{{ $pv->url }}</small></td>
|
||||
<td><span class="badge bg-secondary" style="font-size:9px">{{ $pv->page_type }}</span></td>
|
||||
<td><small class="text-muted">{{ isset($pv->time_on_page) && $pv->time_on_page > 0 ? $pv->time_on_page.'s' : '—' }}</small></td>
|
||||
<td><small class="text-muted">{{ \Carbon\Carbon::parse($pv->created_at)->format('d.m H:i') }}</small></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="4" class="text-center text-muted py-3">Sayfa görüntüleme yok</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,375 @@
|
||||
@extends('admin.layouts.app')
|
||||
@section('title', 'Kullanıcı Analitiği')
|
||||
|
||||
@section('content')
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1"><i class="bi bi-people me-2 text-info"></i>Kullanıcı Analitiği</h4>
|
||||
<p class="text-muted mb-0 small">Gerçek kullanıcılar, bot trafiği, ülke dağılımı ve aktivite takibi</p>
|
||||
</div>
|
||||
<form class="d-flex gap-2 align-items-center" method="GET">
|
||||
<input type="hidden" name="tab" value="{{ $tab }}">
|
||||
<select name="period" class="form-select form-select-sm" onchange="this.form.submit()">
|
||||
@foreach([7=>'Son 7 Gün',14=>'Son 14 Gün',30=>'Son 30 Gün',90=>'Son 90 Gün'] as $d=>$l)
|
||||
<option value="{{ $d }}" {{ $period==$d?'selected':'' }}>{{ $l }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@if($country)
|
||||
<a href="{{ request()->fullUrlWithoutQuery(['country']) }}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-x"></i> {{ $country }}</a>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Overview Cards --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-6 col-md-2">
|
||||
<div class="card text-center py-3 border-info">
|
||||
<div class="h3 mb-0 text-info">{{ number_format($totalReal) }}</div>
|
||||
<small class="text-muted">Toplam Kullanıcı</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<div class="card text-center py-3 border-success">
|
||||
<div class="h3 mb-0 text-success">+{{ number_format($newReal) }}</div>
|
||||
<small class="text-muted">Yeni Üye</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<div class="card text-center py-3 border-primary">
|
||||
<div class="h3 mb-0 text-primary">{{ number_format($active30) }}</div>
|
||||
<small class="text-muted">Aktif Kullanıcı</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<div class="card text-center py-3 border-success">
|
||||
<div class="h3 mb-0 text-success">{{ number_format($realViews) }}</div>
|
||||
<small class="text-muted">Gerçek Görüntüleme</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<div class="card text-center py-3 border-danger">
|
||||
<div class="h3 mb-0 text-danger">{{ number_format($botViews) }}</div>
|
||||
<small class="text-muted">Bot Görüntüleme</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<div class="card text-center py-3">
|
||||
@php $ratio = ($realViews+$botViews) > 0 ? round($botViews/($realViews+$botViews)*100,1) : 0; @endphp
|
||||
<div class="h3 mb-0 {{ $ratio > 30 ? 'text-danger' : 'text-warning' }}">{{ $ratio }}%</div>
|
||||
<small class="text-muted">Bot Oranı</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tabs --}}
|
||||
<ul class="nav nav-tabs mb-3">
|
||||
@foreach(['overview'=>'Genel Bakış','activity'=>'Aktivite Günlüğü','bots'=>'Bot Analizi','country'=>'Ülke Dağılımı'] as $t=>$l)
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ $tab==$t?'active':'' }}" href="{{ request()->fullUrlWithQuery(['tab'=>$t]) }}">{{ $l }}</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
{{-- ── Overview Tab ──────────────────────────────────────────────────────────── --}}
|
||||
@if($tab === 'overview')
|
||||
<div class="row g-4">
|
||||
{{-- Daily new users chart --}}
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong>Günlük Yeni Üye</strong></div>
|
||||
<div class="card-body"><canvas id="dailyChart" height="80"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Device breakdown --}}
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong>Cihaz Dağılımı (gerçek kullanıcı)</strong></div>
|
||||
<div class="card-body">
|
||||
@foreach($deviceBreakdown as $dev)
|
||||
@php $pct = $realViews > 0 ? round($dev->cnt/$realViews*100,1) : 0; @endphp
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="text-capitalize">{{ $dev->device ?: 'bilinmiyor' }}</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div style="width:80px;height:6px;background:#333;border-radius:3px;overflow:hidden">
|
||||
<div style="width:{{ $pct }}%;height:100%;background:var(--bs-info)"></div>
|
||||
</div>
|
||||
<small class="text-muted">{{ number_format($dev->cnt) }} ({{ $pct }}%)</small>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Top active users --}}
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong>En Aktif Kullanıcılar</strong></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-dark"><tr><th>#</th><th>Kullanıcı</th><th>Aksiyon</th></tr></thead>
|
||||
<tbody>
|
||||
@foreach($topUsers as $i => $tu)
|
||||
@php $u = $topUserMap[$tu->user_id] ?? null; @endphp
|
||||
<tr>
|
||||
<td class="text-muted">{{ $i+1 }}</td>
|
||||
<td>
|
||||
@if($u)
|
||||
<a href="{{ route('admin.analytics.user-detail', $u) }}" class="text-decoration-none">{{ $u->name }}</a>
|
||||
<br><small class="text-muted">{{ $u->email }}</small>
|
||||
@else <span class="text-muted">#{{ $tu->user_id }}</span> @endif
|
||||
</td>
|
||||
<td><span class="badge bg-primary">{{ number_format($tu->actions) }}</span></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Action breakdown --}}
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong>Aksiyon Dağılımı</strong></div>
|
||||
<div class="card-body">
|
||||
@foreach($actionBreakdown as $ab)
|
||||
@php $labels = \App\Models\UserActivityLog::$actionLabels; @endphp
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span style="font-size:12px">{{ $labels[$ab->action] ?? $ab->action }}</span>
|
||||
<span class="badge bg-secondary">{{ number_format($ab->cnt) }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
@if($actionBreakdown->isEmpty()) <p class="text-muted text-center small py-3">Henüz aktivite yok</p> @endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Activity Tab ──────────────────────────────────────────────────────────── --}}
|
||||
@elseif($tab === 'activity')
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong>Aktivite Günlüğü</strong>
|
||||
<form class="d-flex gap-2" method="GET">
|
||||
<input type="hidden" name="tab" value="activity">
|
||||
<input type="hidden" name="period" value="{{ $period }}">
|
||||
<select name="action" class="form-select form-select-sm" onchange="this.form.submit()">
|
||||
<option value="">Tüm Aksiyonlar</option>
|
||||
@foreach(\App\Models\UserActivityLog::$actionLabels as $k=>$v)
|
||||
<option value="{{ $k }}" {{ request('action')==$k?'selected':'' }}>{{ $v }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="text" name="user_id" class="form-control form-control-sm" placeholder="User ID" value="{{ request('user_id') }}" style="width:100px">
|
||||
<input type="text" name="country" class="form-control form-control-sm" placeholder="Ülke" value="{{ $country }}" style="width:80px">
|
||||
<button type="submit" class="btn btn-sm btn-outline-info"><i class="bi bi-search"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-dark">
|
||||
<tr><th>Kullanıcı</th><th>Aksiyon</th><th>Konu</th><th>IP</th><th>Ülke</th><th>Cihaz</th><th>Zaman</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($actLogs as $log)
|
||||
@php $labels = \App\Models\UserActivityLog::$actionLabels; @endphp
|
||||
<tr>
|
||||
<td>
|
||||
@if($log->user)
|
||||
<a href="{{ route('admin.analytics.user-detail', $log->user) }}" class="text-decoration-none small">{{ $log->user->name }}</a>
|
||||
@else <span class="text-muted small">Misafir</span> @endif
|
||||
</td>
|
||||
<td><span class="badge bg-info text-dark" style="font-size:10px">{{ $labels[$log->action] ?? $log->action }}</span></td>
|
||||
<td><small class="text-muted">{{ $log->subject_type ? class_basename($log->subject_type).'#'.$log->subject_id : '—' }}</small></td>
|
||||
<td><code style="font-size:10px">{{ $log->ip }}</code></td>
|
||||
<td>
|
||||
@if($log->country)
|
||||
<a href="{{ request()->fullUrlWithQuery(['tab'=>'activity','country'=>$log->country]) }}" class="badge bg-secondary text-decoration-none" style="font-size:10px">{{ $log->country }}</a>
|
||||
@else — @endif
|
||||
</td>
|
||||
<td><small class="text-muted text-capitalize">{{ $log->device }}</small></td>
|
||||
<td><small class="text-muted">{{ \Carbon\Carbon::parse($log->created_at)->diffForHumans() }}</small></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" class="text-center text-muted py-4">Kayıt bulunamadı</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer">{{ $actLogs->links() }}</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Bots Tab ──────────────────────────────────────────────────────────────── --}}
|
||||
@elseif($tab === 'bots')
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong><i class="bi bi-robot me-1 text-danger"></i>Bot Trafik Analizi</strong></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-dark"><tr><th>Bot / Araç</th><th>Aksiyon</th><th>İstek</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($botStats as $b)
|
||||
<tr>
|
||||
<td><code style="font-size:11px">{{ $b->bot_name }}</code></td>
|
||||
<td>
|
||||
<span class="badge {{ $b->action==='blocked'?'bg-danger':($b->action==='allowed'?'bg-success':'bg-warning text-dark') }}" style="font-size:10px">
|
||||
{{ $b->action }}
|
||||
</span>
|
||||
</td>
|
||||
<td><span class="badge bg-secondary">{{ number_format($b->cnt) }}</span></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="3" class="text-center text-muted py-3">Bot logu bulunamadı</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong><i class="bi bi-geo-alt me-1 text-danger"></i>En Fazla İstek Atan IP'ler (Botlar)</strong></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-dark"><tr><th>IP</th><th>İstek</th><th>Durum</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($topBotIps as $bip)
|
||||
@php $blocked = $blockedIps->firstWhere('ip', $bip->ip); @endphp
|
||||
<tr>
|
||||
<td><code style="font-size:11px">{{ $bip->ip }}</code></td>
|
||||
<td><span class="badge bg-danger">{{ number_format($bip->cnt) }}</span></td>
|
||||
<td>
|
||||
@if($blocked)
|
||||
<span class="badge bg-warning text-dark" style="font-size:10px">Engelli</span>
|
||||
@else
|
||||
<span class="badge bg-secondary" style="font-size:10px">Serbest</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if(!$blocked)
|
||||
<form method="POST" action="{{ route('admin.analytics.block-ip') }}" class="d-inline">
|
||||
@csrf <input type="hidden" name="ip" value="{{ $bip->ip }}">
|
||||
<input type="hidden" name="reason" value="Bot trafiği — otomatik">
|
||||
<button class="btn btn-xs btn-danger py-0 px-1" style="font-size:10px">Engelle</button>
|
||||
</form>
|
||||
@else
|
||||
<form method="POST" action="{{ route('admin.analytics.unblock-ip') }}" class="d-inline">
|
||||
@csrf <input type="hidden" name="ip" value="{{ $bip->ip }}">
|
||||
<button class="btn btn-xs btn-success py-0 px-1" style="font-size:10px">Serbest Bırak</button>
|
||||
</form>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="4" class="text-center text-muted py-3">Bot IP kaydı yok</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header"><strong><i class="bi bi-shield-x me-1 text-warning"></i>Engelli IP'ler ({{ $blockedIps->count() }})</strong></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm mb-0">
|
||||
<thead class="table-dark"><tr><th>IP</th><th>Sebep</th><th>Otomatik</th><th>Engellenme</th><th>Bitiş</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($blockedIps as $bi)
|
||||
<tr>
|
||||
<td><code style="font-size:11px">{{ $bi->ip }}</code></td>
|
||||
<td><small class="text-muted">{{ $bi->reason }}</small></td>
|
||||
<td>{{ $bi->auto_blocked ? '<span class="badge bg-warning text-dark" style="font-size:10px">Otomatik</span>' : '<span class="badge bg-secondary" style="font-size:10px">Manuel</span>' }}</td>
|
||||
<td><small class="text-muted">{{ \Carbon\Carbon::parse($bi->blocked_at)->format('d.m.Y H:i') }}</small></td>
|
||||
<td><small class="text-muted">{{ $bi->expires_at ? \Carbon\Carbon::parse($bi->expires_at)->diffForHumans() : 'Kalıcı' }}</small></td>
|
||||
<td>
|
||||
<form method="POST" action="{{ route('admin.analytics.unblock-ip') }}" class="d-inline">
|
||||
@csrf <input type="hidden" name="ip" value="{{ $bi->ip }}">
|
||||
<button class="btn btn-xs btn-outline-success py-0 px-1" style="font-size:10px">Serbest</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="text-center text-muted py-3">Engelli IP yok</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Country Tab ───────────────────────────────────────────────────────────── --}}
|
||||
@elseif($tab === 'country')
|
||||
<div class="card">
|
||||
<div class="card-header"><strong><i class="bi bi-globe me-1"></i>Ülke Bazlı Trafik (Gerçek Kullanıcı)</strong></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-dark"><tr><th>#</th><th>Ülke</th><th>Görüntüleme</th><th>Benzersiz Kullanıcı</th><th>Oran</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
@php $totalViews = $countries->sum('views'); @endphp
|
||||
@forelse($countries as $i => $c)
|
||||
@php $pct = $totalViews > 0 ? round($c->views/$totalViews*100,1) : 0; @endphp
|
||||
<tr>
|
||||
<td class="text-muted">{{ $i+1 }}</td>
|
||||
<td class="fw-semibold">{{ $c->country ?: 'Bilinmiyor' }}</td>
|
||||
<td>{{ number_format($c->views) }}</td>
|
||||
<td>{{ number_format($c->users) }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div style="width:100px;height:6px;background:#333;border-radius:3px;overflow:hidden">
|
||||
<div style="width:{{ $pct }}%;height:100%;background:var(--bs-info)"></div>
|
||||
</div>
|
||||
<small class="text-muted">{{ $pct }}%</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ request()->fullUrlWithQuery(['tab'=>'activity','country'=>$c->country]) }}" class="btn btn-xs btn-outline-info py-0 px-1" style="font-size:10px">Aktivite</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="text-center text-muted py-4">Ülke verisi bulunamadı</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
@if($tab === 'overview')
|
||||
const labels = @json($dailyNew->keys());
|
||||
const data = @json($dailyNew->values());
|
||||
new Chart(document.getElementById('dailyChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
label: 'Yeni Üye',
|
||||
data,
|
||||
backgroundColor: 'rgba(13,202,240,0.5)',
|
||||
borderColor: 'rgba(13,202,240,1)',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
x: { grid: { color: '#333' }, ticks: { color: '#aaa', maxRotation: 45 } },
|
||||
y: { grid: { color: '#333' }, ticks: { color: '#aaa' }, beginAtZero: true },
|
||||
}
|
||||
}
|
||||
});
|
||||
@endif
|
||||
</script>
|
||||
@endpush
|
||||
Reference in New Issue
Block a user