feat: implement internship journal entry system with printable reports and approval workflow

This commit is contained in:
Ümit Tunç
2026-07-01 17:16:41 +03:00
parent 5b46b4ac9e
commit 4c5cf1d355
10 changed files with 984 additions and 262 deletions
@@ -0,0 +1,445 @@
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Staj Defteri - {{ $intern->name }}</title>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Outfit', 'Segoe UI', sans-serif;
color: #1e293b;
background-color: #f8fafc;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* Page setup depending on chosen size */
@page {
@if($size === 'a5')
size: A5 portrait;
margin: 10mm;
@else
size: A4 portrait;
margin: 15mm;
@endif
}
.notebook-page {
background-color: #ffffff;
border: 1px solid #e2e8f0;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
page-break-after: always;
break-after: page;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
@if($size === 'a5')
width: 148mm;
height: 210mm;
padding: 12mm;
font-size: 11px;
@else
width: 210mm;
height: 297mm;
padding: 20mm;
font-size: 13px;
@endif
margin: 20px auto;
}
/* Adjustments for actual print mode */
@media print {
body {
background-color: #ffffff;
}
.notebook-page {
border: none;
box-shadow: none;
margin: 0;
width: 100%;
height: 100%;
}
.no-print {
display: none !important;
}
}
/* Top control bar */
.control-bar {
background: #ffffff;
border-bottom: 1px solid #e2e8f0;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 4px rgba(0,0,0,0.02);
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
border-radius: 10px;
font-size: 13px;
font-weight: 700;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
}
.btn-primary {
background-color: #2563eb;
color: #ffffff;
}
.btn-primary:hover {
background-color: #1d4ed8;
}
.btn-secondary {
background-color: #f1f5f9;
color: #475569;
border-color: #cbd5e1;
}
.btn-secondary:hover {
background-color: #e2e8f0;
}
.control-title {
font-weight: 800;
font-size: 16px;
color: #0f172a;
}
.control-actions {
display: flex;
gap: 10px;
}
/* Page Layout Styles */
.header-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 15px;
}
.header-table td {
border: 1px solid #cbd5e1;
padding: 8px 12px;
vertical-align: middle;
}
.logo-cell {
width: 75px;
text-align: center;
}
.logo-img {
max-height: 40px;
max-width: 100%;
}
.company-title {
font-weight: 800;
font-size: 12px;
color: #0f172a;
line-height: 1.3;
text-transform: uppercase;
}
.doc-title {
font-weight: 800;
color: #2563eb;
text-align: right;
font-size: 14px;
}
.meta-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.meta-table td {
border: 1px solid #cbd5e1;
padding: 6px 10px;
font-size: 11px;
}
.meta-label {
font-weight: 700;
color: #475569;
background-color: #f8fafc;
width: 25%;
}
.meta-value {
font-weight: 600;
color: #0f172a;
}
.day-banner {
background: linear-gradient(135deg, #eff6ff, #dbeafe);
border: 1px solid #bfdbfe;
border-radius: 8px;
padding: 10px 15px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.day-badge {
font-weight: 800;
color: #1d4ed8;
font-size: 14px;
}
.day-date {
font-weight: 700;
color: #475569;
font-size: 12px;
}
.content-area {
flex-grow: 1;
border: 1px solid #cbd5e1;
border-radius: 8px;
padding: 20px;
background: #ffffff;
margin-bottom: 20px;
min-height: 250px;
white-space: pre-wrap;
line-height: 1.6;
color: #334155;
font-size: 13px;
}
.empty-content {
color: #94a3b8;
font-style: italic;
text-align: center;
padding-top: 50px;
}
.footer-section {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: auto;
border-t: 1px solid #e2e8f0;
padding-top: 15px;
}
.qr-block {
display: flex;
align-items: center;
gap: 10px;
}
.qr-img {
width: 65px;
height: 65px;
border: 1px solid #e2e8f0;
padding: 2px;
border-radius: 4px;
}
.qr-text {
font-size: 9px;
color: #64748b;
max-width: 120px;
line-height: 1.3;
font-weight: 500;
}
.signatures-container {
display: flex;
gap: 20px;
}
.signature-box {
border: 1px solid #cbd5e1;
border-radius: 6px;
padding: 8px 12px;
width: 150px;
text-align: center;
font-size: 9px;
background: #f8fafc;
min-height: 70px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.sig-title {
font-weight: 700;
color: #475569;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 4px;
margin-bottom: 4px;
text-transform: uppercase;
}
.sig-status {
font-weight: 800;
color: #059669;
margin: 6px 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.sig-status.waiting {
color: #d97706;
}
.sig-badge {
background-color: #d1fae5;
color: #065f46;
padding: 1px 6px;
border-radius: 4px;
font-size: 7px;
font-weight: 800;
display: inline-block;
margin-top: 2px;
}
.sig-badge.waiting {
background-color: #fef3c7;
color: #92400e;
}
.sig-name {
font-weight: 700;
color: #1e293b;
}
</style>
</head>
<body>
<!-- Control Bar -->
<div class="control-bar no-print">
<div class="control-title">Staj Defteri Önizleme ({{ $size === 'a5' ? 'A5 Boyutu' : 'A4 Boyutu' }})</div>
<div class="control-actions">
<a href="{{ route('intern.dashboard') }}" class="btn btn-secondary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
<span>Panele Dön</span>
</a>
<div style="border-left: 1px solid #e2e8f0; margin: 0 5px; height: 35px;"></div>
<a href="?size=a4{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}" class="btn {{ $size === 'a4' ? 'btn-primary' : 'btn-secondary' }}">A4 Boyutu</a>
<a href="?size=a5{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}" class="btn {{ $size === 'a5' ? 'btn-primary' : 'btn-secondary' }}">A5 Boyutu</a>
<button onclick="window.print()" class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
<span>Yazdır / PDF Kaydet</span>
</button>
</div>
</div>
<!-- Render Each Day -->
@foreach($days as $day)
@php
$entry = $savedEntries->get($day['day_number']);
$dayNum = $day['day_number'];
$dayDate = $day['date'];
$dayDateFormatted = $day['formatted_date'];
// Build the verification QR code URL pointing to the verification page
$verifyUrl = route('internship.verify', $intern->certificate_code) . '?day=' . $dayNum;
$qrCodeUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=' . urlencode($verifyUrl);
@endphp
<div class="notebook-page">
<div>
<!-- Header Card -->
<table class="header-table">
<tr>
<td class="logo-cell">
<img src="{{ asset('assets/img/logo.png') }}" class="logo-img" alt="Logo" onerror="this.src='https://www.truncgil.com/assets/images/logo/logo.svg'">
</td>
<td>
<div class="company-title">TRUNÇGİL TEKNOLOJİ</div>
<div style="font-size: 8px; color: #64748b; margin-top: 2px;">Gaziantep Teknopark, Şahinbey / Gaziantep</div>
</td>
<td style="text-align: right;">
<div class="doc-title">STAJ GÜNLÜK RAPORU</div>
<div style="font-size: 8px; color: #64748b; margin-top: 2px;">Staj Defteri Sayfası</div>
</td>
</tr>
</table>
<!-- Student & Metadata Grid -->
<table class="meta-table">
<tr>
<td class="meta-label">Öğrenci Ad Soyad</td>
<td class="meta-value">{{ $intern->name }}</td>
<td class="meta-label">Staj Türü / Deposu</td>
<td class="meta-value">{{ $intern->github_repo ? 'GitHub Projeli Staj' : 'Genel Staj' }}</td>
</tr>
<tr>
<td class="meta-label">Staj Başlangıç</td>
<td class="meta-value">{{ \Carbon\Carbon::parse($intern->internship_start_date)->format('d.m.Y') }}</td>
<td class="meta-label">Toplam İş Günü</td>
<td class="meta-value">{{ $intern->internship_total_days }} Gün</td>
</tr>
</table>
<!-- Day Header Banner -->
<div class="day-banner">
<span class="day-badge">{{ $dayNum }}. Gün Raporu</span>
<span class="day-date"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block; vertical-align:middle; margin-right:4px;"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>{{ $dayDateFormatted }}</span>
</div>
<!-- Content Area -->
<div class="content-area">
@if($entry && trim($entry->content))
{!! nl2br(e($entry->content)) !!}
@else
<div class="empty-content">Bu gün için herhangi bir staj raporu girilmemiştir.</div>
@endif
</div>
</div>
<!-- Footer & Signatures Block -->
<div class="footer-section">
<!-- QR Code Block -->
<div class="qr-block">
<img src="{{ $qrCodeUrl }}" class="qr-img" alt="QR Code">
<div class="qr-text">
<strong>KOD DOĞRULAMA</strong><br>
Bu staj sayfası webden doğrulanabilir. Doğrulamak için QR kodu taratın.
</div>
</div>
<!-- Signatures -->
<div class="signatures-container">
<!-- Supervisor -->
<div class="signature-box">
<div class="sig-title">Staj Sorumlusu</div>
@if($intern->notebook_supervisor_signed)
<div class="sig-status">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color: #059669;"><polyline points="20 6 9 17 4 12"/></svg>
<span class="sig-badge">E-ONAYLI</span>
</div>
<div class="sig-name">{{ $intern->notebook_supervisor_name ?: 'Alperen Trunç' }}</div>
@else
<div class="sig-status waiting">
<span class="sig-badge waiting">İMZA BEKLENİYOR</span>
</div>
<div class="sig-name">&nbsp;</div>
@endif
</div>
<!-- Unit Officer -->
<div class="signature-box">
<div class="sig-title">Birim Yetkilisi</div>
@if($intern->notebook_unit_signed)
<div class="sig-status">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color: #059669;"><polyline points="20 6 9 17 4 12"/></svg>
<span class="sig-badge">E-ONAYLI</span>
</div>
<div class="sig-name">{{ $intern->notebook_unit_name ?: 'Yetkili Birim Amiri' }}</div>
@else
<div class="sig-status waiting">
<span class="sig-badge waiting">İMZA BEKLENİYOR</span>
</div>
<div class="sig-name">&nbsp;</div>
@endif
</div>
</div>
</div>
</div>
@endforeach
</body>
</html>