feat: add approval system for internship journal entries including database fields, admin toggle functionality, and rich-text rendering

This commit is contained in:
Ümit Tunç
2026-07-01 21:58:56 +03:00
parent d9c1c825d9
commit f84f78a138
8 changed files with 199 additions and 9 deletions
@@ -199,7 +199,13 @@ class InternApplicationResource extends Resource
$saved = $record->journalEntries()->get()->keyBy('day_number');
$html = '<div class="space-y-4" style="max-height: 400px; overflow-y: auto; padding-right: 10px; border: 1px solid #cbd5e1; border-radius: 8px; padding: 15px;">';
$html = '<style>
.rich-text-content p { margin-bottom: 8px; }
.rich-text-content ul { list-style-type: disc; padding-left: 20px; margin-bottom: 8px; }
.rich-text-content ol { list-style-type: decimal; padding-left: 20px; margin-bottom: 8px; }
.rich-text-content li { margin-bottom: 4px; }
</style>';
$html .= '<div class="space-y-4" style="max-height: 400px; overflow-y: auto; padding-right: 10px; border: 1px solid #cbd5e1; border-radius: 8px; padding: 15px;">';
foreach ($days as $d) {
$dayNum = $d['day_number'];
$dateF = $d['formatted_date'];
@@ -220,11 +226,114 @@ class InternApplicationResource extends Resource
$html .= ' <span style="margin-left: auto;">' . $dateF . '</span>';
}
$html .= ' </div>';
$html .= ' <div style="font-size:12px; color:#1e293b; white-space:pre-wrap; line-height:1.5;">' . ($content ? e($content) : '<em style="color:#94a3b8;">Rapor yazılmamış</em>') . '</div>';
$cleanContent = $content ? strip_tags($content, ['p', 'strong', 'ul', 'li', 'em', 'br', 'b', 'i', 'ol', 'span']) : '<em style="color:#94a3b8;">Rapor yazılmamış</em>';
$html .= ' <div class="rich-text-content" style="font-size:12px; color:#1e293b; line-height:1.5;">' . $cleanContent . '</div>';
if ($entry && trim($content) !== '') {
$supApproved = $entry->supervisor_approved;
$unitApproved = $entry->unit_approved;
$html .= ' <div style="display:flex; align-items:center; gap:12px; margin-top:12px; padding-top:10px; border-top:1px dashed #e2e8f0; font-size:11px;">';
// Supervisor approval
$supBg = $supApproved ? '#d1fae5' : '#f1f5f9';
$supColor = $supApproved ? '#065f46' : '#64748b';
$supText = $supApproved ? 'Sorumlu Onayladı' : 'Sorumlu Onayı Bekliyor';
$html .= ' <span id="sup-badge-' . $entry->id . '" style="background:' . $supBg . '; color:' . $supColor . '; padding: 2px 8px; border-radius: 4px; font-weight: 700;">' . $supText . '</span>';
// Unit approval
$unitBg = $unitApproved ? '#d1fae5' : '#f1f5f9';
$unitColor = $unitApproved ? '#065f46' : '#64748b';
$unitText = $unitApproved ? 'Birim Yetkilisi Onayladı' : 'Birim Yetkilisi Onayı Bekliyor';
$html .= ' <span id="unit-badge-' . $entry->id . '" style="background:' . $unitBg . '; color:' . $unitColor . '; padding: 2px 8px; border-radius: 4px; font-weight: 700;">' . $unitText . '</span>';
// Buttons
$supBtnText = $supApproved ? 'Onayı Kaldır' : 'Onayla';
$supBtnBg = $supApproved ? '#ef4444' : '#2563eb';
$html .= ' <button type="button" onclick="toggleApproval(' . $entry->id . ', \'supervisor\', this)" style="margin-left:auto; background:' . $supBtnBg . '; color:white; border:none; padding:4px 10px; border-radius:6px; font-weight:bold; cursor:pointer; font-size:10px;">Sorumlu ' . $supBtnText . '</button>';
$unitBtnText = $unitApproved ? 'Onayı Kaldır' : 'Onayla';
$unitBtnBg = $unitApproved ? '#ef4444' : '#2563eb';
$html .= ' <button type="button" onclick="toggleApproval(' . $entry->id . ', \'unit\', this)" style="background:' . $unitBtnBg . '; color:white; border:none; padding:4px 10px; border-radius:6px; font-weight:bold; cursor:pointer; font-size:10px;">Yetkili ' . $unitBtnText . '</button>';
$html .= ' </div>';
}
$html .= '</div>';
}
$html .= '</div>';
// JS handler
$html .= '
<script>
if (typeof window.toggleApproval !== "function") {
window.toggleApproval = function(entryId, type, btn) {
btn.disabled = true;
btn.style.opacity = "0.5";
fetch("' . route('intern.admin.toggle-journal-approval') . '", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": "' . csrf_token() . '"
},
body: JSON.stringify({
entry_id: entryId,
type: type
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (type === "supervisor") {
const badge = document.getElementById("sup-badge-" + entryId);
if (data.status) {
badge.style.background = "#d1fae5";
badge.style.color = "#065f46";
badge.textContent = "Sorumlu Onayladı";
btn.textContent = "Sorumlu Onayı Kaldır";
btn.style.background = "#ef4444";
} else {
badge.style.background = "#f1f5f9";
badge.style.color = "#64748b";
badge.textContent = "Sorumlu Onayı Bekliyor";
btn.textContent = "Sorumlu Onayla";
btn.style.background = "#2563eb";
}
} else {
const badge = document.getElementById("unit-badge-" + entryId);
if (data.status) {
badge.style.background = "#d1fae5";
badge.style.color = "#065f46";
badge.textContent = "Birim Yetkilisi Onayladı";
btn.textContent = "Yetkili Onayı Kaldır";
btn.style.background = "#ef4444";
} else {
badge.style.background = "#f1f5f9";
badge.style.color = "#64748b";
badge.textContent = "Birim Yetkilisi Onayı Bekliyor";
btn.textContent = "Yetkili Onayla";
btn.style.background = "#2563eb";
}
}
} else {
alert(data.message || "Bir hata oluştu.");
}
})
.catch(err => {
console.error(err);
alert("Bağlantı hatası oluştu.");
})
.finally(() => {
btn.disabled = false;
btn.style.opacity = "1";
});
};
}
</script>
';
// Add preview buttons
$html .= '<div style="margin-top: 15px; display: flex; gap: 10px;">';
$html .= ' <a href="' . route('intern.print-journal') . '?size=a4&intern_id=' . $record->id . '" target="_blank" style="display:inline-flex; align-items:center; justify-content:center; padding: 8px 16px; background:#2563eb; color:white; border-radius:8px; font-weight:bold; font-size:12px; text-decoration:none; box-shadow: 0 1px 3px rgba(37, 99, 235, 0.2);">A4 Defteri Önizle / Yazdır</a>';
+32
View File
@@ -529,5 +529,37 @@ class CareerController extends Controller
'size' => $size,
]);
}
public function toggleJournalApproval(Request $request)
{
if (!auth()->check()) {
return response()->json(['success' => false, 'message' => 'Yetkisiz işlem.'], 403);
}
$request->validate([
'entry_id' => 'required|integer|exists:internship_journal_entries,id',
'type' => 'required|string|in:supervisor,unit',
]);
$entry = \App\Models\InternshipJournalEntry::findOrFail($request->entry_id);
if ($request->type === 'supervisor') {
$entry->supervisor_approved = !$entry->supervisor_approved;
$entry->save();
return response()->json([
'success' => true,
'status' => $entry->supervisor_approved,
'message' => 'Staj sorumlusu onayı güncellendi.'
]);
} else {
$entry->unit_approved = !$entry->unit_approved;
$entry->save();
return response()->json([
'success' => true,
'status' => $entry->unit_approved,
'message' => 'Birim yetkilisi onayı güncellendi.'
]);
}
}
}
+4
View File
@@ -15,10 +15,14 @@ class InternshipJournalEntry extends Model
'date',
'content',
'is_retroactive',
'supervisor_approved',
'unit_approved',
];
protected $casts = [
'is_retroactive' => 'boolean',
'supervisor_approved' => 'boolean',
'unit_approved' => 'boolean',
];
/**