feat: implement quick approval dashboard for intern reports with filtered views and modal navigation
This commit is contained in:
@@ -23,6 +23,14 @@
|
||||
<i class="uil uil-list-ul text-lg"></i>
|
||||
<span>Stajyer Listesi ve Detaylı Takip</span>
|
||||
</button>
|
||||
|
||||
<button type="button" role="tab" aria-selected="false" data-tab-target="quick-approval" class="admin-tab-btn flex-1 flex items-center justify-center gap-3 py-4 px-6 rounded-2xl text-center font-bold text-sm transition-all border border-transparent text-slate-600 hover:bg-slate-50 cursor-pointer relative">
|
||||
<i class="uil uil-check-square text-lg"></i>
|
||||
<span>Hızlı Defter Onaylama</span>
|
||||
<span id="quick-approval-badge" class="{{ $unapprovedCount > 0 ? '' : 'hidden' }} absolute -top-1 -right-1 flex h-5 min-w-[20px] items-center justify-center rounded-full bg-rose-500 px-1.5 text-[10px] font-black text-white ring-2 ring-white shadow-md">
|
||||
{{ $unapprovedCount }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,6 +43,10 @@
|
||||
@include('front.career.partials.list')
|
||||
</div>
|
||||
|
||||
<div id="quick-approval-panel" class="admin-tab-panel hidden space-y-6">
|
||||
@include('front.career.partials.quick_approval')
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -632,6 +644,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetId === 'quick-approval') {
|
||||
if (typeof loadQuickApprovalEntries === 'function') {
|
||||
loadQuickApprovalEntries();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,525 @@
|
||||
<!-- 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>
|
||||
Reference in New Issue
Block a user