370 lines
21 KiB
PHP
370 lines
21 KiB
PHP
@extends('admin.layouts.app')
|
||
@section('title', 'Düzenle: ' . $anime->title)
|
||
@section('page-title', $anime->title)
|
||
|
||
@push('styles')
|
||
<style>
|
||
.section-card { background:var(--bg2); border:1px solid var(--border); border-radius:var(--radius); margin-bottom:16px; overflow:hidden; }
|
||
.section-header { display:flex; align-items:center; gap:10px; padding:14px 20px; border-bottom:1px solid var(--border); }
|
||
.section-header h6 { font-size:13.5px; font-weight:700; color:var(--text); margin:0; }
|
||
.section-body { padding:20px; }
|
||
.ai-banner {
|
||
display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px;
|
||
padding:14px 18px;
|
||
background:linear-gradient(135deg,rgba(6,182,212,0.06),rgba(124,58,237,0.06));
|
||
border:1px solid rgba(6,182,212,0.2);
|
||
border-radius:var(--radius-sm);
|
||
margin-bottom:16px;
|
||
}
|
||
.genre-chip { display:inline-flex; align-items:center; gap:6px; padding:5px 12px; border-radius:99px; border:1px solid var(--border2); background:transparent; color:var(--text2); font-size:12.5px; font-weight:600; cursor:pointer; transition:all .15s; user-select:none; }
|
||
.genre-chip input { display:none; }
|
||
.genre-chip:has(input:checked) { background:var(--accent-dim); border-color:var(--accent); color:var(--accent-lt); }
|
||
</style>
|
||
@endpush
|
||
|
||
@section('content')
|
||
|
||
<div class="d-flex align-items-center gap-3 mb-4 flex-wrap">
|
||
<a href="{{ route('admin.animes.show', $anime) }}" class="btn btn-secondary btn-sm">
|
||
<i class="bi bi-arrow-left"></i> Geri Dön
|
||
</a>
|
||
<div style="font-size:13px;color:var(--text2)">
|
||
Son güncelleme: {{ $anime->updated_at->format('d.m.Y H:i') }}
|
||
</div>
|
||
</div>
|
||
|
||
{{-- AI Banner --}}
|
||
<div class="ai-banner">
|
||
<div>
|
||
<div style="font-size:13.5px;font-weight:700;color:var(--info);display:flex;align-items:center;gap:7px">
|
||
<i class="bi bi-stars"></i> AI ile Tüm Boşları Doldur
|
||
</div>
|
||
<div style="font-size:12px;color:var(--text2);margin-top:2px">
|
||
Açıklama, yıl, stüdyo, tür, durum ve puan bilgilerini DeepSeek'ten çeker. Yalnızca boş alanları doldurur.
|
||
</div>
|
||
</div>
|
||
<button type="button" class="btn btn-info btn-sm" id="ai-meta-btn" onclick="aiAutoFill()">
|
||
<i class="bi bi-magic"></i> Tümünü Doldur
|
||
</button>
|
||
</div>
|
||
<div id="ai-meta-status" style="display:none;font-size:13px;padding:8px 12px;border-radius:8px;margin-bottom:12px"></div>
|
||
|
||
<form method="POST" action="{{ route('admin.animes.update', $anime) }}" enctype="multipart/form-data">
|
||
@csrf @method('PUT')
|
||
|
||
<div class="row g-4">
|
||
<div class="col-xl-8 col-lg-7">
|
||
|
||
{{-- Temel Bilgiler --}}
|
||
<div class="section-card">
|
||
<div class="section-header">
|
||
<i class="bi bi-info-circle" style="color:var(--accent-lt)"></i>
|
||
<h6>Temel Bilgiler</h6>
|
||
</div>
|
||
<div class="section-body">
|
||
<div class="mb-4">
|
||
<label class="form-label">Türkçe Başlık <span style="color:var(--danger)">*</span></label>
|
||
<input type="text" name="title" class="form-control" value="{{ old('title', $anime->title) }}" required style="font-size:15px;font-weight:600">
|
||
</div>
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-md-6">
|
||
<label class="form-label">İngilizce Başlık</label>
|
||
<input type="text" name="title_en" class="form-control" value="{{ old('title_en', $anime->title_en) }}">
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label">Japonca Başlık</label>
|
||
<input type="text" name="title_jp" class="form-control" value="{{ old('title_jp', $anime->title_jp) }}">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
<label class="form-label mb-0">Açıklama</label>
|
||
<button type="button" class="btn btn-sm btn-secondary" id="ai-anime-btn" onclick="aiGenerateAnime()">
|
||
<i class="bi bi-stars" style="color:var(--info)"></i> AI ile Yaz
|
||
</button>
|
||
</div>
|
||
<textarea name="description" id="anime-description" class="form-control" rows="5">{{ old('description', $anime->description) }}</textarea>
|
||
<div id="ai-anime-status" class="form-text" style="display:none"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Detaylar --}}
|
||
<div class="section-card">
|
||
<div class="section-header">
|
||
<i class="bi bi-sliders" style="color:var(--info)"></i>
|
||
<h6>Detaylar</h6>
|
||
</div>
|
||
<div class="section-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-4">
|
||
<label class="form-label">Tip</label>
|
||
<select name="type" class="form-select">
|
||
@foreach(['series'=>'Dizi','movie'=>'Film','ova'=>'OVA','ona'=>'ONA','special'=>'Special'] as $v => $l)
|
||
<option value="{{ $v }}" {{ old('type',$anime->type)===$v?'selected':'' }}>{{ $l }}</option>
|
||
@endforeach
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Durum</label>
|
||
<select name="status" class="form-select">
|
||
<option value="ongoing" {{ old('status',$anime->status)==='ongoing' ?'selected':'' }}>Devam Ediyor</option>
|
||
<option value="completed" {{ old('status',$anime->status)==='completed' ?'selected':'' }}>Tamamlandı</option>
|
||
<option value="upcoming" {{ old('status',$anime->status)==='upcoming' ?'selected':'' }}>Yakında</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Yayın Yılı</label>
|
||
<input type="number" name="release_year" class="form-control" value="{{ old('release_year',$anime->release_year) }}" min="1900" max="2099">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Stüdyo</label>
|
||
<input type="text" name="studio" class="form-control" value="{{ old('studio',$anime->studio) }}" placeholder="MAPPA, WIT Studio...">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Puan (0–10)</label>
|
||
<input type="number" name="rating" class="form-control" step="0.1" min="0" max="10" value="{{ old('rating',$anime->rating) }}">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">
|
||
MAL ID
|
||
@foreach($anime->seasons as $s)
|
||
@if($s->mal_id)<span class="badge bg-success ms-1" style="font-size:10px">S{{ $s->season_number }}:{{ $s->mal_id }}</span>@endif
|
||
@endforeach
|
||
</label>
|
||
<div class="input-group">
|
||
<input type="text" name="mal_id" id="mal_id_input" class="form-control"
|
||
value="{{ old('mal_id',$anime->mal_id) }}" placeholder="örn: 16498">
|
||
<button type="button" class="btn btn-secondary btn-sm" id="btn-fetch-mal"
|
||
onclick="fetchMalSeasons()" title="MAL ID kaydet + sezon ID'lerini Jikan'dan çek">
|
||
<i class="bi bi-magic"></i>
|
||
</button>
|
||
</div>
|
||
<div id="mal-fetch-result" class="form-text">
|
||
@if(!$anime->mal_id)
|
||
<span style="color:var(--warning)">⚠ MAL ID girilmemiş — İntro Atla çalışmaz</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label">Fragman URL</label>
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-youtube" style="color:#ff0000"></i></span>
|
||
<input type="url" name="trailer_url" class="form-control" value="{{ old('trailer_url',$anime->trailer_url) }}" placeholder="https://youtube.com/...">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Türler --}}
|
||
<div class="section-card">
|
||
<div class="section-header">
|
||
<i class="bi bi-tags" style="color:var(--warning)"></i>
|
||
<h6>Türler</h6>
|
||
</div>
|
||
<div class="section-body">
|
||
<div class="d-flex flex-wrap gap-2">
|
||
@foreach($genres as $genre)
|
||
<label class="genre-chip">
|
||
<input type="checkbox" name="genres[]" value="{{ $genre->id }}"
|
||
{{ $anime->genres->contains($genre->id) ? 'checked' : '' }}>
|
||
{{ $genre->name }}
|
||
</label>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- İzinler --}}
|
||
@if(isset($permissions) && count($permissions))
|
||
<div class="section-card">
|
||
<div class="section-header">
|
||
<i class="bi bi-shield-lock" style="color:var(--success)"></i>
|
||
<h6>İzin Ayarları</h6>
|
||
<span style="font-size:12px;color:var(--text2)">— Global ayarları override eder</span>
|
||
</div>
|
||
<div class="section-body p-0">
|
||
@foreach($permissions as $perm)
|
||
@php $current = $contentPerms[$perm->key] ?? 'free'; @endphp
|
||
<div class="perm-row">
|
||
<div>
|
||
<div class="perm-name">{{ $perm->label }}</div>
|
||
@if($perm->description)<div class="perm-desc">{{ $perm->description }}</div>@endif
|
||
</div>
|
||
<div class="perm-toggle">
|
||
<input type="radio" name="permissions[{{ $perm->key }}]" id="ep_{{ $perm->key }}_free" value="free" {{ $current==='free'?'checked':'' }}>
|
||
<label for="ep_{{ $perm->key }}_free" class="free">Ücretsiz</label>
|
||
<input type="radio" name="permissions[{{ $perm->key }}]" id="ep_{{ $perm->key }}_prem" value="premium" {{ $current==='premium'?'checked':'' }}>
|
||
<label for="ep_{{ $perm->key }}_prem" class="premium">Premium</label>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
</div>
|
||
|
||
{{-- ── Sağ Kolon ── --}}
|
||
<div class="col-xl-4 col-lg-5">
|
||
|
||
{{-- Kapak --}}
|
||
<div class="section-card">
|
||
<div class="section-header">
|
||
<i class="bi bi-image" style="color:var(--info)"></i>
|
||
<h6>Kapak Görseli</h6>
|
||
</div>
|
||
<div class="section-body">
|
||
@if($anime->coverUrl)
|
||
<img src="{{ $anime->coverUrl }}" alt=""
|
||
style="width:100%;aspect-ratio:2/3;object-fit:cover;border-radius:var(--radius-sm);margin-bottom:12px">
|
||
@endif
|
||
<label style="font-size:12px;color:var(--text2);margin-bottom:6px;display:block">
|
||
{{ $anime->coverUrl ? 'Yeni görsel yükle (mevcut üzerine yazar)' : 'Kapak görseli yükle' }}
|
||
</label>
|
||
<input type="file" name="cover_image" class="form-control" accept="image/*">
|
||
<div class="form-text">Önerilen: 300×450px</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Banner --}}
|
||
<div class="section-card">
|
||
<div class="section-header">
|
||
<i class="bi bi-panorama" style="color:var(--info)"></i>
|
||
<h6>Banner Görseli</h6>
|
||
</div>
|
||
<div class="section-body">
|
||
<input type="file" name="banner_image" class="form-control" accept="image/*">
|
||
<div class="form-text">Önerilen: 1280×720px</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Yayın --}}
|
||
<div class="section-card">
|
||
<div class="section-header">
|
||
<i class="bi bi-toggle-on" style="color:var(--success)"></i>
|
||
<h6>Yayın Ayarları</h6>
|
||
</div>
|
||
<div class="section-body d-flex flex-column gap-3">
|
||
<div class="d-flex align-items-center justify-content-between">
|
||
<div>
|
||
<div style="font-size:13.5px;font-weight:600;color:var(--text)">Yayınla</div>
|
||
<div style="font-size:12px;color:var(--text2)">Kullanıcılara görünür</div>
|
||
</div>
|
||
<div class="form-check form-switch mb-0">
|
||
<input class="form-check-input" type="checkbox" name="is_published" value="1"
|
||
id="is_published" role="switch" {{ $anime->is_published ? 'checked' : '' }}>
|
||
</div>
|
||
</div>
|
||
<div class="d-flex align-items-center justify-content-between">
|
||
<div>
|
||
<div style="font-size:13.5px;font-weight:600;color:var(--text)">Öne Çıkar</div>
|
||
<div style="font-size:12px;color:var(--text2)">Ana sayfada öne çıkar</div>
|
||
</div>
|
||
<div class="form-check form-switch mb-0">
|
||
<input class="form-check-input" type="checkbox" name="is_featured" value="1"
|
||
id="is_featured" role="switch" {{ $anime->is_featured ? 'checked' : '' }}>
|
||
</div>
|
||
</div>
|
||
<div class="d-flex align-items-center justify-content-between">
|
||
<div>
|
||
<div style="font-size:13.5px;font-weight:600;color:var(--text)">Türkçe Dublaj</div>
|
||
<div style="font-size:12px;color:var(--text2)">Ana sayfada Dublaj bölümünde göster</div>
|
||
</div>
|
||
<div class="form-check form-switch mb-0">
|
||
<input class="form-check-input" type="checkbox" name="is_dubbed" value="1"
|
||
id="is_dubbed" role="switch" {{ $anime->is_dubbed ? 'checked' : '' }}>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary w-100 btn-lg">
|
||
<i class="bi bi-floppy"></i> Değişiklikleri Kaydet
|
||
</button>
|
||
<a href="{{ route('admin.animes.show', $anime) }}" class="btn btn-secondary w-100 mt-2">İptal</a>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
@endsection
|
||
|
||
@push('scripts')
|
||
<script>
|
||
const AI_META_URL = '{{ route("admin.ai.anime-meta") }}';
|
||
const AI_GEN_URL = '{{ route("admin.ai.generate") }}';
|
||
const CSRF_TOKEN = document.querySelector('meta[name=csrf-token]')?.content || '';
|
||
|
||
async function aiAutoFill() {
|
||
const btn = document.getElementById('ai-meta-btn');
|
||
const status = document.getElementById('ai-meta-status');
|
||
const title = document.querySelector('[name="title"]')?.value?.trim();
|
||
if (!title) { axToast('Önce anime başlığını girin.', 'warning'); return; }
|
||
|
||
btn.disabled = true;
|
||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1" style="width:12px;height:12px"></span> Sorgulanıyor...';
|
||
status.style.display = 'block';
|
||
status.style.background = 'var(--info-dim)';
|
||
status.style.color = 'var(--info)';
|
||
status.textContent = 'DeepSeek sorgulanıyor, lütfen bekleyin (~5-10 sn)...';
|
||
|
||
try {
|
||
const res = await fetch(AI_META_URL, { method:'POST', headers:{'Content-Type':'application/json','X-CSRF-TOKEN':CSRF_TOKEN}, body:JSON.stringify({ title, title_jp: document.querySelector('[name="title_jp"]')?.value?.trim()||'' }) });
|
||
const data = await res.json();
|
||
if (!res.ok || data.error) { status.style.background='var(--danger-dim)'; status.style.color='var(--danger)'; status.textContent='❌ '+(data.error||'Hata'); return; }
|
||
|
||
const m = data.meta; let filled = [];
|
||
const fillF = (sel, val) => { if (!val) return; const el = document.querySelector(sel); if (el && !el.value?.trim()) { el.value = val; filled.push(el.name||sel); } };
|
||
fillF('[name="description"]',m.description); fillF('[name="title_en"]',m.title_en); fillF('[name="title_jp"]',m.title_jp);
|
||
fillF('[name="studio"]',m.studio); fillF('[name="release_year"]',m.release_year); fillF('[name="rating"]',m.rating); fillF('[name="trailer_url"]',m.trailer_url);
|
||
const fillS = (sel, val) => { if (!val) return; const el = document.querySelector(sel); if (!el) return; const opt = [...el.options].find(o => o.value===val); if (opt) { el.value=val; filled.push(el.name); } };
|
||
fillS('[name="type"]',m.type); fillS('[name="status"]',m.status);
|
||
if (Array.isArray(m.genres) && m.genres.length) {
|
||
document.querySelectorAll('[name="genres[]"]').forEach(cb => {
|
||
const lbl = cb.closest('label')?.textContent?.trim()?.toLowerCase();
|
||
if (!lbl) return;
|
||
if (m.genres.some(g => lbl.includes(g.toLowerCase()) || g.toLowerCase().includes(lbl)) && !cb.checked) { cb.checked=true; filled.push('genre'); }
|
||
});
|
||
}
|
||
status.style.background='var(--success-dim)'; status.style.color='var(--success)';
|
||
status.textContent = `✓ ${filled.length} alan dolduruldu. Kontrol edip kaydedin.`;
|
||
} catch(e) { status.style.background='var(--danger-dim)'; status.style.color='var(--danger)'; status.textContent='❌ Bağlantı hatası: '+e.message; }
|
||
finally { btn.disabled=false; btn.innerHTML='<i class="bi bi-magic"></i> Tümünü Doldur'; }
|
||
}
|
||
|
||
async function aiGenerateAnime() {
|
||
const btn = document.getElementById('ai-anime-btn');
|
||
const status = document.getElementById('ai-anime-status');
|
||
const ta = document.getElementById('anime-description');
|
||
const title = document.querySelector('[name="title"]')?.value?.trim();
|
||
if (!title) { axToast('Önce başlık girin.', 'warning'); return; }
|
||
|
||
btn.disabled=true; btn.innerHTML='<span class="spinner-border spinner-border-sm" style="width:12px;height:12px"></span>';
|
||
status.style.display='block'; status.className='form-text'; status.style.color='var(--info)'; status.textContent='AI yazıyor...';
|
||
try {
|
||
const res = await fetch(AI_GEN_URL, { method:'POST', headers:{'Content-Type':'application/json','X-CSRF-TOKEN':CSRF_TOKEN}, body:JSON.stringify({ type:'anime', title, title_jp: document.querySelector('[name="title_jp"]')?.value?.trim(), genres:[...document.querySelectorAll('[name="genres[]"]:checked')].map(el=>el.closest('label')?.textContent?.trim()||el.value).join(', ') }) });
|
||
const data = await res.json();
|
||
if (data.error) { status.style.color='var(--danger)'; status.textContent='❌ '+data.error; }
|
||
else { ta.value=data.description; status.style.color='var(--success)'; status.textContent='✓ Açıklama oluşturuldu.'; }
|
||
} catch(e) { status.style.color='var(--danger)'; status.textContent='❌ Hata.'; }
|
||
finally { btn.disabled=false; btn.innerHTML='<i class="bi bi-stars" style="color:var(--info)"></i> AI ile Yaz'; }
|
||
}
|
||
|
||
async function fetchMalSeasons() {
|
||
const btn=document.getElementById('btn-fetch-mal'); const result=document.getElementById('mal-fetch-result');
|
||
const malId=document.getElementById('mal_id_input').value.trim();
|
||
if (!malId) { result.innerHTML='<span style="color:var(--warning)">⚠ MAL ID girin.</span>'; return; }
|
||
btn.disabled=true; btn.innerHTML='<span class="spinner-border spinner-border-sm" style="width:12px;height:12px"></span>';
|
||
result.innerHTML='<span style="color:var(--text2)">Jikan API\'ye bağlanılıyor...</span>';
|
||
try {
|
||
await fetch('{{ route("admin.animes.update", $anime) }}', { method:'POST', headers:{'X-CSRF-TOKEN':CSRF_TOKEN,'Accept':'application/json','Content-Type':'application/x-www-form-urlencoded'}, body:new URLSearchParams({_method:'PUT',mal_id:malId}) });
|
||
const res = await fetch('{{ route("admin.animes.fetch-mal-seasons", $anime) }}', { method:'POST', headers:{'X-CSRF-TOKEN':CSRF_TOKEN,'Accept':'application/json'} });
|
||
const data = await res.json();
|
||
if (data.success) { const rows=data.updated.map(u=>`S${u.season}→${u.mal_id}`).join(', '); result.innerHTML=`<span style="color:var(--success)">✓ ${data.message}${rows?' ('+rows+')':''}</span>`; }
|
||
else { result.innerHTML=`<span style="color:var(--danger)">❌ ${data.error}</span>`; }
|
||
} catch(e) { result.innerHTML='<span style="color:var(--danger)">❌ Hata.</span>'; }
|
||
finally { btn.disabled=false; btn.innerHTML='<i class="bi bi-magic"></i>'; }
|
||
}
|
||
</script>
|
||
@endpush
|