Files
animexe/resources/views/admin/animes/edit.blade.php
T
2026-07-14 00:01:48 +03:00

370 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@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 (010)</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