339 lines
14 KiB
PHP
339 lines
14 KiB
PHP
<!-- Gantt Chart Card -->
|
||
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50 mb-8">
|
||
<div class="flex items-center justify-between mb-6 pb-4 border-b border-slate-100">
|
||
<h3 class="font-bold text-slate-800 text-lg flex items-center gap-2">
|
||
<i class="uil uil-schedule text-blue-600 text-xl"></i>
|
||
<span>Stajyer Gantt Şeması ve Takvimi</span>
|
||
</h3>
|
||
<span class="text-xs px-2.5 py-1 bg-blue-50 text-blue-600 rounded-full font-semibold">DevExtreme Görünümü</span>
|
||
</div>
|
||
|
||
@if($ganttInterns->isEmpty())
|
||
<div class="flex flex-col items-center justify-center p-8 text-center bg-slate-50 rounded-2xl border border-dashed border-slate-200">
|
||
<i class="uil uil-calendar-slash text-4xl text-slate-300 mb-2"></i>
|
||
<p class="text-sm font-medium text-slate-500">Tarihleri belirlenmiş onaylı stajyer bulunamadı.</p>
|
||
</div>
|
||
@else
|
||
<!-- Gantt Container -->
|
||
<div class="dx-viewport demo-container" style="height: 480px; overflow: hidden; border-radius: 16px; border: 1px solid rgba(0,0,0,0.05);">
|
||
<div id="gantt" style="height: 100%; width: 100%;"></div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
<!-- Dynamic Journal Modal -->
|
||
<div id="journal-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-[90vh]">
|
||
<!-- Modal 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">
|
||
<div>
|
||
<h4 id="modal-title" class="font-bold text-slate-800 text-lg">Staj Raporu Detayı</h4>
|
||
<p id="modal-subtitle" class="text-xs text-slate-500 font-semibold mt-0.5"></p>
|
||
</div>
|
||
<button type="button" onclick="closeJournalModal()" 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>
|
||
|
||
<!-- Modal Body -->
|
||
<div class="p-6 overflow-y-auto flex-grow prose max-w-none text-slate-700 leading-relaxed text-sm no-scrollbar">
|
||
<div id="modal-content" class="min-h-[100px] flex flex-col justify-center">
|
||
<!-- Content gets loaded here -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal Footer -->
|
||
<div class="px-6 py-4 bg-slate-50/50 border-t border-slate-100 flex items-center justify-between">
|
||
<div id="modal-status-badge">
|
||
<!-- Status badge -->
|
||
</div>
|
||
<div class="flex gap-3">
|
||
<button type="button" onclick="closeJournalModal()" class="px-4 py-2 border border-slate-200 text-slate-600 hover:bg-slate-50 rounded-xl font-bold text-xs transition-colors">Kapat</button>
|
||
<button type="button" id="modal-action-btn" onclick="handleModalAction()" class="hidden px-4 py-2 text-white bg-blue-600 hover:bg-blue-700 rounded-xl font-bold text-xs shadow-lg shadow-blue-500/20 transition-all"></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
@push('styles')
|
||
<!-- DevExtreme Gantt CSS Dependencies -->
|
||
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.2.5/css/dx.fluent.blue.light.css">
|
||
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.2.5/css/dx-gantt.min.css">
|
||
<style>
|
||
.current-time-line {
|
||
background-color: rgba(239, 68, 68, 0.4) !important;
|
||
border-left: 2px dashed #ef4444 !important;
|
||
width: 2px !important;
|
||
}
|
||
.rich-text-content p { margin-bottom: 8px; }
|
||
.rich-text-content ul { list-style-type: disc !important; padding-left: 20px !important; margin-bottom: 8px !important; }
|
||
.rich-text-content ol { list-style-type: decimal !important; padding-left: 20px !important; margin-bottom: 8px !important; }
|
||
.rich-text-content li { margin-bottom: 4px !important; }
|
||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
||
</style>
|
||
<script>
|
||
tailwind = {
|
||
config: {
|
||
corePlugins: {
|
||
preflight: false,
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
@endpush
|
||
|
||
@push('scripts')
|
||
<!-- Gantt Chart Javascript Dependencies & Initialization -->
|
||
@if(!$ganttInterns->isEmpty())
|
||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||
<script src="https://cdn3.devexpress.com/jslib/23.2.5/js/dx-gantt.min.js"></script>
|
||
<script src="https://cdn3.devexpress.com/jslib/23.2.5/js/dx.all.js"></script>
|
||
<script>
|
||
$(function() {
|
||
const ganttData = @json($ganttInterns);
|
||
|
||
const formattedData = ganttData.map(intern => ({
|
||
id: intern.id,
|
||
title: intern.title,
|
||
start: new Date(intern.start),
|
||
end: new Date(intern.end),
|
||
progress: 0
|
||
}));
|
||
|
||
let dayColumnsMap = {};
|
||
|
||
$('#gantt').dxGantt({
|
||
tasks: {
|
||
dataSource: formattedData,
|
||
},
|
||
editing: {
|
||
enabled: false,
|
||
},
|
||
validation: {
|
||
autoUpdateParentTasks: true,
|
||
},
|
||
toolbar: {
|
||
items: [
|
||
'collapseAll',
|
||
'expandAll',
|
||
'separator',
|
||
'zoomIn',
|
||
'zoomOut',
|
||
],
|
||
},
|
||
columns: [{
|
||
dataField: 'title',
|
||
caption: 'Stajyer Adı',
|
||
width: 200,
|
||
}, {
|
||
dataField: 'start',
|
||
caption: 'Başlangıç Tarihi',
|
||
dataType: 'date',
|
||
format: 'dd.MM.yyyy',
|
||
width: 110,
|
||
}, {
|
||
dataField: 'end',
|
||
caption: 'Bitiş Tarihi',
|
||
dataType: 'date',
|
||
format: 'dd.MM.yyyy',
|
||
width: 110,
|
||
}],
|
||
scaleType: 'days',
|
||
taskListWidth: 420,
|
||
stripLines: [{
|
||
title: 'Bugün',
|
||
start: new Date(),
|
||
cssClass: 'current-time-line'
|
||
}],
|
||
onScaleCellPrepared: function(e) {
|
||
if (e.scaleType === 'days') {
|
||
const $el = $(e.scaleElement || e.element);
|
||
const left = $el.position().left;
|
||
const width = $el.outerWidth();
|
||
const dateStr = e.startDate.toISOString().substring(0, 10);
|
||
dayColumnsMap[dateStr] = {
|
||
left: left,
|
||
right: left + width,
|
||
date: e.startDate,
|
||
dateStr: dateStr
|
||
};
|
||
}
|
||
}
|
||
});
|
||
|
||
// Grid task area cell click listener
|
||
$(document).on('click', '#gantt', function(e) {
|
||
const $target = $(e.target);
|
||
if ($target.closest('.dx-gantt-task-area').length > 0 || $target.closest('.dx-gantt-ts-area').length > 0) {
|
||
const ganttInstance = $('#gantt').dxGantt('instance');
|
||
const selectedKey = ganttInstance.option("selectedRowKey");
|
||
if (!selectedKey) {
|
||
alert("Lütfen önce sol taraftan stajyeri seçin, ardından tıklamak istediğiniz güne tıklayın.");
|
||
return;
|
||
}
|
||
|
||
const $taskArea = $('.dx-gantt-task-area').first();
|
||
if (!$taskArea.length) return;
|
||
|
||
const scrollLeft = ganttInstance._ganttView._taskAreaContainer.scrollLeft;
|
||
const clickX = e.pageX - $taskArea.offset().left;
|
||
const totalX = scrollLeft + clickX;
|
||
|
||
const cols = Object.values(dayColumnsMap);
|
||
const clickedCol = cols.find(col => totalX >= col.left && totalX <= col.right);
|
||
if (clickedCol) {
|
||
showDailyJournalPopup(selectedKey, clickedCol.dateStr);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
let activeEntryId = null;
|
||
|
||
function showDailyJournalPopup(internId, dateStr) {
|
||
const modal = document.getElementById('journal-modal');
|
||
const contentDiv = document.getElementById('modal-content');
|
||
const titleH = document.getElementById('modal-title');
|
||
const subtitleP = document.getElementById('modal-subtitle');
|
||
const badgeDiv = document.getElementById('modal-status-badge');
|
||
const actionBtn = document.getElementById('modal-action-btn');
|
||
|
||
modal.classList.remove('hidden');
|
||
modal.classList.add('flex');
|
||
setTimeout(() => {
|
||
modal.firstElementChild.classList.remove('scale-95', 'opacity-0');
|
||
modal.firstElementChild.classList.add('scale-100', 'opacity-100');
|
||
}, 50);
|
||
|
||
contentDiv.innerHTML = `
|
||
<div class="flex items-center justify-center p-8">
|
||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||
</div>
|
||
`;
|
||
titleH.textContent = "Yükleniyor...";
|
||
subtitleP.textContent = "";
|
||
badgeDiv.innerHTML = "";
|
||
actionBtn.classList.add('hidden');
|
||
activeEntryId = null;
|
||
|
||
fetch(`/stajyer/admin/journal-entry?intern_id=${internId}&date=${dateStr}`)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (!data.success) {
|
||
contentDiv.innerHTML = `<p class="text-slate-500 italic text-center p-8">${data.message}</p>`;
|
||
titleH.textContent = "Bilgi";
|
||
return;
|
||
}
|
||
|
||
titleH.textContent = `${data.intern_name}`;
|
||
subtitleP.textContent = `${data.day_number}. Gün Raporu — ${data.date_formatted}`;
|
||
|
||
if (!data.entry) {
|
||
contentDiv.innerHTML = `
|
||
<div class="flex flex-col items-center justify-center p-8 text-center bg-slate-50 rounded-2xl border border-dashed border-slate-200">
|
||
<i class="uil uil-file-slash text-3xl text-slate-400 mb-2"></i>
|
||
<p class="text-sm font-medium text-slate-500">Bu gün için henüz staj raporu yazılmamıştır.</p>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
activeEntryId = data.entry.id;
|
||
const entryContent = data.entry.content || '<p class="text-slate-400 italic">Boş içerik.</p>';
|
||
|
||
let contentHtml = '';
|
||
if (data.entry.is_retroactive) {
|
||
contentHtml += `
|
||
<div class="mb-4 inline-flex items-center gap-1.5 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
|
||
</div>
|
||
`;
|
||
}
|
||
contentHtml += `<div class="rich-text-content prose max-w-none text-slate-700 leading-relaxed text-sm select-text">${entryContent}</div>`;
|
||
contentDiv.innerHTML = contentHtml;
|
||
|
||
updateModalStatus(data.entry.supervisor_approved, data.entry.supervisor_name);
|
||
})
|
||
.catch(err => {
|
||
console.error(err);
|
||
contentDiv.innerHTML = `<p class="text-red-500 text-center p-8 font-semibold">Veriler yüklenirken bir hata oluştu.</p>`;
|
||
titleH.textContent = "Hata";
|
||
});
|
||
}
|
||
|
||
function updateModalStatus(approved, name) {
|
||
const badgeDiv = document.getElementById('modal-status-badge');
|
||
const actionBtn = document.getElementById('modal-action-btn');
|
||
|
||
actionBtn.classList.remove('hidden');
|
||
if (approved) {
|
||
badgeDiv.innerHTML = `
|
||
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-50 text-emerald-700 text-xs font-bold border border-emerald-100">
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>
|
||
Sorumlu Onayladı ${name ? `(${name})` : ''}
|
||
</span>
|
||
`;
|
||
actionBtn.textContent = "Onayı Kaldır";
|
||
actionBtn.className = "px-4 py-2 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 {
|
||
badgeDiv.innerHTML = `
|
||
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-slate-100 text-slate-600 text-xs font-bold border border-slate-200">
|
||
Onay Bekliyor
|
||
</span>
|
||
`;
|
||
actionBtn.textContent = "Raporu Onayla";
|
||
actionBtn.className = "px-4 py-2 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 handleModalAction() {
|
||
if (!activeEntryId) return;
|
||
|
||
const actionBtn = document.getElementById('modal-action-btn');
|
||
actionBtn.disabled = true;
|
||
actionBtn.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: activeEntryId
|
||
})
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
updateModalStatus(data.status, data.supervisor_name);
|
||
} else {
|
||
alert(data.message || "Onay güncellenemedi.");
|
||
}
|
||
})
|
||
.catch(err => {
|
||
console.error(err);
|
||
alert("İşlem sırasında bir hata oluştu.");
|
||
})
|
||
.finally(() => {
|
||
actionBtn.disabled = false;
|
||
actionBtn.style.opacity = "1";
|
||
});
|
||
}
|
||
|
||
function closeJournalModal() {
|
||
const modal = document.getElementById('journal-modal');
|
||
modal.firstElementChild.classList.add('scale-95', 'opacity-0');
|
||
modal.firstElementChild.classList.remove('scale-100', 'opacity-100');
|
||
setTimeout(() => {
|
||
modal.classList.add('hidden');
|
||
modal.classList.remove('flex');
|
||
}, 200);
|
||
}
|
||
</script>
|
||
@endif
|
||
@endpush
|