526 lines
22 KiB
PHP
526 lines
22 KiB
PHP
<!-- 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 => ({
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>',
|
||
"'": ''',
|
||
'"': '"'
|
||
}[tag] || tag)
|
||
);
|
||
}
|
||
|
||
// Handle ESC key for quick approval modal
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
closeQuickEntryModal();
|
||
}
|
||
});
|
||
</script>
|