feat: implement internship journal entry system with printable reports and approval workflow
This commit is contained in:
@@ -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"> </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"> </div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user