275 lines
12 KiB
PHP
275 lines
12 KiB
PHP
@extends('admin.layouts.app')
|
||
@section('title','Trend Yönetimi')
|
||
@section('page-title','Trend Yönetimi')
|
||
|
||
@push('styles')
|
||
<style>
|
||
.trend-item {
|
||
display:flex; align-items:center; gap:12px;
|
||
padding:12px 16px;
|
||
border-bottom:1px solid var(--border);
|
||
transition:background .12s;
|
||
}
|
||
.trend-item:last-child { border-bottom:none; }
|
||
.trend-item:hover { background:rgba(255,255,255,0.02); }
|
||
.trend-rank {
|
||
width:28px; height:28px;
|
||
border-radius:8px;
|
||
background:var(--surface);
|
||
display:flex; align-items:center; justify-content:center;
|
||
font-size:12px; font-weight:800; color:var(--text2);
|
||
flex-shrink:0;
|
||
}
|
||
.trend-cover {
|
||
width:36px; height:52px;
|
||
object-fit:cover; border-radius:6px;
|
||
flex-shrink:0;
|
||
}
|
||
.trend-cover-ph {
|
||
width:36px; height:52px;
|
||
background:var(--surface);
|
||
border-radius:6px; flex-shrink:0;
|
||
display:flex; align-items:center; justify-content:center;
|
||
color:var(--text3); font-size:14px;
|
||
}
|
||
.search-result-item {
|
||
display:flex; align-items:center; gap:10px;
|
||
padding:10px 12px; border-radius:8px;
|
||
margin-bottom:4px;
|
||
background:rgba(255,255,255,0.03);
|
||
border:1px solid var(--border);
|
||
}
|
||
</style>
|
||
@endpush
|
||
|
||
@section('content')
|
||
|
||
<div class="page-header">
|
||
<div>
|
||
<h5 style="font-size:18px;font-weight:700;color:var(--text);margin:0">Trend Yönetimi</h5>
|
||
<div style="font-size:13px;color:var(--text2);margin-top:2px">
|
||
Ana sayfada gösterilecek trend animeleri yönet
|
||
@if($lastComputed)
|
||
<span class="badge bg-secondary ms-2">Son hesap: {{ $lastComputed }}</span>
|
||
@else
|
||
<span class="badge bg-danger ms-2">Skor henüz hesaplanmadı</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<button class="btn btn-warning" id="btn-compute-scores" onclick="computeScores()">
|
||
<i class="bi bi-lightning-fill me-1"></i>
|
||
Trend Skorlarını Hesapla
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="compute-result" style="display:none" class="alert alert-success mb-3"></div>
|
||
|
||
<div class="row g-4">
|
||
|
||
{{-- ── Sol: Manuel Trend ── --}}
|
||
<div class="col-lg-6">
|
||
<div class="card h-100">
|
||
<div class="card-header">
|
||
<i class="bi bi-fire" style="color:var(--warning)"></i>
|
||
<h6>Manuel Trend Listesi</h6>
|
||
<span style="font-size:12px;color:var(--text2)">{{ $manual->count() }} anime</span>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
@if($manual->isEmpty())
|
||
<div class="empty-state">
|
||
<i class="bi bi-fire"></i>
|
||
<h6>Manuel trend seçilmedi</h6>
|
||
<p>Sağdan anime ekleyin veya otomatik trend kullanılır.</p>
|
||
</div>
|
||
@else
|
||
@foreach($manual as $anime)
|
||
<div class="trend-item">
|
||
<div class="trend-rank">{{ $anime->trending_order }}</div>
|
||
@if($anime->coverUrl)
|
||
<img src="{{ $anime->coverUrl }}" class="trend-cover" alt="">
|
||
@else
|
||
<div class="trend-cover-ph"><i class="bi bi-image"></i></div>
|
||
@endif
|
||
<div style="flex:1;min-width:0">
|
||
<div style="font-size:13.5px;font-weight:700;color:var(--text)" class="text-truncate">{{ $anime->title }}</div>
|
||
<div style="font-size:12px;color:var(--text2)">{{ $anime->release_year }} · {{ strtoupper($anime->type) }}</div>
|
||
</div>
|
||
<div class="d-flex gap-1">
|
||
@if(!$loop->first)
|
||
<form method="POST" action="{{ route('admin.trending.move',$anime) }}">
|
||
@csrf
|
||
<input type="hidden" name="direction" value="up">
|
||
<button type="submit" class="btn btn-sm btn-secondary" title="Yukarı">
|
||
<i class="bi bi-chevron-up"></i>
|
||
</button>
|
||
</form>
|
||
@endif
|
||
@if(!$loop->last)
|
||
<form method="POST" action="{{ route('admin.trending.move',$anime) }}">
|
||
@csrf
|
||
<input type="hidden" name="direction" value="down">
|
||
<button type="submit" class="btn btn-sm btn-secondary" title="Aşağı">
|
||
<i class="bi bi-chevron-down"></i>
|
||
</button>
|
||
</form>
|
||
@endif
|
||
<form method="POST" action="{{ route('admin.trending.toggle',$anime) }}">
|
||
@csrf
|
||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Çıkar">
|
||
<i class="bi bi-x-lg"></i>
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ── Sağ: Otomatik + Ekle ── --}}
|
||
<div class="col-lg-6">
|
||
|
||
{{-- Arama ve Ekle --}}
|
||
<div class="card mb-3">
|
||
<div class="card-header">
|
||
<i class="bi bi-search" style="color:var(--info)"></i>
|
||
<h6>Anime Ekle</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="search-input-wrap mb-2">
|
||
<i class="bi bi-search"></i>
|
||
<input type="text" id="trending-search" class="form-control"
|
||
placeholder="Anime adı yazın..." autocomplete="off">
|
||
</div>
|
||
<div id="search-results" style="display:none"></div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Otomatik Trend --}}
|
||
<div class="card mb-3">
|
||
<div class="card-header">
|
||
<i class="bi bi-graph-up-arrow" style="color:var(--info)"></i>
|
||
<h6>Otomatik Trend</h6>
|
||
<span style="font-size:12px;color:var(--text2)">YouTube-benzeri trending skoru</span>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
@foreach($autoTrending as $anime)
|
||
<div class="trend-item">
|
||
@if($anime->coverUrl)
|
||
<img src="{{ $anime->coverUrl }}" class="trend-cover" alt="">
|
||
@else
|
||
<div class="trend-cover-ph"><i class="bi bi-image"></i></div>
|
||
@endif
|
||
<div style="flex:1;min-width:0">
|
||
<div style="font-size:13px;font-weight:700;color:var(--text)" class="text-truncate">{{ $anime->title }}</div>
|
||
<div style="font-size:12px;color:var(--text2)">
|
||
@if($anime->trending_score > 0)
|
||
<i class="bi bi-lightning-fill" style="color:var(--warning);font-size:10px"></i>
|
||
{{ number_format($anime->trending_score, 1) }} puan
|
||
@else
|
||
{{ number_format($anime->recent_views ?? 0) }} izlenme
|
||
@endif
|
||
</div>
|
||
</div>
|
||
@if($anime->is_trending)
|
||
<span class="badge" style="background:var(--warning-dim);color:var(--warning);border:1px solid rgba(245,158,11,.3)">
|
||
<i class="bi bi-fire"></i> Trend
|
||
</span>
|
||
@else
|
||
<form method="POST" action="{{ route('admin.trending.toggle',$anime) }}">
|
||
@csrf
|
||
<button type="submit" class="btn btn-sm btn-outline-warning">
|
||
<i class="bi bi-plus-lg"></i> Ekle
|
||
</button>
|
||
</form>
|
||
@endif
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Açıklama --}}
|
||
<div class="card">
|
||
<div class="card-body" style="padding:16px">
|
||
<div style="font-size:13px;font-weight:700;color:var(--text);margin-bottom:8px;display:flex;align-items:center;gap:7px">
|
||
<i class="bi bi-info-circle" style="color:var(--info)"></i> Nasıl Çalışır?
|
||
</div>
|
||
<ul style="font-size:12.5px;color:var(--text2);padding-left:16px;line-height:2;margin:0">
|
||
<li>Manuel liste boşsa → otomatik trend (izlenme) gösterilir</li>
|
||
<li>Manuel liste doluysa → seçtiğiniz animeler sırayla görünür</li>
|
||
<li>Sıralama yukarı/aşağı butonlarıyla değiştirilir</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
@endsection
|
||
|
||
@push('scripts')
|
||
<script>
|
||
let searchTimer = null;
|
||
const CSRF = document.querySelector('meta[name=csrf-token]')?.content || '';
|
||
|
||
document.getElementById('trending-search').addEventListener('input', function() {
|
||
clearTimeout(searchTimer);
|
||
const q = this.value.trim();
|
||
const box = document.getElementById('search-results');
|
||
if (q.length < 2) { box.style.display='none'; return; }
|
||
|
||
searchTimer = setTimeout(async () => {
|
||
const r = await fetch(`/admin/trending/search?q=${encodeURIComponent(q)}`);
|
||
const d = await r.json();
|
||
box.style.display = 'block';
|
||
if (!d.results.length) {
|
||
box.innerHTML = '<div style="font-size:12.5px;color:var(--text2);padding:8px">Sonuç bulunamadı.</div>';
|
||
return;
|
||
}
|
||
box.innerHTML = d.results.map(a => `
|
||
<div class="search-result-item">
|
||
${a.cover ? `<img src="${a.cover}" style="width:30px;height:42px;object-fit:cover;border-radius:5px;flex-shrink:0">` : '<div style="width:30px;height:42px;background:var(--surface);border-radius:5px;flex-shrink:0"></div>'}
|
||
<div style="flex:1;min-width:0;font-size:13px;font-weight:600;color:var(--text)" class="text-truncate">${a.title}</div>
|
||
${a.is_trending
|
||
? '<span class="badge" style="background:var(--warning-dim);color:var(--warning)"><i class="bi bi-fire"></i> Trend</span>'
|
||
: `<form method="POST" action="/admin/trending/${a.id}/toggle" style="flex-shrink:0"><input type="hidden" name="_token" value="${CSRF}"><button class="btn btn-sm btn-outline-warning"><i class="bi bi-plus-lg"></i> Ekle</button></form>`
|
||
}
|
||
</div>
|
||
`).join('');
|
||
}, 350);
|
||
});
|
||
|
||
// ── Trend Skoru Hesapla ──────────────────────────────────────────────────────
|
||
async function computeScores() {
|
||
const btn = document.getElementById('btn-compute-scores');
|
||
const res = document.getElementById('compute-result');
|
||
btn.disabled = true;
|
||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Hesaplanıyor...';
|
||
res.style.display = 'none';
|
||
|
||
try {
|
||
const r = await fetch('{{ route("admin.trending.compute-scores") }}', {
|
||
method: 'POST',
|
||
headers: { 'X-CSRF-TOKEN': CSRF, 'Accept': 'application/json' },
|
||
});
|
||
const d = await r.json();
|
||
res.style.display = 'block';
|
||
res.className = 'alert alert-success mb-3';
|
||
res.innerHTML = `<i class="bi bi-check-circle-fill me-2"></i><strong>${d.message}</strong>
|
||
<br><small>Anasayfa cache temizlendi. Sayfa yenileniyor...</small>`;
|
||
btn.innerHTML = '<i class="bi bi-check me-1"></i>Tamamlandı!';
|
||
setTimeout(() => location.reload(), 2000);
|
||
} catch(e) {
|
||
res.style.display = 'block';
|
||
res.className = 'alert alert-danger mb-3';
|
||
res.textContent = 'Hata: ' + e.message;
|
||
btn.disabled = false;
|
||
btn.innerHTML = '<i class="bi bi-lightning-fill me-1"></i>Trend Skorlarını Hesapla';
|
||
}
|
||
}
|
||
</script>
|
||
@endpush
|