Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 129dae4570 | |||
| f0a9180a54 |
@@ -393,6 +393,7 @@ class CareerController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$allInterns = CareerApplication::where('type', 'internship')
|
$allInterns = CareerApplication::where('type', 'internship')
|
||||||
|
->with(['journalEntries'])
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
@@ -606,5 +607,114 @@ class CareerController extends Controller
|
|||||||
'message' => 'Staj sorumlusu onayı güncellendi.'
|
'message' => 'Staj sorumlusu onayı güncellendi.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getInternJournalDetails(Request $request)
|
||||||
|
{
|
||||||
|
if (!auth()->check()) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Yetkisiz işlem.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'intern_id' => 'required|integer|exists:career_applications,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$intern = CareerApplication::findOrFail($request->intern_id);
|
||||||
|
|
||||||
|
// Get all days calculated
|
||||||
|
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
|
||||||
|
|
||||||
|
// Get saved journal entries
|
||||||
|
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
|
||||||
|
|
||||||
|
// Prepare entries list matched by day number
|
||||||
|
$entries = [];
|
||||||
|
$filledDaysCount = 0;
|
||||||
|
|
||||||
|
foreach ($days as $d) {
|
||||||
|
$dayNum = $d['day_number'];
|
||||||
|
$entry = $savedEntries->get($dayNum);
|
||||||
|
|
||||||
|
$hasSaved = $entry && trim($entry->content) !== '';
|
||||||
|
if ($hasSaved) {
|
||||||
|
$filledDaysCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries[] = [
|
||||||
|
'day_number' => $dayNum,
|
||||||
|
'date' => $d['date'],
|
||||||
|
'formatted_date' => $d['formatted_date'],
|
||||||
|
'filled' => $hasSaved,
|
||||||
|
'entry_id' => $entry ? $entry->id : null,
|
||||||
|
'content' => $entry ? $entry->content : null,
|
||||||
|
'is_retroactive' => $entry ? $entry->is_retroactive : false,
|
||||||
|
'supervisor_approved' => $entry ? (bool)$entry->supervisor_approved : false,
|
||||||
|
'supervisor_name' => $entry ? $entry->supervisor_name : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'intern' => [
|
||||||
|
'id' => $intern->id,
|
||||||
|
'name' => $intern->name,
|
||||||
|
'email' => $intern->email,
|
||||||
|
'phone' => $intern->phone,
|
||||||
|
'start_date' => $intern->internship_start_date,
|
||||||
|
'end_date' => $intern->internship_end_date,
|
||||||
|
'total_days' => $intern->internship_total_days,
|
||||||
|
'filled_days' => $filledDaysCount,
|
||||||
|
'notebook_supervisor_signed' => (bool)$intern->notebook_supervisor_signed,
|
||||||
|
'notebook_supervisor_name' => $intern->notebook_supervisor_name,
|
||||||
|
'notebook_unit_signed' => (bool)$intern->notebook_unit_signed,
|
||||||
|
'notebook_unit_name' => $intern->notebook_unit_name,
|
||||||
|
'notebook_approved' => (bool)$intern->notebook_approved,
|
||||||
|
],
|
||||||
|
'entries' => $entries,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toggleNotebookSignature(Request $request)
|
||||||
|
{
|
||||||
|
if (!auth()->check()) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Yetkisiz işlem.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'intern_id' => 'required|integer|exists:career_applications,id',
|
||||||
|
'type' => 'required|string|in:supervisor,unit,approved',
|
||||||
|
'signed' => 'required|boolean',
|
||||||
|
'name' => 'nullable|string|max:255',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$intern = CareerApplication::findOrFail($request->intern_id);
|
||||||
|
|
||||||
|
$type = $request->type;
|
||||||
|
$signed = $request->signed;
|
||||||
|
$name = $request->name ?: auth()->user()->name;
|
||||||
|
|
||||||
|
if ($type === 'supervisor') {
|
||||||
|
$intern->notebook_supervisor_signed = $signed;
|
||||||
|
$intern->notebook_supervisor_name = $signed ? $name : null;
|
||||||
|
} elseif ($type === 'unit') {
|
||||||
|
$intern->notebook_unit_signed = $signed;
|
||||||
|
$intern->notebook_unit_name = $signed ? $name : null;
|
||||||
|
} elseif ($type === 'approved') {
|
||||||
|
$intern->notebook_approved = $signed;
|
||||||
|
}
|
||||||
|
|
||||||
|
$intern->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Staj defteri onay durumu güncellendi.',
|
||||||
|
'intern' => [
|
||||||
|
'notebook_supervisor_signed' => (bool)$intern->notebook_supervisor_signed,
|
||||||
|
'notebook_supervisor_name' => $intern->notebook_supervisor_name,
|
||||||
|
'notebook_unit_signed' => (bool)$intern->notebook_unit_signed,
|
||||||
|
'notebook_unit_name' => $intern->notebook_unit_name,
|
||||||
|
'notebook_approved' => (bool)$intern->notebook_approved,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,4 +22,542 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
@include('front.career.partials.guide_modal')
|
@include('front.career.partials.guide_modal')
|
||||||
|
<!-- Intern Journal Tracking & Approval Modal -->
|
||||||
|
<div id="intern-journal-modal" class="fixed inset-0 z-[9998] 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-4xl w-full mx-4 overflow-hidden transform scale-95 opacity-0 transition-all duration-300 flex flex-col max-h-[90vh]">
|
||||||
|
<!-- 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="ijm-name" class="font-bold text-slate-800 text-lg">Stajyer Defteri ve İnceleme</h4>
|
||||||
|
<p id="ijm-meta" class="text-xs text-slate-500 font-semibold mt-0.5"></p>
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="closeInternJournalModal()" 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 grid grid-cols-1 md:grid-cols-12 gap-6">
|
||||||
|
|
||||||
|
<!-- Left Sidebar: Progress & General Approvals (col-span-4) -->
|
||||||
|
<div class="md:col-span-4 space-y-6">
|
||||||
|
|
||||||
|
<!-- Progress Bar Card -->
|
||||||
|
<div class="bg-slate-50 rounded-2xl p-5 border border-slate-200/50">
|
||||||
|
<h5 class="font-bold text-slate-700 text-xs uppercase tracking-wider mb-4 flex items-center gap-1.5">
|
||||||
|
<i class="uil uil-chart-bar text-blue-600"></i>
|
||||||
|
<span>Staj İlerlemesi</span>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<!-- Journal Filling Progress -->
|
||||||
|
<div class="space-y-2 mb-4">
|
||||||
|
<div class="flex justify-between text-xs font-bold">
|
||||||
|
<span class="text-slate-500">Defter Doldurma</span>
|
||||||
|
<span id="ijm-fill-text" class="text-slate-700">0 / 0 Gün</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-slate-200 h-2.5 rounded-full overflow-hidden">
|
||||||
|
<div id="ijm-fill-progress" class="bg-blue-600 h-full rounded-full transition-all duration-500" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Journal Approval Progress -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between text-xs font-bold">
|
||||||
|
<span class="text-slate-500">Onaylı Günler</span>
|
||||||
|
<span id="ijm-approved-text" class="text-slate-700">0 / 0 Gün</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-slate-200 h-2.5 rounded-full overflow-hidden">
|
||||||
|
<div id="ijm-approved-progress" class="bg-emerald-500 h-full rounded-full transition-all duration-500" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Overall Signatures Card -->
|
||||||
|
<div class="bg-slate-50 rounded-2xl p-5 border border-slate-200/50 space-y-4">
|
||||||
|
<h5 class="font-bold text-slate-700 text-xs uppercase tracking-wider flex items-center gap-1.5">
|
||||||
|
<i class="uil uil-signature text-indigo-600"></i>
|
||||||
|
<span>Resmi Onaylar / İmzalar</span>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<!-- Supervisor Signature -->
|
||||||
|
<div class="p-3 bg-white rounded-xl border border-slate-100 flex flex-col gap-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xs font-bold text-slate-600">Sorumlu İmzası</span>
|
||||||
|
<button type="button" id="ijm-btn-supervisor" onclick="toggleNotebookSignature('supervisor')" class="px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border transition-all"></button>
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] text-slate-400 font-semibold" id="ijm-text-supervisor">İmzalanmadı</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Unit Signature -->
|
||||||
|
<div class="p-3 bg-white rounded-xl border border-slate-100 flex flex-col gap-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xs font-bold text-slate-600">Birim Sorumlu İmzası</span>
|
||||||
|
<button type="button" id="ijm-btn-unit" onclick="toggleNotebookSignature('unit')" class="px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border transition-all"></button>
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] text-slate-400 font-semibold" id="ijm-text-unit">İmzalanmadı</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notebook Approved -->
|
||||||
|
<div class="p-3 bg-white rounded-xl border border-slate-100 flex flex-col gap-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xs font-bold text-slate-600">Genel Defter Onayı</span>
|
||||||
|
<button type="button" id="ijm-btn-approved" onclick="toggleNotebookSignature('approved')" class="px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border transition-all"></button>
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] text-slate-400 font-semibold" id="ijm-text-approved">Onaylanmadı</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Section: Carousel & Navigation (col-span-8) -->
|
||||||
|
<div class="md:col-span-8 flex flex-col space-y-4">
|
||||||
|
|
||||||
|
<!-- Carousel Controls -->
|
||||||
|
<div class="bg-slate-50 p-4 rounded-2xl border border-slate-200/50 flex items-center justify-between gap-3">
|
||||||
|
<button type="button" onclick="prevSlide()" class="p-2 bg-white hover:bg-slate-100 border border-slate-200 text-slate-600 hover:text-slate-900 rounded-xl transition-all flex items-center justify-center cursor-pointer">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M15 19l-7-7 7-7"/></svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="flex-grow flex items-center justify-center gap-3">
|
||||||
|
<span id="ijm-day-indicator" class="text-sm font-extrabold text-blue-600 bg-blue-50 px-3 py-1.5 rounded-xl">Gün: 0 / 0</span>
|
||||||
|
|
||||||
|
<!-- Quick Day Dropdown -->
|
||||||
|
<select id="ijm-day-select" onchange="goToSlide(this.value)" class="text-xs font-semibold text-slate-700 bg-white border border-slate-200 rounded-xl px-2.5 py-1.5 focus:outline-none focus:border-blue-400">
|
||||||
|
<!-- Options loaded dynamically -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" onclick="nextSlide()" class="p-2 bg-white hover:bg-slate-100 border border-slate-200 text-slate-600 hover:text-slate-900 rounded-xl transition-all flex items-center justify-center cursor-pointer">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Carousel Slide Container -->
|
||||||
|
<div class="bg-white rounded-2xl border border-slate-100 p-6 flex-grow flex flex-col shadow-sm min-h-[300px]">
|
||||||
|
<!-- Day Title & Date -->
|
||||||
|
<div class="flex items-center justify-between pb-3 border-b border-slate-100 mb-4 flex-shrink-0">
|
||||||
|
<div>
|
||||||
|
<span id="ijm-slide-title" class="text-sm font-extrabold text-slate-800">Seçili Gün</span>
|
||||||
|
<span id="ijm-slide-date" class="text-xs font-semibold text-slate-400 ml-2"></span>
|
||||||
|
</div>
|
||||||
|
<div id="ijm-slide-retroactive">
|
||||||
|
<!-- Retroactive badge -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide Content (Rich text HTML) -->
|
||||||
|
<div class="flex-grow overflow-y-auto no-scrollbar prose max-w-none text-slate-700 leading-relaxed text-sm" style="max-height: 250px;">
|
||||||
|
<div id="ijm-slide-content">
|
||||||
|
<!-- Day content loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide Actions / Daily approval status -->
|
||||||
|
<div class="pt-4 border-t border-slate-100 flex items-center justify-between mt-4 flex-shrink-0">
|
||||||
|
<div id="ijm-slide-status-badge">
|
||||||
|
<!-- Status badge -->
|
||||||
|
</div>
|
||||||
|
<button type="button" id="ijm-slide-action-btn" onclick="toggleActiveDayApproval()" class="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 cursor-pointer">
|
||||||
|
Onayla
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="px-6 py-4 bg-slate-50 border-t border-slate-100 flex items-center justify-end flex-shrink-0">
|
||||||
|
<button type="button" onclick="closeInternJournalModal()" class="px-5 py-2.5 bg-slate-800 hover:bg-slate-900 text-white font-bold rounded-xl text-xs transition-colors shadow-md cursor-pointer">Kapat</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
let currentInternId = null;
|
||||||
|
let journalEntries = [];
|
||||||
|
let activeSlideIndex = 0;
|
||||||
|
let isUpdatingSignature = false;
|
||||||
|
let isUpdatingDayApproval = false;
|
||||||
|
|
||||||
|
function openInternJournalModal(internId) {
|
||||||
|
currentInternId = internId;
|
||||||
|
activeSlideIndex = 0;
|
||||||
|
|
||||||
|
const modal = document.getElementById('intern-journal-modal');
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
modal.classList.add('flex');
|
||||||
|
|
||||||
|
// Animate open
|
||||||
|
const card = modal.querySelector('.max-w-4xl');
|
||||||
|
if (card) {
|
||||||
|
setTimeout(() => {
|
||||||
|
card.classList.remove('scale-95', 'opacity-0');
|
||||||
|
card.classList.add('scale-100', 'opacity-100');
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadInternJournalDetails(internId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeInternJournalModal() {
|
||||||
|
const modal = document.getElementById('intern-journal-modal');
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const card = modal.querySelector('.max-w-4xl');
|
||||||
|
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 loadInternJournalDetails(internId) {
|
||||||
|
const slideContent = document.getElementById('ijm-slide-content');
|
||||||
|
if (slideContent) {
|
||||||
|
slideContent.innerHTML = `
|
||||||
|
<div class="flex items-center justify-center p-12">
|
||||||
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`/stajyer/admin/journal-details?intern_id=${internId}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) {
|
||||||
|
alert(data.message || 'Veriler yüklenemedi.');
|
||||||
|
closeInternJournalModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Header details
|
||||||
|
const nameEl = document.getElementById('ijm-name');
|
||||||
|
if (nameEl) nameEl.textContent = data.intern.name;
|
||||||
|
|
||||||
|
let startF = data.intern.start_date ? formatDateStr(data.intern.start_date) : '-';
|
||||||
|
let endF = data.intern.end_date ? formatDateStr(data.intern.end_date) : '-';
|
||||||
|
const metaEl = document.getElementById('ijm-meta');
|
||||||
|
if (metaEl) metaEl.textContent = `${startF} - ${endF} (${data.intern.total_days} İş Günü)`;
|
||||||
|
|
||||||
|
// Setup Progress Bars
|
||||||
|
const fillPercent = data.intern.total_days > 0 ? (data.intern.filled_days / data.intern.total_days) * 100 : 0;
|
||||||
|
const fillTextEl = document.getElementById('ijm-fill-text');
|
||||||
|
if (fillTextEl) fillTextEl.textContent = `${data.intern.filled_days} / ${data.intern.total_days} Gün`;
|
||||||
|
const fillBarEl = document.getElementById('ijm-fill-progress');
|
||||||
|
if (fillBarEl) fillBarEl.style.width = `${fillPercent}%`;
|
||||||
|
|
||||||
|
// Approved days count
|
||||||
|
let approvedDaysCount = 0;
|
||||||
|
data.entries.forEach(e => {
|
||||||
|
if (e.filled && e.supervisor_approved) {
|
||||||
|
approvedDaysCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const approvedPercent = data.intern.total_days > 0 ? (approvedDaysCount / data.intern.total_days) * 100 : 0;
|
||||||
|
const appTextEl = document.getElementById('ijm-approved-text');
|
||||||
|
if (appTextEl) appTextEl.textContent = `${approvedDaysCount} / ${data.intern.total_days} Gün`;
|
||||||
|
const appBarEl = document.getElementById('ijm-approved-progress');
|
||||||
|
if (appBarEl) appBarEl.style.width = `${approvedPercent}%`;
|
||||||
|
|
||||||
|
// Setup Signatures
|
||||||
|
updateSignatureUI('supervisor', data.intern.notebook_supervisor_signed, data.intern.notebook_supervisor_name);
|
||||||
|
updateSignatureUI('unit', data.intern.notebook_unit_signed, data.intern.notebook_unit_name);
|
||||||
|
updateSignatureUI('approved', data.intern.notebook_approved, null);
|
||||||
|
|
||||||
|
// Setup carousel slide data
|
||||||
|
journalEntries = data.entries;
|
||||||
|
|
||||||
|
// Setup dropdown
|
||||||
|
const select = document.getElementById('ijm-day-select');
|
||||||
|
if (select) {
|
||||||
|
select.innerHTML = '';
|
||||||
|
journalEntries.forEach((entry, idx) => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = idx;
|
||||||
|
opt.textContent = `${entry.day_number}. Gün (${entry.formatted_date})` + (entry.filled ? ' [DOLU]' : ' [BOŞ]');
|
||||||
|
select.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set total indicator text
|
||||||
|
const indEl = document.getElementById('ijm-day-indicator');
|
||||||
|
if (indEl) indEl.textContent = `Gün: 1 / ${journalEntries.length}`;
|
||||||
|
|
||||||
|
// Render first slide
|
||||||
|
renderSlide(0);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert('Staj detayları yüklenirken bir hata oluştu.');
|
||||||
|
closeInternJournalModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateStr(dateStr) {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const parts = dateStr.split('-');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
return `${parts[2]}.${parts[1]}.${parts[0]}`;
|
||||||
|
}
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSignatureUI(type, signed, name) {
|
||||||
|
const btn = document.getElementById(`ijm-btn-${type}`);
|
||||||
|
const label = document.getElementById(`ijm-text-${type}`);
|
||||||
|
if (!btn || !label) return;
|
||||||
|
|
||||||
|
if (type === 'supervisor') {
|
||||||
|
if (signed) {
|
||||||
|
btn.textContent = "İmzayı Kaldır";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-rose-200 bg-rose-50 text-rose-600 hover:bg-rose-100 transition-all cursor-pointer";
|
||||||
|
label.innerHTML = `<span class="text-green-600 font-extrabold"><i class="uil uil-check-circle mr-0.5"></i>İmzaladı: ${name || ''}</span>`;
|
||||||
|
} else {
|
||||||
|
btn.textContent = "İmzala";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-indigo-200 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 transition-all cursor-pointer";
|
||||||
|
label.textContent = "İmzalanmadı";
|
||||||
|
}
|
||||||
|
} else if (type === 'unit') {
|
||||||
|
if (signed) {
|
||||||
|
btn.textContent = "İmzayı Kaldır";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-rose-200 bg-rose-50 text-rose-600 hover:bg-rose-100 transition-all cursor-pointer";
|
||||||
|
label.innerHTML = `<span class="text-green-600 font-extrabold"><i class="uil uil-check-circle mr-0.5"></i>İmzaladı: ${name || ''}</span>`;
|
||||||
|
} else {
|
||||||
|
btn.textContent = "İmzala";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-indigo-200 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 transition-all cursor-pointer";
|
||||||
|
label.textContent = "İmzalanmadı";
|
||||||
|
}
|
||||||
|
} else if (type === 'approved') {
|
||||||
|
if (signed) {
|
||||||
|
btn.textContent = "Onayı Kaldır";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-rose-200 bg-rose-50 text-rose-600 hover:bg-rose-100 transition-all cursor-pointer";
|
||||||
|
label.innerHTML = `<span class="text-green-600 font-extrabold"><i class="uil uil-check-circle mr-0.5"></i>Defter Onaylandı</span>`;
|
||||||
|
} else {
|
||||||
|
btn.textContent = "Onayla";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-emerald-200 bg-emerald-50 text-emerald-600 hover:bg-emerald-100 transition-all cursor-pointer";
|
||||||
|
label.textContent = "Onaylanmadı";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleNotebookSignature(type) {
|
||||||
|
if (isUpdatingSignature) return;
|
||||||
|
|
||||||
|
// Get current state
|
||||||
|
const btn = document.getElementById(`ijm-btn-${type}`);
|
||||||
|
if (!btn) return;
|
||||||
|
const isSignedCurrently = btn.textContent.includes('Kaldır');
|
||||||
|
const newSignState = !isSignedCurrently;
|
||||||
|
|
||||||
|
let promptName = '';
|
||||||
|
if (newSignState && (type === 'supervisor' || type === 'unit')) {
|
||||||
|
promptName = prompt("İmzalayan yetkili ismini giriniz veya onaylayınız:", @json(auth()->user()->name));
|
||||||
|
if (promptName === null) return; // cancelled
|
||||||
|
if (promptName.trim() === '') {
|
||||||
|
promptName = @json(auth()->user()->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isUpdatingSignature = true;
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
|
||||||
|
fetch('/stajyer/admin/toggle-notebook-signature', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
intern_id: currentInternId,
|
||||||
|
type: type,
|
||||||
|
signed: newSignState ? 1 : 0,
|
||||||
|
name: promptName
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
const key = type === 'supervisor' ? 'notebook_supervisor_signed' : (type === 'unit' ? 'notebook_unit_signed' : 'notebook_approved');
|
||||||
|
const nameKey = type === 'supervisor' ? 'notebook_supervisor_name' : (type === 'unit' ? 'notebook_unit_name' : null);
|
||||||
|
|
||||||
|
updateSignatureUI(type, data.intern[key], data.intern[nameKey]);
|
||||||
|
} else {
|
||||||
|
alert(data.message || 'Hata oluştu.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert('İşlem gerçekleştirilemedi.');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isUpdatingSignature = false;
|
||||||
|
btn.style.opacity = '1';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSlide(index) {
|
||||||
|
if (index < 0 || index >= journalEntries.length) return;
|
||||||
|
|
||||||
|
activeSlideIndex = index;
|
||||||
|
const entry = journalEntries[index];
|
||||||
|
|
||||||
|
// Update indicator & select dropdown
|
||||||
|
const indEl = document.getElementById('ijm-day-indicator');
|
||||||
|
if (indEl) indEl.textContent = `Gün: ${index + 1} / ${journalEntries.length}`;
|
||||||
|
const selEl = document.getElementById('ijm-day-select');
|
||||||
|
if (selEl) selEl.value = index;
|
||||||
|
|
||||||
|
// Title & Date
|
||||||
|
const titleEl = document.getElementById('ijm-slide-title');
|
||||||
|
if (titleEl) titleEl.textContent = `${entry.day_number}. Gün Raporu`;
|
||||||
|
const dateEl = document.getElementById('ijm-slide-date');
|
||||||
|
if (dateEl) dateEl.textContent = entry.formatted_date;
|
||||||
|
|
||||||
|
// Retroactive badge
|
||||||
|
const retroBadge = document.getElementById('ijm-slide-retroactive');
|
||||||
|
if (retroBadge) {
|
||||||
|
retroBadge.innerHTML = '';
|
||||||
|
if (entry.filled && entry.is_retroactive) {
|
||||||
|
retroBadge.innerHTML = `
|
||||||
|
<span class="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
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content area
|
||||||
|
const contentArea = document.getElementById('ijm-slide-content');
|
||||||
|
const badgeDiv = document.getElementById('ijm-slide-status-badge');
|
||||||
|
const actionBtn = document.getElementById('ijm-slide-action-btn');
|
||||||
|
if (!contentArea || !badgeDiv || !actionBtn) return;
|
||||||
|
|
||||||
|
if (!entry.filled) {
|
||||||
|
contentArea.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>
|
||||||
|
`;
|
||||||
|
badgeDiv.innerHTML = `<span class="text-xs text-slate-400 font-semibold">Boş Rapor</span>`;
|
||||||
|
actionBtn.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
actionBtn.classList.remove('hidden');
|
||||||
|
const entryContent = entry.content || '<p class="text-slate-400 italic">Boş içerik.</p>';
|
||||||
|
contentArea.innerHTML = `<div class="rich-text-content prose max-w-none text-slate-700 leading-relaxed text-sm select-text">${entryContent}</div>`;
|
||||||
|
|
||||||
|
updateDayApprovalUI(entry.supervisor_approved, entry.supervisor_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDayApprovalUI(approved, name) {
|
||||||
|
const badgeDiv = document.getElementById('ijm-slide-status-badge');
|
||||||
|
const actionBtn = document.getElementById('ijm-slide-action-btn');
|
||||||
|
if (!badgeDiv || !actionBtn) return;
|
||||||
|
|
||||||
|
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 = "Günü 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 toggleActiveDayApproval() {
|
||||||
|
if (isUpdatingDayApproval) return;
|
||||||
|
|
||||||
|
const entry = journalEntries[activeSlideIndex];
|
||||||
|
if (!entry || !entry.entry_id) return;
|
||||||
|
|
||||||
|
const actionBtn = document.getElementById('ijm-slide-action-btn');
|
||||||
|
if (!actionBtn) return;
|
||||||
|
isUpdatingDayApproval = 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: entry.entry_id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Update local object
|
||||||
|
entry.supervisor_approved = data.status;
|
||||||
|
entry.supervisor_name = data.supervisor_name;
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
updateDayApprovalUI(data.status, data.supervisor_name);
|
||||||
|
|
||||||
|
// Recalculate and update approved progress bar
|
||||||
|
let approvedDaysCount = 0;
|
||||||
|
journalEntries.forEach(e => {
|
||||||
|
if (e.filled && e.supervisor_approved) {
|
||||||
|
approvedDaysCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const approvedPercent = journalEntries.length > 0 ? (approvedDaysCount / journalEntries.length) * 100 : 0;
|
||||||
|
|
||||||
|
const appTextEl = document.getElementById('ijm-approved-text');
|
||||||
|
if (appTextEl) appTextEl.textContent = `${approvedDaysCount} / ${journalEntries.length} Gün`;
|
||||||
|
const appBarEl = document.getElementById('ijm-approved-progress');
|
||||||
|
if (appBarEl) appBarEl.style.width = `${approvedPercent}%`;
|
||||||
|
} else {
|
||||||
|
alert(data.message || "Onay güncellenemedi.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert("İşlem sırasında bir hata oluştu.");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isUpdatingDayApproval = false;
|
||||||
|
actionBtn.style.opacity = '1';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevSlide() {
|
||||||
|
if (activeSlideIndex > 0) {
|
||||||
|
renderSlide(activeSlideIndex - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextSlide() {
|
||||||
|
if (activeSlideIndex < journalEntries.length - 1) {
|
||||||
|
renderSlide(activeSlideIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToSlide(idx) {
|
||||||
|
renderSlide(parseInt(idx));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<tr class="border-b border-slate-100 text-xs font-extrabold uppercase tracking-wider text-slate-400">
|
<tr class="border-b border-slate-100 text-xs font-extrabold uppercase tracking-wider text-slate-400">
|
||||||
<th class="pb-4">Stajyer</th>
|
<th class="pb-4">Stajyer</th>
|
||||||
<th class="pb-4">Staj Tarihleri</th>
|
<th class="pb-4">Staj Tarihleri</th>
|
||||||
|
<th class="pb-4">Defter İlerlemesi</th>
|
||||||
<th class="pb-4">Durum</th>
|
<th class="pb-4">Durum</th>
|
||||||
<th class="pb-4">Belgeler</th>
|
<th class="pb-4">Belgeler</th>
|
||||||
<th class="pb-4">GitHub & Repo</th>
|
<th class="pb-4">GitHub & Repo</th>
|
||||||
@@ -22,7 +23,10 @@
|
|||||||
<tr class="hover:bg-slate-50/50 transition-colors">
|
<tr class="hover:bg-slate-50/50 transition-colors">
|
||||||
<!-- Name & Info -->
|
<!-- Name & Info -->
|
||||||
<td class="py-4">
|
<td class="py-4">
|
||||||
<div class="font-bold text-slate-800 text-sm">{{ $intern->name }}</div>
|
<div class="font-bold text-slate-800 text-sm cursor-pointer hover:underline hover:text-blue-600 flex items-center gap-1.5" onclick="openInternJournalModal({{ $intern->id }})">
|
||||||
|
<span>{{ $intern->name }}</span>
|
||||||
|
<span class="inline-flex px-1.5 py-0.5 rounded-md bg-blue-50 text-blue-600 text-[9px] font-extrabold uppercase border border-blue-100 tracking-wider">Defteri İncele</span>
|
||||||
|
</div>
|
||||||
<div class="text-xs text-slate-400 mt-0.5 flex flex-col gap-0.5">
|
<div class="text-xs text-slate-400 mt-0.5 flex flex-col gap-0.5">
|
||||||
<span><i class="uil uil-envelope mr-1"></i>{{ $intern->email }}</span>
|
<span><i class="uil uil-envelope mr-1"></i>{{ $intern->email }}</span>
|
||||||
@if($intern->phone)
|
@if($intern->phone)
|
||||||
@@ -43,6 +47,30 @@
|
|||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<!-- Defter İlerlemesi -->
|
||||||
|
<td class="py-4">
|
||||||
|
@if($intern->internship_total_days)
|
||||||
|
@php
|
||||||
|
$filledDays = $intern->journalEntries->filter(function($entry) {
|
||||||
|
return !empty(trim($entry->content));
|
||||||
|
})->count();
|
||||||
|
$progressPercent = ($filledDays / $intern->internship_total_days) * 100;
|
||||||
|
if ($progressPercent > 100) $progressPercent = 100;
|
||||||
|
@endphp
|
||||||
|
<div class="flex flex-col gap-1 max-w-[130px]">
|
||||||
|
<div class="flex justify-between text-[10px] font-extrabold">
|
||||||
|
<span class="text-slate-400">Doldurulan</span>
|
||||||
|
<span class="text-blue-600">{{ $filledDays }} / {{ $intern->internship_total_days }} Gün</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-slate-100 h-2 rounded-full overflow-hidden border border-slate-200/40">
|
||||||
|
<div class="bg-blue-600 h-full rounded-full transition-all duration-300" style="width: {{ $progressPercent }}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<span class="text-slate-300 italic text-xs">Belirlenmemiş</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<td class="py-4">
|
<td class="py-4">
|
||||||
<span class="inline-flex px-2.5 py-1 rounded-full text-[10px] font-extrabold uppercase tracking-wider
|
<span class="inline-flex px-2.5 py-1 rounded-full text-[10px] font-extrabold uppercase tracking-wider
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ Route::post('/stajyer/admin/giris', [\App\Http\Controllers\CareerController::cla
|
|||||||
Route::get('/stajyer/admin/panel', [\App\Http\Controllers\CareerController::class, 'internAdminDashboard'])->name('intern.admin.dashboard');
|
Route::get('/stajyer/admin/panel', [\App\Http\Controllers\CareerController::class, 'internAdminDashboard'])->name('intern.admin.dashboard');
|
||||||
Route::get('/stajyer/admin/journal-entry', [\App\Http\Controllers\CareerController::class, 'getJournalEntry'])->name('intern.admin.get-journal-entry');
|
Route::get('/stajyer/admin/journal-entry', [\App\Http\Controllers\CareerController::class, 'getJournalEntry'])->name('intern.admin.get-journal-entry');
|
||||||
Route::post('/stajyer/admin/toggle-approval', [\App\Http\Controllers\CareerController::class, 'toggleJournalApproval'])->name('intern.admin.toggle-journal-approval');
|
Route::post('/stajyer/admin/toggle-approval', [\App\Http\Controllers\CareerController::class, 'toggleJournalApproval'])->name('intern.admin.toggle-journal-approval');
|
||||||
|
Route::get('/stajyer/admin/journal-details', [\App\Http\Controllers\CareerController::class, 'getInternJournalDetails'])->name('intern.admin.get-journal-details');
|
||||||
|
Route::post('/stajyer/admin/toggle-notebook-signature', [\App\Http\Controllers\CareerController::class, 'toggleNotebookSignature'])->name('intern.admin.toggle-notebook-signature');
|
||||||
Route::post('/stajyer/admin/cikis', [\App\Http\Controllers\CareerController::class, 'internAdminLogout'])->name('intern.admin.logout');
|
Route::post('/stajyer/admin/cikis', [\App\Http\Controllers\CareerController::class, 'internAdminLogout'])->name('intern.admin.logout');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user