Files
citrus/resources/views/front/career/partials/quick_approval.blade.php
T

526 lines
22 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.
<!-- Quick Approval Management View -->
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50">
<!-- Header & Description -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-8 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-check-square text-[#e31e24] text-xl"></i>
<span>Hızlı Defter Onaylama</span>
</h3>
<p class="text-xs text-slate-400 font-semibold mt-1">Stajyerlerin günlük yazdığı raporları tek bir ekrandan inceleyin ve onaylayın.</p>
</div>
<!-- Filter Options -->
<div class="flex flex-wrap items-center gap-3">
<div class="flex bg-slate-50 p-1 rounded-xl border border-slate-200/50">
<button type="button" onclick="setQuickFilter('unapproved')" id="qf-btn-unapproved" class="px-4 py-2 text-xs font-bold rounded-lg transition-all cursor-pointer bg-white text-blue-600 shadow-sm border border-slate-200/20">
Onay Bekleyenler
</button>
<button type="button" onclick="setQuickFilter('today')" id="qf-btn-today" class="px-4 py-2 text-xs font-bold rounded-lg transition-all cursor-pointer text-slate-600 hover:text-slate-900">
Bugün Yazılanlar
</button>
</div>
<!-- Date Picker for custom date -->
<div id="qf-date-wrapper" class="hidden">
<input type="date" id="qf-date-input" onchange="loadQuickApprovalEntries()" value="{{ date('Y-m-d') }}" class="text-xs font-semibold text-slate-700 bg-white border border-slate-200 rounded-xl px-3 py-2 focus:outline-none focus:border-blue-400">
</div>
</div>
</div>
<!-- Loading State -->
<div id="qa-loading" class="flex flex-col items-center justify-center py-16">
<div class="animate-spin rounded-full h-10 w-10 border-b-2 border-blue-600 mb-3"></div>
<p class="text-xs font-semibold text-slate-400">Defter kayıtları yükleniyor...</p>
</div>
<!-- Empty State -->
<div id="qa-empty" class="hidden flex flex-col items-center justify-center py-16 text-center">
<div class="w-16 h-16 bg-emerald-50 rounded-2xl text-emerald-500 flex items-center justify-center text-3xl mb-4 border border-emerald-100">
<i class="uil uil-smile-beam"></i>
</div>
<h4 id="qa-empty-title" class="font-extrabold text-slate-800 text-sm">Harika! Onay Bekleyen Defter Yok</h4>
<p id="qa-empty-desc" class="text-xs text-slate-400 font-semibold mt-1">Şu anda onayınızı bekleyen hiçbir stajyer raporu bulunmuyor.</p>
</div>
<!-- Content List Grid -->
<div id="qa-list-grid" class="hidden grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Loaded dynamically -->
</div>
</div>
<!-- Dedicated Quick Approval Entry Reading Modal -->
<div id="quick-entry-modal" class="fixed inset-0 z-[9999] hidden items-center justify-center bg-slate-900/60 backdrop-blur-sm transition-opacity duration-300">
<div class="bg-white rounded-3xl shadow-2xl border border-slate-100 max-w-2xl w-full mx-4 overflow-hidden transform scale-95 opacity-0 transition-all duration-300 flex flex-col max-h-[85vh]" id="quick-entry-modal-card">
<!-- Header -->
<div class="px-6 py-4 bg-gradient-to-r from-blue-50 to-indigo-50/30 border-b border-slate-100 flex items-center justify-between flex-shrink-0">
<div>
<h4 id="qem-intern-name" class="font-bold text-slate-800 text-sm">Stajyer İsmi</h4>
<p id="qem-meta" class="text-xs text-slate-400 font-semibold mt-0.5"></p>
</div>
<button type="button" onclick="closeQuickEntryModal()" class="text-slate-400 hover:text-slate-600 transition-colors p-1.5 rounded-full hover:bg-slate-100/80">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<!-- Body -->
<div class="flex-grow p-6 overflow-y-auto no-scrollbar flex flex-col min-h-[250px]">
<div id="qem-retroactive-badge" class="mb-4">
<!-- Retroactive indicator -->
</div>
<div id="qem-content" class="prose max-w-none text-slate-700 leading-relaxed text-sm select-text flex-grow">
<!-- Full text rendered safely -->
</div>
</div>
<!-- Footer Controls -->
<div class="px-6 py-4 bg-slate-50 border-t border-slate-100 flex flex-col sm:flex-row items-center justify-between gap-4 flex-shrink-0">
<!-- Carousel navigation -->
<div class="flex items-center gap-2">
<button type="button" id="qem-btn-prev" onclick="prevQuickEntry()" class="px-3 py-1.5 bg-white hover:bg-slate-100 border border-slate-200 text-slate-600 rounded-xl text-xs font-bold transition-all flex items-center gap-1 cursor-pointer">
<i class="uil uil-angle-left-b text-sm"></i> Önceki
</button>
<span id="qem-indicator" class="text-xs font-extrabold text-slate-500 bg-slate-200/50 px-2.5 py-1.5 rounded-lg">0 / 0</span>
<button type="button" id="qem-btn-next" onclick="nextQuickEntry()" class="px-3 py-1.5 bg-white hover:bg-slate-100 border border-slate-200 text-slate-600 rounded-xl text-xs font-bold transition-all flex items-center gap-1 cursor-pointer">
Sonraki <i class="uil uil-angle-right-b text-sm"></i>
</button>
</div>
<!-- Action Button -->
<div class="flex items-center gap-3">
<button type="button" id="qem-action-btn" onclick="toggleQuickEntryApproval()" class="px-5 py-2.5 text-white rounded-xl font-bold text-xs shadow-lg transition-all cursor-pointer">
Onayla
</button>
<button type="button" onclick="closeQuickEntryModal()" class="px-4 py-2.5 bg-slate-800 hover:bg-slate-900 text-white font-bold rounded-xl text-xs transition-colors cursor-pointer">Kapat</button>
</div>
</div>
</div>
</div>
<!-- Safe DOM HTML Sanitizer script -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.1.6/purify.min.js" integrity="sha384-+VfUPEb0PdtChMwmBcBmykRMDd+v6D/oFmB3rZM/puCMDYcIvF968OimRh4KQY9a" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
let quickApprovalEntries = [];
let currentQuickFilter = 'unapproved';
let activeQuickEntryIndex = -1;
let isUpdatingQuickApproval = false;
function setQuickFilter(filter) {
currentQuickFilter = filter;
// Toggle active state classes on filter buttons
const btnUnapproved = document.getElementById('qf-btn-unapproved');
const btnToday = document.getElementById('qf-btn-today');
const dateWrapper = document.getElementById('qf-date-wrapper');
if (filter === 'unapproved') {
btnUnapproved.className = "px-4 py-2 text-xs font-bold rounded-lg transition-all cursor-pointer bg-white text-blue-600 shadow-sm border border-slate-200/20";
btnToday.className = "px-4 py-2 text-xs font-bold rounded-lg transition-all cursor-pointer text-slate-600 hover:text-slate-900";
dateWrapper.classList.add('hidden');
} else {
btnToday.className = "px-4 py-2 text-xs font-bold rounded-lg transition-all cursor-pointer bg-white text-blue-600 shadow-sm border border-slate-200/20";
btnUnapproved.className = "px-4 py-2 text-xs font-bold rounded-lg transition-all cursor-pointer text-slate-600 hover:text-slate-900";
dateWrapper.classList.remove('hidden');
}
loadQuickApprovalEntries();
}
function loadQuickApprovalEntries() {
const listGrid = document.getElementById('qa-list-grid');
const loadingEl = document.getElementById('qa-loading');
const emptyEl = document.getElementById('qa-empty');
listGrid.classList.add('hidden');
emptyEl.classList.add('hidden');
loadingEl.classList.remove('hidden');
let url = `/stajyer/admin/quick-approval-entries?type=${currentQuickFilter}`;
if (currentQuickFilter === 'today') {
const selectedDate = document.getElementById('qf-date-input').value;
if (selectedDate) {
url += `&date=${selectedDate}`;
}
}
fetch(url)
.then(res => res.json())
.then(data => {
loadingEl.classList.add('hidden');
if (!data.success) {
alert('Kayıtlar yüklenemedi.');
return;
}
quickApprovalEntries = data.entries;
// Update unapproved badge dynamic count if filter is unapproved
if (currentQuickFilter === 'unapproved') {
updateBadgeCount(quickApprovalEntries.length);
}
if (quickApprovalEntries.length === 0) {
const emptyTitle = document.getElementById('qa-empty-title');
const emptyDesc = document.getElementById('qa-empty-desc');
if (currentQuickFilter === 'unapproved') {
emptyTitle.textContent = "Onay Bekleyen Defter Yok";
emptyDesc.textContent = "Harika! Şu anda onayınızı bekleyen hiçbir stajyer raporu bulunmuyor.";
} else {
emptyTitle.textContent = "Kayıt Bulunamadı";
emptyDesc.textContent = "Seçilen tarihte yazılmış herhangi bir stajyer defteri kaydı bulunmuyor.";
}
emptyEl.classList.remove('hidden');
return;
}
renderQuickListGrid();
listGrid.classList.remove('hidden');
})
.catch(err => {
console.error(err);
loadingEl.classList.add('hidden');
alert('Sunucuyla bağlantı kurulurken bir hata oluştu.');
});
}
function renderQuickListGrid() {
const grid = document.getElementById('qa-list-grid');
grid.innerHTML = '';
quickApprovalEntries.forEach((entry, index) => {
// Helper to strip HTML tags for a clean content preview snippet
const tempDiv = document.createElement('div');
tempDiv.innerHTML = entry.content;
const cleanText = tempDiv.textContent || tempDiv.innerText || "";
const snippet = cleanText.length > 120 ? cleanText.substring(0, 120) + '...' : cleanText;
const card = document.createElement('div');
card.id = `qa-card-${entry.id}`;
card.className = "bg-slate-50/50 rounded-2xl p-5 border border-slate-200/40 hover:border-blue-200 hover:bg-blue-50/5 transition-all flex flex-col justify-between shadow-sm relative";
let badgeHtml = '';
if (entry.supervisor_approved) {
badgeHtml = `<span class="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-[10px] font-extrabold uppercase bg-emerald-50 text-emerald-700 border border-emerald-100">ONAYLI</span>`;
} else {
badgeHtml = `<span class="inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-[10px] font-extrabold uppercase bg-amber-50 text-amber-700 border border-amber-200">ONAY BEKLİYOR</span>`;
}
let retroHtml = '';
if (entry.is_retroactive) {
retroHtml = `<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[8px] font-extrabold uppercase bg-rose-50 text-rose-600 border border-rose-100">Geriye Dönük</span>`;
}
card.innerHTML = `
<div class="mb-4">
<!-- Top Row -->
<div class="flex items-start justify-between gap-2 mb-3">
<div>
<h4 class="font-extrabold text-slate-800 text-sm hover:underline hover:text-blue-600 cursor-pointer" onclick="openInternJournalModal(${entry.intern.id})">
${escapeHTML(entry.intern.name)}
</h4>
<span class="text-[10px] text-slate-400 font-semibold block mt-0.5">${escapeHTML(entry.intern.email)}</span>
</div>
<div class="flex flex-col items-end gap-1.5">
${badgeHtml}
${retroHtml}
</div>
</div>
<!-- Day & Date Info -->
<div class="mb-3 text-[11px] font-extrabold text-blue-600 bg-blue-50/50 px-2.5 py-1.5 rounded-xl w-max">
${entry.day_number}. Gün (${entry.formatted_date})
</div>
<!-- Snippet Content Preview -->
<p class="text-xs text-slate-600 leading-relaxed font-medium bg-white p-3.5 rounded-xl border border-slate-100/80 min-h-[60px] italic">
"${escapeHTML(snippet)}"
</p>
</div>
<!-- Card Actions -->
<div class="pt-3 border-t border-slate-200/50 flex items-center justify-between gap-3">
<button type="button" onclick="openQuickEntryModal(${index})" class="px-3.5 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 font-bold rounded-xl text-[11px] transition-all cursor-pointer flex items-center gap-1.5">
<i class="uil uil-book-open"></i> Oku & Onayla
</button>
${!entry.supervisor_approved ? `
<button type="button" id="quick-approve-btn-${entry.id}" onclick="quickApproveEntry(${entry.id}, ${index})" class="px-3.5 py-2 bg-emerald-600 hover:bg-emerald-700 text-white font-bold rounded-xl text-[11px] transition-all shadow-md shadow-emerald-500/10 cursor-pointer flex items-center gap-1">
<i class="uil uil-check"></i> Hızlı Onayla
</button>
` : `
<button type="button" id="quick-approve-btn-${entry.id}" onclick="quickApproveEntry(${entry.id}, ${index})" class="px-3.5 py-2 bg-rose-50 hover:bg-rose-100 text-rose-600 border border-rose-200 font-bold rounded-xl text-[11px] transition-all cursor-pointer flex items-center gap-1">
<i class="uil uil-times"></i> Onayı Kaldır
</button>
`}
</div>
`;
grid.appendChild(card);
});
}
function updateBadgeCount(count) {
const badge = document.getElementById('quick-approval-badge');
if (badge) {
badge.textContent = count;
if (count > 0) {
badge.classList.remove('hidden');
} else {
badge.classList.add('hidden');
}
}
}
function quickApproveEntry(entryId, index) {
const btn = document.getElementById(`quick-approve-btn-${entryId}`);
if (!btn || isUpdatingQuickApproval) return;
isUpdatingQuickApproval = true;
btn.style.opacity = '0.5';
fetch('/stajyer/admin/toggle-approval', {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": "{{ csrf_token() }}"
},
body: JSON.stringify({
entry_id: entryId
})
})
.then(res => res.json())
.then(data => {
if (data.success) {
// Update entry status in local array
quickApprovalEntries[index].supervisor_approved = data.status;
quickApprovalEntries[index].supervisor_name = data.supervisor_name;
// If in "unapproved" view, and we approved, we can slide out the card nicely
if (currentQuickFilter === 'unapproved' && data.status) {
const card = document.getElementById(`qa-card-${entryId}`);
if (card) {
card.style.transition = 'all 0.3s ease-out';
card.style.transform = 'scale(0.95)';
card.style.opacity = '0';
setTimeout(() => {
// Remove item from array and re-render or just remove the element
quickApprovalEntries = quickApprovalEntries.filter(e => e.id !== entryId);
updateBadgeCount(quickApprovalEntries.length);
if (quickApprovalEntries.length === 0) {
document.getElementById('qa-list-grid').classList.add('hidden');
document.getElementById('qa-empty').classList.remove('hidden');
} else {
renderQuickListGrid();
}
}, 300);
}
} else {
// Just re-render list
renderQuickListGrid();
}
} else {
alert(data.message || 'Onay güncellenemedi.');
}
})
.catch(err => {
console.error(err);
alert('İşlem gerçekleştirilemedi.');
})
.finally(() => {
isUpdatingQuickApproval = false;
if (btn) btn.style.opacity = '1';
});
}
// Quick entry modal logic
function openQuickEntryModal(index) {
activeQuickEntryIndex = index;
const modal = document.getElementById('quick-entry-modal');
if (!modal) return;
modal.classList.remove('hidden');
modal.classList.add('flex');
const card = document.getElementById('quick-entry-modal-card');
if (card) {
setTimeout(() => {
card.classList.remove('scale-95', 'opacity-0');
card.classList.add('scale-100', 'opacity-100');
}, 50);
}
renderQuickEntryDetails();
}
function closeQuickEntryModal() {
const modal = document.getElementById('quick-entry-modal');
if (!modal) return;
const card = document.getElementById('quick-entry-modal-card');
if (card) {
card.classList.remove('scale-100', 'opacity-100');
card.classList.add('scale-95', 'opacity-0');
}
setTimeout(() => {
modal.classList.add('hidden');
modal.classList.remove('flex');
}, 200);
}
function renderQuickEntryDetails() {
if (activeQuickEntryIndex < 0 || activeQuickEntryIndex >= quickApprovalEntries.length) return;
const entry = quickApprovalEntries[activeQuickEntryIndex];
// Header details
document.getElementById('qem-intern-name').textContent = entry.intern.name;
document.getElementById('qem-meta').textContent = `${entry.day_number}. Gün - ${entry.formatted_date}`;
// Retroactive badge
const retroBadge = document.getElementById('qem-retroactive-badge');
retroBadge.innerHTML = '';
if (entry.is_retroactive) {
retroBadge.innerHTML = `
<span class="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-rose-50 text-rose-700 text-[10px] font-extrabold uppercase border border-rose-100">
<span class="w-1.5 h-1.5 rounded-full bg-rose-500 animate-pulse"></span>
Geriye Dönük Kayıt
</span>
`;
}
// Body content (Sanitized using DOMPurify!)
const cleanHTML = DOMPurify.sanitize(entry.content);
document.getElementById('qem-content').innerHTML = cleanHTML || '<p class="text-slate-400 italic">Boş içerik.</p>';
// Carousel buttons indicators
document.getElementById('qem-indicator').textContent = `${activeQuickEntryIndex + 1} / ${quickApprovalEntries.length}`;
const btnPrev = document.getElementById('qem-btn-prev');
const btnNext = document.getElementById('qem-btn-next');
btnPrev.disabled = activeQuickEntryIndex === 0;
btnPrev.style.opacity = activeQuickEntryIndex === 0 ? '0.4' : '1';
btnPrev.style.cursor = activeQuickEntryIndex === 0 ? 'not-allowed' : 'pointer';
btnNext.disabled = activeQuickEntryIndex === quickApprovalEntries.length - 1;
btnNext.style.opacity = activeQuickEntryIndex === quickApprovalEntries.length - 1 ? '0.4' : '1';
btnNext.style.cursor = activeQuickEntryIndex === quickApprovalEntries.length - 1 ? 'not-allowed' : 'pointer';
// Action button
const actBtn = document.getElementById('qem-action-btn');
if (entry.supervisor_approved) {
actBtn.textContent = "Onayı Kaldır";
actBtn.className = "px-5 py-2.5 text-white bg-rose-600 hover:bg-rose-700 rounded-xl font-bold text-xs shadow-lg shadow-rose-500/20 transition-all cursor-pointer";
} else {
actBtn.textContent = "Günü Onayla";
actBtn.className = "px-5 py-2.5 text-white bg-emerald-600 hover:bg-emerald-700 rounded-xl font-bold text-xs shadow-lg shadow-emerald-500/20 transition-all cursor-pointer";
}
}
function prevQuickEntry() {
if (activeQuickEntryIndex > 0) {
activeQuickEntryIndex--;
renderQuickEntryDetails();
}
}
function nextQuickEntry() {
if (activeQuickEntryIndex < quickApprovalEntries.length - 1) {
activeQuickEntryIndex++;
renderQuickEntryDetails();
}
}
function toggleQuickEntryApproval() {
if (activeQuickEntryIndex < 0 || isUpdatingQuickApproval) return;
const entry = quickApprovalEntries[activeQuickEntryIndex];
const actBtn = document.getElementById('qem-action-btn');
isUpdatingQuickApproval = true;
actBtn.style.opacity = '0.5';
fetch('/stajyer/admin/toggle-approval', {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": "{{ csrf_token() }}"
},
body: JSON.stringify({
entry_id: entry.id
})
})
.then(res => res.json())
.then(data => {
if (data.success) {
entry.supervisor_approved = data.status;
entry.supervisor_name = data.supervisor_name;
// If in unapproved filter, and we approved, we'll remove it after closing/switching, or we can handle it seamlessly
// For modal, it's nice to either show updated status, or automatically move to next unapproved entry!
if (currentQuickFilter === 'unapproved' && data.status) {
// Approved! Remove from list, decrement badge, and move to next or close if empty
const oldIndex = activeQuickEntryIndex;
quickApprovalEntries = quickApprovalEntries.filter(e => e.id !== entry.id);
updateBadgeCount(quickApprovalEntries.length);
if (quickApprovalEntries.length === 0) {
closeQuickEntryModal();
document.getElementById('qa-list-grid').classList.add('hidden');
document.getElementById('qa-empty').classList.remove('hidden');
} else {
// Keep the index clamped within the new list bounds
activeQuickEntryIndex = Math.min(oldIndex, quickApprovalEntries.length - 1);
renderQuickEntryDetails();
renderQuickListGrid();
}
} else {
// Just update UI inside modal & grid
renderQuickEntryDetails();
renderQuickListGrid();
}
} else {
alert(data.message || 'Onay güncellenemedi.');
}
})
.catch(err => {
console.error(err);
alert('İşlem gerçekleştirilemedi.');
})
.finally(() => {
isUpdatingQuickApproval = false;
actBtn.style.opacity = '1';
});
}
function escapeHTML(str) {
if (!str) return '';
return str.replace(/[&<>'"]/g,
tag => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;'
}[tag] || tag)
);
}
// Handle ESC key for quick approval modal
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeQuickEntryModal();
}
});
</script>