feat: implement intern blog management system with approval workflows and administrative feedback

This commit is contained in:
Ümit Tunç
2026-07-29 16:18:21 +03:00
parent 7c04433f6d
commit aa003df568
8 changed files with 695 additions and 36 deletions
@@ -377,6 +377,23 @@
<span class="block text-xs text-slate-400 font-semibold mt-0.5">Günlük çalışma defteri</span>
</div>
</button>
<!-- Menu Item 4: Blog Yazıları -->
@php
$approvedBlogsCount = isset($blogs) ? $blogs->where('status', 'published')->count() : 0;
@endphp
<button type="button" role="tab" aria-selected="false" data-tab-target="blogs" class="tab-btn w-full flex items-center gap-4 p-3 rounded-2xl text-left transition-all border border-transparent">
<div class="w-10 h-10 rounded-xl bg-slate-50 text-slate-500 flex items-center justify-center text-xl flex-shrink-0 tab-icon">
<i class="uil uil-newspaper"></i>
</div>
<div class="flex-grow">
<div class="flex items-center justify-between">
<span class="block text-sm font-extrabold text-slate-800">Blog Yazıları</span>
<span class="text-[10px] font-extrabold px-2 py-0.5 rounded-full @if($approvedBlogsCount >= 3) bg-green-100 text-green-700 @else bg-blue-100 text-blue-700 @endif">{{ $approvedBlogsCount }}/3</span>
</div>
<span class="block text-xs text-slate-400 font-semibold mt-0.5">Sitede yayınlanacak bloglar</span>
</div>
</button>
</div>
</div>
</div>
@@ -785,6 +802,222 @@
</div>
</div>
<!-- Panel 4: Blog Yazıları -->
<div id="blogs-panel" class="tab-panel hidden space-y-6">
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 pb-6 border-b border-slate-100">
<div>
<h3 class="font-bold text-slate-800 text-lg flex items-center gap-2">
<i class="uil uil-newspaper text-blue-600"></i>
<span>Staj Blog Yazıları (Min. 3 Adet)</span>
</h3>
<p class="text-xs text-slate-400 mt-1">
Staj süresince en az 3 blog yazısı kaleme almalısınız. Onaylanan yazılar web sitemizin blog bölümünde yayınlanacaktır.
</p>
</div>
<button type="button" onclick="openBlogModal()" class="px-4 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs transition-all shadow-md shadow-blue-500/10 flex items-center gap-2">
<i class="uil uil-plus text-base"></i>
<span>Yeni Blog Yazısı Ekle</span>
</button>
</div>
<!-- Progress Bar -->
@php
$userBlogs = $blogs ?? collect();
$publishedBlogs = $userBlogs->where('status', 'published');
$publishedCount = $publishedBlogs->count();
$progressPercent = min(100, round(($publishedCount / 3) * 100));
@endphp
<div class="mt-6 p-5 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl border border-blue-100">
<div class="flex justify-between items-center mb-2">
<span class="text-xs font-extrabold text-blue-900">Blog Görev Tamamlama Oranı</span>
<span class="text-xs font-black text-blue-700">{{ $publishedCount }} / 3 Yayınlandı (%{{ $progressPercent }})</span>
</div>
<div class="w-full bg-blue-200/60 rounded-full h-2.5 overflow-hidden">
<div class="bg-blue-600 h-2.5 rounded-full transition-all duration-500" style="width: {{ $progressPercent }}%"></div>
</div>
</div>
<!-- 3 Mandatory Category Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
@php
$expBlog = $userBlogs->where('intern_category', 'experience')->first();
$techBlog = $userBlogs->where('intern_category', 'technical_challenge')->first();
$prodBlog = $userBlogs->where('intern_category', 'product_showcase')->first();
@endphp
<!-- Category 1 -->
<div class="p-4 rounded-2xl border border-slate-100 bg-slate-50/50 flex flex-col justify-between">
<div>
<div class="flex justify-between items-center mb-2">
<span class="text-[10px] uppercase font-extrabold tracking-wider text-slate-400">1. Konu</span>
@if($expBlog && $expBlog->status === 'published')
<span class="px-2 py-0.5 bg-green-50 text-green-700 border border-green-200 text-[10px] font-bold rounded-lg">Yayınlandı ✓</span>
@elseif($expBlog && $expBlog->status === 'pending')
<span class="px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200 text-[10px] font-bold rounded-lg">Onay Bekliyor</span>
@elseif($expBlog && $expBlog->status === 'rejected')
<span class="px-2 py-0.5 bg-red-50 text-red-700 border border-red-200 text-[10px] font-bold rounded-lg">Revize İstendi</span>
@elseif($expBlog && $expBlog->status === 'draft')
<span class="px-2 py-0.5 bg-slate-100 text-slate-600 border border-slate-200 text-[10px] font-bold rounded-lg">Taslak</span>
@else
<span class="px-2 py-0.5 bg-slate-100 text-slate-400 border border-slate-200 text-[10px] font-bold rounded-lg">Bekliyor</span>
@endif
</div>
<h4 class="text-xs font-extrabold text-slate-800">Staj Tecrübesi & Adaptasyon</h4>
<p class="text-[11px] text-slate-400 mt-1">Staj süreci, şirket kültürü ve ilk izlenimlerinizi anlatan yazı.</p>
</div>
</div>
<!-- Category 2 -->
<div class="p-4 rounded-2xl border border-slate-100 bg-slate-50/50 flex flex-col justify-between">
<div>
<div class="flex justify-between items-center mb-2">
<span class="text-[10px] uppercase font-extrabold tracking-wider text-slate-400">2. Konu</span>
@if($techBlog && $techBlog->status === 'published')
<span class="px-2 py-0.5 bg-green-50 text-green-700 border border-green-200 text-[10px] font-bold rounded-lg">Yayınlandı ✓</span>
@elseif($techBlog && $techBlog->status === 'pending')
<span class="px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200 text-[10px] font-bold rounded-lg">Onay Bekliyor</span>
@elseif($techBlog && $techBlog->status === 'rejected')
<span class="px-2 py-0.5 bg-red-50 text-red-700 border border-red-200 text-[10px] font-bold rounded-lg">Revize İstendi</span>
@elseif($techBlog && $techBlog->status === 'draft')
<span class="px-2 py-0.5 bg-slate-100 text-slate-600 border border-slate-200 text-[10px] font-bold rounded-lg">Taslak</span>
@else
<span class="px-2 py-0.5 bg-slate-100 text-slate-400 border border-slate-200 text-[10px] font-bold rounded-lg">Bekliyor</span>
@endif
</div>
<h4 class="text-xs font-extrabold text-slate-800">Teknik Zorluklar & Çözümler</h4>
<p class="text-[11px] text-slate-400 mt-1">Gelişim sürecinde karşılaştığınız teknik engeller ve çözüm yöntemleri.</p>
</div>
</div>
<!-- Category 3 -->
<div class="p-4 rounded-2xl border border-slate-100 bg-slate-50/50 flex flex-col justify-between">
<div>
<div class="flex justify-between items-center mb-2">
<span class="text-[10px] uppercase font-extrabold tracking-wider text-slate-400">3. Konu</span>
@if($prodBlog && $prodBlog->status === 'published')
<span class="px-2 py-0.5 bg-green-50 text-green-700 border border-green-200 text-[10px] font-bold rounded-lg">Yayınlandı ✓</span>
@elseif($prodBlog && $prodBlog->status === 'pending')
<span class="px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200 text-[10px] font-bold rounded-lg">Onay Bekliyor</span>
@elseif($prodBlog && $prodBlog->status === 'rejected')
<span class="px-2 py-0.5 bg-red-50 text-red-700 border border-red-200 text-[10px] font-bold rounded-lg">Revize İstendi</span>
@elseif($prodBlog && $prodBlog->status === 'draft')
<span class="px-2 py-0.5 bg-slate-100 text-slate-600 border border-slate-200 text-[10px] font-bold rounded-lg">Taslak</span>
@else
<span class="px-2 py-0.5 bg-slate-100 text-slate-400 border border-slate-200 text-[10px] font-bold rounded-lg">Bekliyor</span>
@endif
</div>
<h4 class="text-xs font-extrabold text-slate-800">Ürün / Proje Tanıtımı (Showcase)</h4>
<p class="text-[11px] text-slate-400 mt-1">Geliştirdiğiniz nihai ürünün amacı, mimarisi ve canlı demo anlatımı.</p>
</div>
</div>
</div>
<!-- Blog List Table -->
<div class="mt-8">
<h4 class="text-sm font-extrabold text-slate-800 mb-4">Gönderilen Blog Yazılarınız</h4>
@if($userBlogs->isEmpty())
<div class="text-center py-10 border-2 border-dashed border-slate-100 rounded-2xl">
<i class="uil uil-file-edit-alt text-4xl text-slate-300"></i>
<p class="text-xs text-slate-500 font-bold mt-2">Henüz bir blog yazısı eklemediniz.</p>
<button type="button" onclick="openBlogModal()" class="mt-3 text-xs font-extrabold text-blue-600 hover:text-blue-800">
+ İlk Blog Yazısını Ekle
</button>
</div>
@else
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-slate-100 text-[11px] font-extrabold text-slate-400 uppercase tracking-wider">
<th class="py-3 px-3">Görsel</th>
<th class="py-3 px-3">Başlık & Konu</th>
<th class="py-3 px-3">Durum</th>
<th class="py-3 px-3">Yönetici Notu</th>
<th class="py-3 px-3 text-right">İşlemler</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 text-xs">
@foreach($userBlogs as $blogItem)
<tr>
<td class="py-3 px-3">
@if($blogItem->featured_image)
<img src="{{ asset('storage/' . $blogItem->featured_image) }}" class="w-12 h-12 object-cover rounded-xl border border-slate-100" />
@else
<div class="w-12 h-12 rounded-xl bg-slate-100 flex items-center justify-center text-slate-400">
<i class="uil uil-image text-xl"></i>
</div>
@endif
</td>
<td class="py-3 px-3">
<span class="font-bold text-slate-800 block text-sm">{{ $blogItem->title }}</span>
<span class="text-[10px] font-bold text-slate-400 block mt-0.5">
@if($blogItem->intern_category === 'experience') 1. Staj Tecrübesi
@elseif($blogItem->intern_category === 'technical_challenge') 2. Teknik Zorluklar
@elseif($blogItem->intern_category === 'product_showcase') 3. Ürün Tanıtımı
@else {{ $blogItem->intern_category }} @endif
</span>
</td>
<td class="py-3 px-3">
@if($blogItem->status === 'published')
<span class="px-2.5 py-1 bg-green-50 text-green-700 border border-green-200 font-extrabold rounded-full text-[11px] inline-flex items-center gap-1">
<i class="uil uil-check-circle text-sm"></i> Sitede Yayınlandı
</span>
@elseif($blogItem->status === 'pending')
<span class="px-2.5 py-1 bg-amber-50 text-amber-700 border border-amber-200 font-extrabold rounded-full text-[11px] inline-flex items-center gap-1">
<i class="uil uil-clock text-sm"></i> Onay Bekliyor
</span>
@elseif($blogItem->status === 'rejected')
<span class="px-2.5 py-1 bg-red-50 text-red-700 border border-red-200 font-extrabold rounded-full text-[11px] inline-flex items-center gap-1">
<i class="uil uil-exclamation-triangle text-sm"></i> Revize İstendi
</span>
@else
<span class="px-2.5 py-1 bg-slate-100 text-slate-600 border border-slate-200 font-extrabold rounded-full text-[11px] inline-flex items-center gap-1">
<i class="uil uil-edit text-sm"></i> Taslak
</span>
@endif
</td>
<td class="py-3 px-3">
@if($blogItem->admin_feedback)
<div class="p-2 bg-red-50 border border-red-100 rounded-xl text-red-700 text-[11px] font-semibold max-w-xs">
<strong>Geribildirim:</strong> {{ $blogItem->admin_feedback }}
</div>
@else
<span class="text-slate-300">-</span>
@endif
</td>
<td class="py-3 px-3 text-right">
<div class="flex items-center justify-end gap-2">
@if($blogItem->status === 'published')
<a href="{{ route('blog.show', $blogItem->slug) }}" target="_blank" class="px-3 py-1.5 bg-green-50 text-green-700 hover:bg-green-100 font-bold rounded-lg text-xs transition-all flex items-center gap-1">
<i class="uil uil-external-link-alt"></i> Sitede Gör
</a>
@else
<button type="button" onclick='editBlogItem({{ json_encode($blogItem) }})' class="px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-100 font-bold rounded-lg text-xs transition-all flex items-center gap-1">
<i class="uil uil-pen"></i> Düzenle
</button>
<form action="{{ route('intern.blog.delete', $blogItem->id) }}" method="POST" onsubmit="return confirm('Bu blog yazısını silmek istediğinize emin misiniz?');">
@csrf
@method('DELETE')
<button type="submit" class="px-2.5 py-1.5 bg-slate-100 text-red-600 hover:bg-red-50 font-bold rounded-lg text-xs transition-all">
<i class="uil uil-trash-alt"></i>
</button>
</form>
@endif
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</div>
</div>
</div>
</div>
@@ -1563,6 +1796,89 @@
}, 2000);
});
}
let blogQuill = null;
document.addEventListener('DOMContentLoaded', function () {
if (document.getElementById('blog-quill-editor')) {
blogQuill = new Quill('#blog-quill-editor', {
theme: 'snow',
placeholder: 'Blog yazınızı buraya detaylı bir şekilde yazın...',
modules: {
toolbar: [
[{ 'header': [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
['link', 'clean']
]
}
});
}
});
function openBlogModal() {
document.getElementById('modal_blog_id').value = '';
document.getElementById('modal_blog_title').value = '';
document.getElementById('modal_intern_category').value = 'experience';
document.getElementById('modal_blog_excerpt').value = '';
if (blogQuill) blogQuill.setContents([]);
document.getElementById('blog-modal-title').querySelector('span').textContent = 'Yeni Blog Yazısı Ekle';
const modal = document.getElementById('blog-modal');
const card = document.getElementById('blog-modal-card');
modal.classList.remove('hidden');
setTimeout(() => {
card.classList.remove('scale-95', 'opacity-0');
card.classList.add('scale-100', 'opacity-100');
}, 10);
}
function editBlogItem(blogItem) {
document.getElementById('modal_blog_id').value = blogItem.id;
document.getElementById('modal_blog_title').value = blogItem.title;
document.getElementById('modal_intern_category').value = blogItem.intern_category || 'experience';
document.getElementById('modal_blog_excerpt').value = blogItem.excerpt || '';
if (blogQuill && blogItem.content) {
blogQuill.setContents([]);
blogQuill.clipboard.dangerouslyPasteHTML(blogItem.content);
}
document.getElementById('blog-modal-title').querySelector('span').textContent = 'Blog Yazısını Düzenle';
const modal = document.getElementById('blog-modal');
const card = document.getElementById('blog-modal-card');
modal.classList.remove('hidden');
setTimeout(() => {
card.classList.remove('scale-95', 'opacity-0');
card.classList.add('scale-100', 'opacity-100');
}, 10);
}
function closeBlogModal() {
const modal = document.getElementById('blog-modal');
const card = document.getElementById('blog-modal-card');
card.classList.remove('scale-100', 'opacity-100');
card.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modal.classList.add('hidden');
}, 300);
}
function submitBlogForm(actionType) {
const title = document.getElementById('modal_blog_title').value.trim();
if (!title) {
alert('Lütfen blog başlığını giriniz.');
return;
}
const htmlContent = blogQuill ? blogQuill.root.innerHTML.trim() : '';
if (!htmlContent || htmlContent === '<p><br></p>') {
alert('Lütfen blog içeriğini giriniz.');
return;
}
document.getElementById('modal_action_type').value = actionType;
document.getElementById('modal_blog_content').value = htmlContent;
document.getElementById('blog-form').submit();
}
</script>
<style>
.shadow-xl {
@@ -1592,5 +1908,75 @@
</style>
@endpush
<!-- Blog Creation/Edit Modal -->
<div id="blog-modal" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-3xl p-6 md:p-8 max-w-3xl w-full shadow-2xl border border-slate-100/50 transform scale-95 opacity-0 transition-all duration-300 max-h-[90vh] overflow-y-auto" id="blog-modal-card">
<div class="flex justify-between items-start mb-6">
<div>
<h3 class="text-xl font-extrabold text-slate-800 flex items-center gap-2" id="blog-modal-title">
<i class="uil uil-pen text-blue-600"></i>
<span>Yeni Blog Yazısı Ekle</span>
</h3>
<p class="text-xs text-slate-400 mt-1">Yazınız yöneticiniz tarafından onaylandıktan sonra web sitesinde yayınlanacaktır.</p>
</div>
<button type="button" onclick="closeBlogModal()" class="text-slate-400 hover:text-slate-600">
<i class="uil uil-multiply text-2xl"></i>
</button>
</div>
<form id="blog-form" action="{{ route('intern.blog.save') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="hidden" name="blog_id" id="modal_blog_id" value="" />
<input type="hidden" name="action_type" id="modal_action_type" value="submit" />
<input type="hidden" name="content" id="modal_blog_content" value="" />
<div class="space-y-5">
<div>
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Blog Başlığı *</label>
<input type="text" name="title" id="modal_blog_title" required class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 text-sm font-bold text-slate-800" placeholder="Örn: Uzaktan Staj Deneyimim ve Karşılaştığım Zorluklar" />
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Staj Konu Kategorisi *</label>
<select name="intern_category" id="modal_intern_category" required class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 text-sm font-bold text-slate-800">
<option value="experience">1. Staj Tecrübesi & Adaptasyon</option>
<option value="technical_challenge">2. Teknik Zorluklar & Çözümler</option>
<option value="product_showcase">3. Ürün / Proje Tanıtımı (Showcase)</option>
</select>
</div>
<div>
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Kapak Görseli</label>
<input type="file" name="featured_image" id="modal_featured_image" accept="image/*" class="w-full text-xs text-slate-500 file:mr-4 file:py-2.5 file:px-4 file:rounded-xl file:border-0 file:text-xs file:font-bold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100" />
</div>
</div>
<div>
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Kısa Özet (Excerpt)</label>
<textarea name="excerpt" id="modal_blog_excerpt" rows="2" class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 text-xs font-medium text-slate-700" placeholder="Yazınızın liste sayfalarında görünecek kısa özeti..."></textarea>
</div>
<div>
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Blog İçeriği *</label>
<div id="blog-quill-editor-container" class="rounded-xl border border-slate-200 overflow-hidden">
<div id="blog-quill-editor" style="height: 250px;"></div>
</div>
</div>
</div>
<div class="flex flex-col sm:flex-row justify-end gap-3 mt-8 pt-4 border-t border-slate-100">
<button type="button" onclick="submitBlogForm('draft')" class="px-5 py-3 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-700 font-extrabold text-xs transition-all">
Taslak Olarak Kaydet
</button>
<button type="button" onclick="submitBlogForm('submit')" class="px-6 py-3 rounded-xl bg-blue-600 hover:bg-blue-700 text-white font-extrabold text-xs transition-all shadow-md shadow-blue-500/10 flex items-center justify-center gap-2">
<i class="uil uil-arrow-right text-base"></i>
<span>Onaya Gönder</span>
</button>
</div>
</form>
</div>
</div>
@include('front.career.partials.guide_modal')
@endsection