Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c5cf1d355 | |||
| 5b46b4ac9e |
@@ -107,13 +107,16 @@ class InternApplicationResource extends Resource
|
||||
|
||||
TextInput::make('username')
|
||||
->label('Kullanıcı Adı')
|
||||
->unique(ignoreRecord: true)
|
||||
->nullable(),
|
||||
->default(fn ($record) => $record?->email)
|
||||
->disabled()
|
||||
->dehydrated()
|
||||
->autocomplete('new-username'),
|
||||
|
||||
TextInput::make('password')
|
||||
->label('Şifre')
|
||||
->password()
|
||||
->revealable()
|
||||
->autocomplete('new-password')
|
||||
->formatStateUsing(fn () => null)
|
||||
->dehydrateStateUsing(fn ($state) => filled($state) ? Hash::make($state) : null)
|
||||
->dehydrated(fn ($state) => filled($state))
|
||||
@@ -184,6 +187,66 @@ class InternApplicationResource extends Resource
|
||||
->columnSpanFull()
|
||||
]),
|
||||
|
||||
Tab::make('Staj Defteri & Onay')
|
||||
->icon('heroicon-o-book-open')
|
||||
->schema([
|
||||
\Filament\Forms\Components\Placeholder::make('notebook_view')
|
||||
->label('Doldurulan Staj Defteri')
|
||||
->content(function ($record) {
|
||||
if (!$record) return 'Henüz başvuru bulunmamaktadır.';
|
||||
$days = \App\Http\Controllers\CareerController::getInternshipDates($record->internship_start_date, $record->internship_total_days);
|
||||
if (empty($days)) return 'Staj başlangıç tarihi veya süresi girilmemiş.';
|
||||
|
||||
$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;">';
|
||||
foreach ($days as $d) {
|
||||
$dayNum = $d['day_number'];
|
||||
$dateF = $d['formatted_date'];
|
||||
$content = isset($saved[$dayNum]) ? $saved[$dayNum]->content : '';
|
||||
|
||||
$html .= '<div style="margin-bottom: 12px; padding: 12px; border: 1px solid #e2e8f0; border-radius: 8px; background: #f8fafc;">';
|
||||
$html .= ' <div style="display:flex; justify-content:between; font-size:11px; font-weight:700; color:#475569; border-bottom:1px solid #e2e8f0; padding-bottom:6px; margin-bottom:8px;">';
|
||||
$html .= ' <span style="font-weight: 800; color: #2563eb;">' . $dayNum . '. Gün Raporu</span>';
|
||||
$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>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
|
||||
// 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>';
|
||||
$html .= ' <a href="' . route('intern.print-journal') . '?size=a5&intern_id=' . $record->id . '" target="_blank" style="display:inline-flex; align-items:center; justify-content:center; padding: 8px 16px; background:#4b5563; color:white; border-radius:8px; font-weight:bold; font-size:12px; text-decoration:none; box-shadow: 0 1px 3px rgba(75, 85, 99, 0.2);">A5 Defteri Önizle / Yazdır</a>';
|
||||
$html .= '</div>';
|
||||
|
||||
return new \Illuminate\Support\HtmlString($html);
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
\Filament\Forms\Components\Section::make('Onay ve İmza Bilgileri')
|
||||
->schema([
|
||||
\Filament\Forms\Components\Toggle::make('notebook_supervisor_signed')
|
||||
->label('Staj Sorumlusu İmzala / Onayla')
|
||||
->live(),
|
||||
TextInput::make('notebook_supervisor_name')
|
||||
->label('Staj Sorumlusu Adı / Ünvanı')
|
||||
->placeholder('Örn: Alperen Trunç')
|
||||
->default('Alperen Trunç'),
|
||||
\Filament\Forms\Components\Toggle::make('notebook_unit_signed')
|
||||
->label('İlgili Birim Yetkilisi İmzala / Onayla')
|
||||
->live(),
|
||||
TextInput::make('notebook_unit_name')
|
||||
->label('Birim Yetkilisi Adı / Ünvanı')
|
||||
->placeholder('Örn: Yetkili Birim Amiri')
|
||||
->default('Yetkili Birim Amiri'),
|
||||
\Filament\Forms\Components\Toggle::make('notebook_approved')
|
||||
->label('Staj Defterini Genel Olarak Onayla')
|
||||
->columnSpanFull(),
|
||||
])->columns(2),
|
||||
]),
|
||||
|
||||
Tab::make('Sertifika & Transkript')
|
||||
->icon('heroicon-o-academic-cap')
|
||||
->schema([
|
||||
|
||||
@@ -122,8 +122,13 @@ class CareerController extends Controller
|
||||
}
|
||||
|
||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
|
||||
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
|
||||
|
||||
return view('front.career.intern_dashboard', [
|
||||
'intern' => $intern,
|
||||
'days' => $days,
|
||||
'savedEntries' => $savedEntries,
|
||||
'meta' => [
|
||||
'title' => 'Stajyer Paneli',
|
||||
'description' => 'Belgelerinizi buradan indirebilirsiniz.',
|
||||
@@ -327,8 +332,13 @@ class CareerController extends Controller
|
||||
->where('type', 'internship')
|
||||
->firstOrFail();
|
||||
|
||||
$days = self::getInternshipDates($application->internship_start_date, $application->internship_total_days);
|
||||
$savedEntries = $application->journalEntries()->get()->keyBy('day_number');
|
||||
|
||||
return view('front.career.verify', [
|
||||
'application' => $application,
|
||||
'days' => $days,
|
||||
'savedEntries' => $savedEntries,
|
||||
'meta' => [
|
||||
'title' => 'Staj Bitirme Sertifikası Doğrulama - ' . $application->name,
|
||||
'description' => $application->name . ' isimli stajyerimizin staj bitirme sertifikası ve performans raporu doğrulama sayfası.',
|
||||
@@ -418,5 +428,95 @@ class CareerController extends Controller
|
||||
auth()->logout();
|
||||
return redirect()->route('intern.admin.login')->with('success', 'Yönetici oturumu sonlandırıldı.');
|
||||
}
|
||||
|
||||
public static function getInternshipDates($startDate, $totalDays)
|
||||
{
|
||||
if (!$startDate || !$totalDays || $totalDays <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dates = [];
|
||||
$temp = \Carbon\Carbon::parse($startDate);
|
||||
$daysToAdd = intval($totalDays);
|
||||
$count = 0;
|
||||
|
||||
while ($count < $daysToAdd) {
|
||||
if ($temp->isWeekend()) {
|
||||
$temp->addDay();
|
||||
continue;
|
||||
}
|
||||
$dates[] = [
|
||||
'day_number' => $count + 1,
|
||||
'date' => $temp->format('Y-m-d'),
|
||||
'formatted_date' => $temp->format('d.m.Y'),
|
||||
];
|
||||
$temp->addDay();
|
||||
$count++;
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
public function saveJournalEntry(Request $request)
|
||||
{
|
||||
if (!session()->has('intern_id')) {
|
||||
return response()->json(['success' => false, 'message' => 'Lütfen giriş yapın.'], 401);
|
||||
}
|
||||
|
||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||
|
||||
$request->validate([
|
||||
'day_number' => 'required|integer|min:1',
|
||||
'date' => 'required|date',
|
||||
'content' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$dateObj = \Carbon\Carbon::parse($request->date);
|
||||
if ($dateObj->isWeekend()) {
|
||||
return response()->json(['success' => false, 'message' => 'Hafta sonu günlerine staj günlüğü girilemez.'], 422);
|
||||
}
|
||||
|
||||
$entry = \App\Models\InternshipJournalEntry::updateOrCreate([
|
||||
'career_application_id' => $intern->id,
|
||||
'day_number' => $request->day_number,
|
||||
'date' => $request->date,
|
||||
], [
|
||||
'content' => $request->content,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => $request->day_number . '. Gün kaydı başarıyla kaydedildi.',
|
||||
'entry' => $entry
|
||||
]);
|
||||
}
|
||||
|
||||
public function printJournal(Request $request)
|
||||
{
|
||||
if (request()->has('intern_id') && auth()->check() && auth()->user()->hasRole('super_admin')) {
|
||||
$internId = request('intern_id');
|
||||
} else {
|
||||
if (!session()->has('intern_id')) {
|
||||
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.');
|
||||
}
|
||||
$internId = session('intern_id');
|
||||
}
|
||||
|
||||
$intern = CareerApplication::findOrFail($internId);
|
||||
$size = $request->query('size', 'a4');
|
||||
if (!in_array($size, ['a4', 'a5'])) {
|
||||
$size = 'a4';
|
||||
}
|
||||
|
||||
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
|
||||
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
|
||||
|
||||
return view('front.career.print', [
|
||||
'intern' => $intern,
|
||||
'days' => $days,
|
||||
'savedEntries' => $savedEntries,
|
||||
'size' => $size,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,16 +32,32 @@ class CareerApplication extends Model
|
||||
'github_repo',
|
||||
'certificate_code',
|
||||
'transcript_markdown',
|
||||
'notebook_supervisor_signed',
|
||||
'notebook_supervisor_name',
|
||||
'notebook_unit_signed',
|
||||
'notebook_unit_name',
|
||||
'notebook_approved',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the journal entries for the internship application.
|
||||
*/
|
||||
public function journalEntries()
|
||||
{
|
||||
return $this->hasMany(InternshipJournalEntry::class);
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::saving(function ($model) {
|
||||
if ($model->type === 'internship' && !$model->certificate_code) {
|
||||
do {
|
||||
$code = 'TRN-' . date('Y') . '-' . strtoupper(\Illuminate\Support\Str::random(4)) . '-' . strtoupper(\Illuminate\Support\Str::random(4));
|
||||
} while (static::where('certificate_code', $code)->exists());
|
||||
$model->certificate_code = $code;
|
||||
if ($model->type === 'internship') {
|
||||
$model->username = $model->email;
|
||||
if (!$model->certificate_code) {
|
||||
do {
|
||||
$code = 'TRN-' . date('Y') . '-' . strtoupper(\Illuminate\Support\Str::random(4)) . '-' . strtoupper(\Illuminate\Support\Str::random(4));
|
||||
} while (static::where('certificate_code', $code)->exists());
|
||||
$model->certificate_code = $code;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -55,6 +71,9 @@ class CareerApplication extends Model
|
||||
{
|
||||
return [
|
||||
'password' => 'hashed',
|
||||
'notebook_supervisor_signed' => 'boolean',
|
||||
'notebook_unit_signed' => 'boolean',
|
||||
'notebook_approved' => 'boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class InternshipJournalEntry extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'career_application_id',
|
||||
'day_number',
|
||||
'date',
|
||||
'content',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the career application that owns this journal entry.
|
||||
*/
|
||||
public function careerApplication()
|
||||
{
|
||||
return $this->belongsTo(CareerApplication::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('internship_journal_entries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('career_application_id')
|
||||
->constrained('career_applications')
|
||||
->onDelete('cascade');
|
||||
$table->integer('day_number');
|
||||
$table->date('date');
|
||||
$table->text('content')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['career_application_id', 'day_number'], 'journal_app_day_unique');
|
||||
$table->unique(['career_application_id', 'date'], 'journal_app_date_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('internship_journal_entries');
|
||||
}
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->boolean('notebook_supervisor_signed')->default(false);
|
||||
$table->string('notebook_supervisor_name')->nullable();
|
||||
$table->boolean('notebook_unit_signed')->default(false);
|
||||
$table->string('notebook_unit_name')->nullable();
|
||||
$table->boolean('notebook_approved')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'notebook_supervisor_signed',
|
||||
'notebook_supervisor_name',
|
||||
'notebook_unit_signed',
|
||||
'notebook_unit_name',
|
||||
'notebook_approved'
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -367,14 +367,14 @@
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- Menu Item 3: Staj Günlüğü -->
|
||||
<!-- Menu Item 3: Staj Defteri -->
|
||||
<button type="button" role="tab" aria-selected="false" data-tab-target="journal" class="tab-btn w-full flex items-center gap-4 p-3 rounded-2xl text-left transition-all border border-transparent">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-50 text-slate-500 flex items-center justify-center text-xl flex-shrink-0 tab-icon">
|
||||
<i class="uil uil-github"></i>
|
||||
<i class="uil uil-book-open"></i>
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<span class="block text-sm font-extrabold text-slate-800">Staj Günlüğü</span>
|
||||
<span class="block text-xs text-slate-400 font-semibold mt-0.5">GitHub commit geçmişi</span>
|
||||
<span class="block text-sm font-extrabold text-slate-800">Staj Defteri Doldur</span>
|
||||
<span class="block text-xs text-slate-400 font-semibold mt-0.5">Günlük çalışma defteri</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -495,27 +495,15 @@
|
||||
|
||||
<div class="flex items-center gap-2 w-full sm:w-auto">
|
||||
@if($intern->to_be_signed_internship_form_path)
|
||||
<a href="{{ Storage::disk('public')->url($intern->to_be_signed_internship_form_path) }}" target="_blank" class="px-4 py-2.5 bg-slate-850 hover:bg-slate-900 text-white font-bold rounded-xl text-xs transition-all flex items-center justify-center gap-1.5 shadow-sm">
|
||||
<a href="{{ Storage::disk('public')->url($intern->to_be_signed_internship_form_path) }}" target="_blank" class="px-4 py-2.5 bg-slate-800 hover:bg-slate-900 text-white font-bold rounded-xl text-xs transition-all flex items-center justify-center gap-1.5 shadow-sm">
|
||||
<i class="uil uil-arrow-down-tray"></i>
|
||||
<span>Görüntüle</span>
|
||||
</a>
|
||||
@if(!$intern->signed_internship_form_path)
|
||||
<button type="button" onclick="document.getElementById('upload-form-wrapper').classList.toggle('hidden')" class="px-4 py-2.5 bg-blue-50 hover:bg-blue-100 text-blue-600 font-bold rounded-xl text-xs transition-all flex items-center justify-center gap-1.5">
|
||||
<i class="uil uil-edit"></i>
|
||||
<span>Değiştir</span>
|
||||
</button>
|
||||
@endif
|
||||
@else
|
||||
<button type="button" onclick="document.getElementById('upload-form-wrapper').classList.toggle('hidden')" class="w-full sm:w-auto px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-sm transition-all flex items-center justify-center gap-2 shadow-md shadow-blue-500/10">
|
||||
<i class="uil uil-plus"></i>
|
||||
<span>Yükle</span>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(!$intern->signed_internship_form_path)
|
||||
<div id="upload-form-wrapper" class="{{ $intern->to_be_signed_internship_form_path ? 'hidden' : '' }} mt-4 border-t border-slate-100 pt-4">
|
||||
<div id="upload-form-wrapper" class="mt-4 border-t border-slate-100 pt-4">
|
||||
<form action="{{ route('intern.upload-form') }}" method="POST" enctype="multipart/form-data" class="space-y-4">
|
||||
@csrf
|
||||
|
||||
@@ -576,11 +564,11 @@
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="internship_start_date" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-1.5">Staj Başlangıç Tarihi</label>
|
||||
<input type="date" name="internship_start_date" id="internship_start_date" onchange="calculateEndDateFrontend()" oninput="calculateEndDateFrontend()" required value="{{ $intern->internship_start_date }}" class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" />
|
||||
<input type="date" name="internship_start_date" id="internship_start_date" onchange="calculateEndDateFrontend()" oninput="calculateEndDateFrontend()" required value="{{ $intern->internship_start_date }}" @if($intern->status !== 'pending') disabled @endif class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all disabled:bg-slate-50 disabled:text-slate-500 disabled:cursor-not-allowed" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="internship_total_days" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-1.5">Staj Süresi (İş Günü)</label>
|
||||
<input type="number" name="internship_total_days" id="internship_total_days" onchange="calculateEndDateFrontend()" oninput="calculateEndDateFrontend()" min="1" required value="{{ $intern->internship_total_days }}" placeholder="Örn: 20" class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" />
|
||||
<input type="number" name="internship_total_days" id="internship_total_days" onchange="calculateEndDateFrontend()" oninput="calculateEndDateFrontend()" min="1" required value="{{ $intern->internship_total_days }}" placeholder="Örn: 20" @if($intern->status !== 'pending') disabled @endif class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all disabled:bg-slate-50 disabled:text-slate-500 disabled:cursor-not-allowed" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -589,6 +577,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($intern->status === 'pending')
|
||||
<div class="relative border-2 border-dashed border-slate-200 hover:border-blue-400 rounded-2xl p-6 transition-all bg-white flex flex-col items-center justify-center text-center cursor-pointer group">
|
||||
<input type="file" name="internship_form" id="internship_form" accept=".pdf,.docx,.jpg,.png,.jpeg" onchange="handleFileSelected(this)" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10" />
|
||||
<i class="uil uil-cloud-upload text-4xl text-slate-400 group-hover:text-blue-500 transition-colors mb-2"></i>
|
||||
@@ -603,9 +592,13 @@
|
||||
<span>Dosyayı Kaydet</span>
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
<div class="p-4 bg-slate-50 border border-slate-100 rounded-2xl text-center text-xs text-slate-500 font-semibold">
|
||||
<i class="uil uil-lock-alt text-base mr-1"></i> Başvurunuz bekleme aşamasında (beklemede) olmadığı için staj bilgilerinizi ve dosyanızı güncelleyemezsiniz.
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Signed Internship Form Card -->
|
||||
@@ -626,9 +619,7 @@
|
||||
|
||||
@if($intern->signed_internship_form_path)
|
||||
<a href="{{ Storage::disk('public')->url($intern->signed_internship_form_path) }}" target="_blank" class="w-full sm:w-auto px-5 py-2.5 bg-[#e31e24] hover:bg-[#c4191f] text-white font-bold rounded-xl text-sm transition-all flex items-center justify-center gap-2 shadow-md shadow-red-500/10">
|
||||
<i class="uil uil-arrow-down-tray"></i>
|
||||
<span>İndir</span>
|
||||
</a>
|
||||
</div>
|
||||
@else
|
||||
<button disabled class="w-full sm:w-auto px-5 py-2.5 bg-slate-100 text-slate-400 font-bold rounded-xl text-sm cursor-not-allowed flex items-center justify-center gap-2 border border-slate-200/50">
|
||||
<i class="uil uil-hourglass"></i>
|
||||
@@ -641,20 +632,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panel 3: Staj Günlüğü -->
|
||||
<!-- Panel 3: Staj Defteri -->
|
||||
<div id="journal-panel" class="tab-panel hidden space-y-6">
|
||||
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50">
|
||||
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6 pb-4 border-b border-slate-100">
|
||||
<div>
|
||||
<h3 class="font-bold text-slate-800 text-lg">Staj Günlüğü</h3>
|
||||
<p class="text-xs text-slate-400 mt-0.5">Public GitHub deponuzdaki commit geçmişi günlük olarak listelenir.</p>
|
||||
<h3 class="font-bold text-slate-800 text-lg">Staj Defteri Modülü</h3>
|
||||
<p class="text-xs text-slate-400 mt-0.5">Her staj günü için yaptığınız çalışmaları yazıp kaydedin. Dilerseniz GitHub commitlerini tek tıkla çekebilirsiniz.</p>
|
||||
</div>
|
||||
<div class="flex gap-2 w-full sm:w-auto">
|
||||
<button type="button" onclick="openPrintModal()" class="w-full sm:w-auto px-4 py-2.5 bg-green-600 hover:bg-green-700 text-white font-bold rounded-xl text-xs transition-all flex items-center justify-center gap-2 shadow-md shadow-green-500/10">
|
||||
<i class="uil uil-print text-sm"></i>
|
||||
<span>Defteri Yazdır / İndir</span>
|
||||
</button>
|
||||
</div>
|
||||
@if($intern->github_repo)
|
||||
<a href="{{ route('intern.download-journal') }}" class="w-full sm:w-auto px-4 py-2.5 bg-green-600 hover:bg-green-700 text-white font-bold rounded-xl text-xs transition-all flex items-center justify-center gap-2 shadow-md shadow-green-500/10">
|
||||
<i class="uil uil-arrow-down-tray text-sm"></i>
|
||||
<span>Günlüğü İndir (.md)</span>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- GitHub Repository URL Configuration -->
|
||||
@@ -669,61 +661,82 @@
|
||||
{{ $intern->github_repo ? 'Güncelle' : 'Kaydet' }}
|
||||
</button>
|
||||
</div>
|
||||
<span class="block text-xs text-slate-400 mt-2"><i class="uil uil-info-circle text-blue-600"></i> Deponuzun <strong>public (herkese açık)</strong> olması ve en az bir commit içermesi gerekmektedir.</span>
|
||||
<span class="block text-xs text-slate-400 mt-2"><i class="uil uil-info-circle text-blue-600"></i> Deponuzun <strong>public (herkese açık)</strong> olması gerekmektedir.</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Commits timeline / history load wrapper -->
|
||||
<div id="commits-loader" class="hidden py-8 flex flex-col items-center justify-center">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mb-2"></div>
|
||||
<span class="text-xs text-slate-400 font-semibold">GitHub'dan commitler yükleniyor...</span>
|
||||
@if(empty($intern->internship_start_date) || empty($intern->internship_total_days))
|
||||
<div class="p-6 rounded-2xl border border-amber-100 bg-amber-50 text-amber-800 text-sm font-medium text-center">
|
||||
Staj başlangıç tarihi veya staj gün sayısı girilmemiş. Lütfen staj evraklarınızı yükleyip başlangıç tarihlerinizi tanımlayın.
|
||||
</div>
|
||||
@else
|
||||
<!-- Main Workspace Grid -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||
|
||||
<div id="commits-error" class="hidden p-4 rounded-xl bg-red-50 border border-red-200 text-[#e31e24] text-xs font-semibold">
|
||||
</div>
|
||||
|
||||
<div id="commits-empty" class="hidden p-6 rounded-2xl border border-slate-100 text-center text-slate-400 italic text-sm">
|
||||
Henüz hiç commit bulunamadı veya deponuz tanımlanmadı. Lütfen deponuzu tanımlayın ve commitlerinizi gönderin.
|
||||
</div>
|
||||
|
||||
<!-- Day Paginator / Navbar -->
|
||||
<div id="journal-carousel-nav" class="hidden flex items-center justify-between gap-4 p-3 bg-slate-50/80 border border-slate-100 rounded-2xl mb-6 select-none">
|
||||
<button type="button" id="prev-day-btn" onclick="navigateDay(-1)" class="w-10 h-10 rounded-xl bg-white hover:bg-blue-50 text-slate-600 hover:text-blue-600 border border-slate-200/60 hover:border-blue-100 flex items-center justify-center transition-all shadow-sm">
|
||||
<i class="uil uil-angle-left text-xl"></i>
|
||||
</button>
|
||||
|
||||
<div class="flex-grow overflow-x-auto no-scrollbar py-1">
|
||||
<div id="day-tabs-container" class="flex gap-2 justify-start sm:justify-center min-w-max px-2">
|
||||
<!-- Day tabs rendered dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" id="next-day-btn" onclick="navigateDay(1)" class="w-10 h-10 rounded-xl bg-white hover:bg-blue-50 text-slate-600 hover:text-blue-600 border border-slate-200/60 hover:border-blue-100 flex items-center justify-center transition-all shadow-sm">
|
||||
<i class="uil uil-angle-right text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Day Content Timeline Container (Carousel View) -->
|
||||
<div id="commits-timeline-container" class="hidden">
|
||||
<div id="active-day-card" class="bg-white border border-slate-100/80 rounded-2xl p-6 shadow-sm transition-all duration-300 transform">
|
||||
<!-- Active Day Details (Date / Day Title) -->
|
||||
<div class="flex items-center justify-between mb-8 pb-4 border-b border-slate-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<span id="active-day-title" class="text-base font-extrabold text-blue-600 bg-blue-50 px-4 py-1.5 rounded-full">1. Gün</span>
|
||||
<span id="active-day-date" class="text-sm font-semibold text-slate-400"><i class="uil uil-calendar-alt mr-1"></i> 29.05.2026</span>
|
||||
</div>
|
||||
<span id="active-day-commit-count" class="text-xs font-bold text-slate-400 bg-slate-50 border border-slate-100 px-2.5 py-1 rounded-lg">3 Commit</span>
|
||||
<!-- Left: Days List Navigation -->
|
||||
<div class="lg:col-span-4 space-y-2 max-h-[500px] overflow-y-auto pr-2 no-scrollbar">
|
||||
@foreach($days as $idx => $d)
|
||||
@php
|
||||
$dayNum = $d['day_number'];
|
||||
$dayDate = $d['date'];
|
||||
$dayDateFormatted = $d['formatted_date'];
|
||||
$hasSaved = isset($savedEntries[$dayNum]) && trim($savedEntries[$dayNum]->content) !== '';
|
||||
@endphp
|
||||
<button type="button"
|
||||
onclick="selectNotebookDay({{ $idx }})"
|
||||
id="btn-day-{{ $idx }}"
|
||||
data-day-num="{{ $dayNum }}"
|
||||
data-date="{{ $dayDate }}"
|
||||
data-formatted-date="{{ $dayDateFormatted }}"
|
||||
data-saved-content="{{ $savedEntries[$dayNum]->content ?? '' }}"
|
||||
class="notebook-day-btn w-full flex items-center justify-between p-3 rounded-2xl border text-left transition-all @if($idx === 0) border-blue-200 bg-blue-50/50 @else border-slate-100 bg-white hover:bg-slate-50 @endif">
|
||||
<div>
|
||||
<span class="block text-xs font-extrabold @if($idx === 0) text-blue-700 @else text-slate-800 @endif">{{ $dayNum }}. Gün</span>
|
||||
<span class="block text-[10px] text-slate-400 mt-0.5">{{ $dayDateFormatted }}</span>
|
||||
</div>
|
||||
<span id="badge-day-{{ $idx }}" class="inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase @if($hasSaved) bg-green-50 text-green-700 border border-green-200 @else bg-slate-50 text-slate-400 border border-slate-200 @endif">
|
||||
{{ $hasSaved ? 'DOLU' : 'BOŞ' }}
|
||||
</span>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- Timeline items list -->
|
||||
<div class="relative pl-6 sm:pl-8 before:content-[''] before:absolute before:left-[11px] sm:before:left-[15px] before:top-2 before:bottom-2 before:w-[2px] before:bg-slate-100">
|
||||
<div id="timeline-events" class="space-y-6">
|
||||
<!-- Event items rendered dynamically -->
|
||||
</div>
|
||||
<!-- Right: Workspace Editor -->
|
||||
<div class="lg:col-span-8 space-y-4">
|
||||
<div class="bg-white border border-slate-100 rounded-2xl p-5 shadow-sm">
|
||||
<div class="flex items-center justify-between pb-3 border-b border-slate-100 mb-4">
|
||||
<div>
|
||||
<span id="editor-day-title" class="text-sm font-extrabold text-blue-600 bg-blue-50 px-3 py-1 rounded-full">1. Gün</span>
|
||||
<span id="editor-day-date" class="text-xs font-semibold text-slate-400 ml-2"></span>
|
||||
</div>
|
||||
|
||||
@if($intern->github_repo)
|
||||
<button type="button" onclick="fillFromGithub()" class="px-3 py-1.5 bg-slate-800 hover:bg-slate-900 text-white font-extrabold rounded-lg text-[10px] transition-all flex items-center gap-1.5 shadow-sm">
|
||||
<i class="uil uil-github text-xs"></i>
|
||||
<span>Git Commitlerini Çek</span>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">Günlük Çalışma Raporu</label>
|
||||
<textarea id="editor-content" rows="10" placeholder="Bu staj gününde yaptığınız çalışmaları detaylıca yazın..." class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<div id="save-status" class="text-xs font-semibold text-slate-400"></div>
|
||||
<button type="button" onclick="saveActiveDay()" class="px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs transition-all shadow-md shadow-blue-500/10">
|
||||
Deftere Kaydet
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -736,6 +749,43 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Print Choice Modal -->
|
||||
<div id="print-modal" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-3xl p-6 max-w-md w-full shadow-2xl border border-slate-100/50 transform scale-95 opacity-0 transition-all duration-300" id="print-modal-card">
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-slate-800">Staj Defteri Yazdır / İndir</h3>
|
||||
<p class="text-xs text-slate-400 mt-1">Lütfen yazdırmak istediğiniz staj defterinin sayfa boyutunu seçin.</p>
|
||||
</div>
|
||||
<button onclick="closePrintModal()" class="text-slate-400 hover:text-slate-600">
|
||||
<i class="uil uil-multiply text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<a href="{{ route('intern.print-journal') }}?size=a4" target="_blank" onclick="closePrintModal()" class="flex flex-col items-center justify-center p-5 rounded-2xl border border-slate-100 bg-slate-50/50 hover:border-blue-300 hover:bg-blue-50/20 transition-all group text-center">
|
||||
<div class="w-12 h-12 rounded-xl bg-blue-50 text-blue-600 flex items-center justify-center text-2xl mb-3 group-hover:bg-blue-600 group-hover:text-white transition-all">
|
||||
<i class="uil uil-file-alt"></i>
|
||||
</div>
|
||||
<span class="block text-sm font-extrabold text-slate-800">A4 Boyutu</span>
|
||||
<span class="block text-[10px] text-slate-400 mt-0.5">Dikey (210 x 297 mm)</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('intern.print-journal') }}?size=a5" target="_blank" onclick="closePrintModal()" class="flex flex-col items-center justify-center p-5 rounded-2xl border border-slate-100 bg-slate-50/50 hover:border-blue-300 hover:bg-blue-50/20 transition-all group text-center">
|
||||
<div class="w-12 h-12 rounded-xl bg-blue-50 text-blue-600 flex items-center justify-center text-2xl mb-3 group-hover:bg-blue-600 group-hover:text-white transition-all">
|
||||
<i class="uil uil-file"></i>
|
||||
</div>
|
||||
<span class="block text-sm font-extrabold text-slate-800">A5 Boyutu</span>
|
||||
<span class="block text-[10px] text-slate-400 mt-0.5">Dikey (148 x 210 mm)</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-blue-50 border border-blue-100 rounded-2xl text-xs text-blue-700 font-semibold leading-relaxed">
|
||||
<i class="uil uil-info-circle text-sm mr-1"></i> Açılan önizleme sayfasında <strong>CTRL + P</strong> tuşlarına basarak "Hedef: PDF Olarak Kaydet" seçeneği ile staj defterinizi bilgisayarınıza indirebilirsiniz.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('styles')
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
@@ -773,226 +823,53 @@
|
||||
if (targetPanel) {
|
||||
targetPanel.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// If journal is selected, trigger GitHub commits loading
|
||||
if (targetId === 'journal') {
|
||||
loadGithubCommits();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle initial state of date logic
|
||||
calculateEndDateFrontend();
|
||||
|
||||
// Fetch Github commits in memory if repo is set
|
||||
fetchAllCommitsInMemory();
|
||||
|
||||
// Select first day of the notebook workspace on load
|
||||
selectNotebookDay(0);
|
||||
});
|
||||
|
||||
const githubRepoUrl = @json($intern->github_repo);
|
||||
let commitDays = [];
|
||||
let activeDayIndex = 0;
|
||||
let allGithubCommits = {};
|
||||
let activeDayIdx = 0;
|
||||
|
||||
function loadGithubCommits() {
|
||||
const loader = document.getElementById('commits-loader');
|
||||
const errorEl = document.getElementById('commits-error');
|
||||
const emptyEl = document.getElementById('commits-empty');
|
||||
const navEl = document.getElementById('journal-carousel-nav');
|
||||
const containerEl = document.getElementById('commits-timeline-container');
|
||||
function fetchAllCommitsInMemory() {
|
||||
if (!githubRepoUrl) return;
|
||||
|
||||
if (!githubRepoUrl) {
|
||||
emptyEl.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
loader.classList.remove('hidden');
|
||||
errorEl.classList.add('hidden');
|
||||
emptyEl.classList.add('hidden');
|
||||
navEl.classList.add('hidden');
|
||||
containerEl.classList.add('hidden');
|
||||
commitDays = [];
|
||||
|
||||
// Parse owner and repo
|
||||
let repoClean = githubRepoUrl.replace(/https?:\/\/(www\.)?github\.com\//i, '');
|
||||
repoClean = repoClean.replace(/\/$/, '');
|
||||
const parts = repoClean.split('/');
|
||||
if (parts.length < 2) {
|
||||
loader.classList.add('hidden');
|
||||
errorEl.textContent = 'Github depo URL\'si çözümlenemedi. Geçerli format: https://github.com/kullanici/depo';
|
||||
errorEl.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
if (parts.length < 2) return;
|
||||
|
||||
const owner = parts[0];
|
||||
const repo = parts[1].replace(/\.git$/i, '');
|
||||
|
||||
fetch(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=100`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error('Depo bulunamadı veya private (gizli). Deponuzun public (herkese açık) olduğundan emin olun.');
|
||||
} else if (response.status === 403) {
|
||||
throw new Error('GitHub API istek limiti aşıldı. Lütfen daha sonra tekrar deneyin veya yukarıdaki indirme butonunu kullanın.');
|
||||
} else {
|
||||
throw new Error('GitHub API hatası: ' + response.statusText);
|
||||
}
|
||||
}
|
||||
if (!response.ok) throw new Error('API Error');
|
||||
return response.json();
|
||||
})
|
||||
.then(commits => {
|
||||
loader.classList.add('hidden');
|
||||
if (!Array.isArray(commits) || commits.length === 0) {
|
||||
emptyEl.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reverse to show in chronological order (Day 1, Day 2...)
|
||||
commits.reverse();
|
||||
|
||||
// Group commits by date (YYYY-MM-DD)
|
||||
const grouped = {};
|
||||
if (!Array.isArray(commits)) return;
|
||||
commits.forEach(item => {
|
||||
const dateStr = item.commit.author.date;
|
||||
if (dateStr) {
|
||||
const dateOnly = dateStr.substring(0, 10); // "YYYY-MM-DD"
|
||||
if (!grouped[dateOnly]) {
|
||||
grouped[dateOnly] = [];
|
||||
const dateOnly = dateStr.substring(0, 10);
|
||||
if (!allGithubCommits[dateOnly]) {
|
||||
allGithubCommits[dateOnly] = [];
|
||||
}
|
||||
grouped[dateOnly].push(item);
|
||||
allGithubCommits[dateOnly].push(item);
|
||||
}
|
||||
});
|
||||
|
||||
const sortedDates = Object.keys(grouped).sort();
|
||||
|
||||
commitDays = sortedDates.map((date, index) => {
|
||||
const dateParts = date.split('-');
|
||||
return {
|
||||
date: date,
|
||||
dayNum: index + 1,
|
||||
formattedDate: `${dateParts[2]}.${dateParts[1]}.${dateParts[0]}`,
|
||||
commits: grouped[date]
|
||||
};
|
||||
});
|
||||
|
||||
if (commitDays.length === 0) {
|
||||
emptyEl.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
activeDayIndex = 0;
|
||||
navEl.classList.remove('hidden');
|
||||
containerEl.classList.remove('hidden');
|
||||
|
||||
renderDayTabs();
|
||||
renderActiveDay();
|
||||
})
|
||||
.catch(err => {
|
||||
loader.classList.add('hidden');
|
||||
errorEl.textContent = err.message || 'Bir hata oluştu.';
|
||||
errorEl.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
function renderDayTabs() {
|
||||
const container = document.getElementById('day-tabs-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
commitDays.forEach((day, index) => {
|
||||
const isActive = index === activeDayIndex;
|
||||
const activeClass = isActive
|
||||
? 'text-blue-700 bg-blue-50 border border-blue-200 font-extrabold shadow-sm'
|
||||
: 'bg-white hover:bg-slate-50 text-slate-600 border border-slate-200/60 font-semibold hover:text-blue-600';
|
||||
|
||||
const tab = document.createElement('button');
|
||||
tab.type = 'button';
|
||||
tab.className = `px-5 py-2 rounded-xl transition-all flex flex-col items-center justify-center ${activeClass}`;
|
||||
tab.innerHTML = `
|
||||
<span class="text-xs">${day.dayNum}. Gün</span>
|
||||
<span class="text-[9px] mt-0.5 opacity-70 font-medium">${day.formattedDate}</span>
|
||||
`;
|
||||
tab.onclick = () => {
|
||||
activeDayIndex = index;
|
||||
renderDayTabs();
|
||||
renderActiveDay();
|
||||
};
|
||||
container.appendChild(tab);
|
||||
});
|
||||
|
||||
// Handle scroll to active element
|
||||
const activeTab = container.children[activeDayIndex];
|
||||
if (activeTab) {
|
||||
activeTab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
||||
}
|
||||
|
||||
// Disable/enable arrows
|
||||
const prevBtn = document.getElementById('prev-day-btn');
|
||||
const nextBtn = document.getElementById('next-day-btn');
|
||||
|
||||
prevBtn.disabled = activeDayIndex === 0;
|
||||
prevBtn.classList.toggle('opacity-40', activeDayIndex === 0);
|
||||
prevBtn.classList.toggle('cursor-not-allowed', activeDayIndex === 0);
|
||||
|
||||
nextBtn.disabled = activeDayIndex === commitDays.length - 1;
|
||||
nextBtn.classList.toggle('opacity-40', activeDayIndex === commitDays.length - 1);
|
||||
nextBtn.classList.toggle('cursor-not-allowed', activeDayIndex === commitDays.length - 1);
|
||||
}
|
||||
|
||||
function renderActiveDay() {
|
||||
const card = document.getElementById('active-day-card');
|
||||
|
||||
// Smooth transition
|
||||
card.style.opacity = '0.3';
|
||||
card.style.transform = 'translateY(5px)';
|
||||
|
||||
setTimeout(() => {
|
||||
const day = commitDays[activeDayIndex];
|
||||
|
||||
document.getElementById('active-day-title').textContent = `${day.dayNum}. Gün`;
|
||||
document.getElementById('active-day-date').innerHTML = `<i class="uil uil-calendar-alt mr-1"></i> ${day.formattedDate}`;
|
||||
document.getElementById('active-day-commit-count').textContent = `${day.commits.length} Commit`;
|
||||
|
||||
const eventsContainer = document.getElementById('timeline-events');
|
||||
eventsContainer.innerHTML = '';
|
||||
|
||||
day.commits.forEach(c => {
|
||||
const message = c.commit.message;
|
||||
const authorDate = new Date(c.commit.author.date);
|
||||
const timeStr = authorDate.toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' });
|
||||
const shaShort = c.sha.substring(0, 7);
|
||||
const commitUrl = c.html_url;
|
||||
|
||||
const eventHtml = `
|
||||
<div class="relative group">
|
||||
<!-- Timeline Dot -->
|
||||
<div class="absolute -left-[31px] sm:-left-[39px] top-1.5 w-6 h-6 rounded-full bg-white border-2 border-blue-500 flex items-center justify-center transition-all group-hover:bg-blue-500">
|
||||
<div class="w-2 h-2 rounded-full bg-blue-500 group-hover:bg-white transition-all"></div>
|
||||
</div>
|
||||
<!-- Event Card -->
|
||||
<div class="p-4 bg-slate-50/50 hover:bg-blue-50/10 border border-slate-100 hover:border-blue-100/60 rounded-2xl transition-all shadow-sm">
|
||||
<div class="flex items-center justify-between gap-4 mb-2">
|
||||
<span class="text-xs font-bold text-slate-400"><i class="uil uil-clock mr-1"></i> ${timeStr}</span>
|
||||
<a href="${commitUrl}" target="_blank" class="text-xs font-mono font-bold text-blue-600 hover:text-blue-700 bg-blue-50/60 hover:bg-blue-50 px-2.5 py-0.5 rounded-lg border border-blue-100/40 transition-colors">
|
||||
${shaShort}
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-sm font-bold text-slate-700 leading-relaxed whitespace-pre-line">${escapeHtml(message)}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
eventsContainer.insertAdjacentHTML('beforeend', eventHtml);
|
||||
});
|
||||
|
||||
card.style.opacity = '1';
|
||||
card.style.transform = 'translateY(0)';
|
||||
}, 150);
|
||||
}
|
||||
|
||||
function navigateDay(dir) {
|
||||
const newIndex = activeDayIndex + dir;
|
||||
if (newIndex >= 0 && newIndex < commitDays.length) {
|
||||
activeDayIndex = newIndex;
|
||||
renderDayTabs();
|
||||
renderActiveDay();
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
|
||||
@@ -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>
|
||||
@@ -25,6 +25,84 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================= PAGE 3: STAJ DEFTERİ (PORTRAIT A4) ================= -->
|
||||
@if($application->notebook_approved || $application->notebook_supervisor_signed || $application->notebook_unit_signed)
|
||||
<div class="notebook-container-wrapper overflow-hidden w-full flex justify-center items-start mt-8">
|
||||
<div id="notebook-node" class="transcript-page bg-white rounded-[2rem] shadow-2xl p-6 md:p-8 relative overflow-hidden mx-auto">
|
||||
|
||||
<!-- Thin border decor for notebook -->
|
||||
<div class="absolute inset-4 border border-slate-100 rounded-[1.5rem] pointer-events-none"></div>
|
||||
|
||||
<div class="relative z-10 flex flex-col justify-between h-full text-slate-700">
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center pb-2 border-b border-slate-100 mb-3">
|
||||
<div>
|
||||
<h2 class="font-serif font-bold text-slate-900 transcript-header-title">ONAYLI STAJ DEFTERİ RAPORU</h2>
|
||||
<p class="text-slate-400 tracking-wider mt-0.5 transcript-header-subtitle">DİJİTAL İMZALI VE ONAYLI STAJ RAPORLARI</p>
|
||||
</div>
|
||||
<img src="{{ asset('logos/truncgil-yatay.svg') }}" alt="Trunçgil Logo" class="h-6 object-contain">
|
||||
</div>
|
||||
|
||||
<!-- Signatures Information -->
|
||||
<div class="bg-emerald-50 border border-emerald-200 rounded-xl p-3 mb-4 flex justify-between items-center">
|
||||
<div>
|
||||
<span class="text-xs font-extrabold text-emerald-800 uppercase block tracking-wider">Dijital İmza Durumu</span>
|
||||
<span class="text-[10px] text-emerald-600 font-semibold block mt-0.5">Bu staj defteri yetkililer tarafından elektronik olarak imzalanmıştır.</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@if($application->notebook_supervisor_signed)
|
||||
<span class="inline-flex px-2 py-1 rounded bg-emerald-100 text-emerald-800 text-[8px] font-extrabold uppercase border border-emerald-200">
|
||||
Sorumlu: {{ $application->notebook_supervisor_name ?: 'Alperen Trunç' }}
|
||||
</span>
|
||||
@endif
|
||||
@if($application->notebook_unit_signed)
|
||||
<span class="inline-flex px-2 py-1 rounded bg-emerald-100 text-emerald-800 text-[8px] font-extrabold uppercase border border-emerald-200">
|
||||
Yetkili: {{ $application->notebook_unit_name ?: 'Birim Yetkilisi' }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notebook Days content overview -->
|
||||
<div class="overflow-y-auto max-h-[700px] pr-2 no-scrollbar space-y-4">
|
||||
@foreach($days as $day)
|
||||
@php
|
||||
$entry = $savedEntries->get($day['day_number']);
|
||||
@endphp
|
||||
@if($entry && trim($entry->content))
|
||||
<div class="p-3 bg-slate-50 border border-slate-100 rounded-xl">
|
||||
<div class="flex justify-between items-center pb-1.5 border-b border-slate-200/50 mb-1.5">
|
||||
<span class="text-xs font-extrabold text-slate-800">{{ $day['day_number'] }}. Gün Raporu</span>
|
||||
<span class="text-[10px] text-slate-400 font-bold">{{ $day['formatted_date'] }}</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-600 whitespace-pre-wrap leading-relaxed" style="font-size: 11px;">{{ $entry->content }}</p>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="w-full flex justify-between items-center pt-2 border-t border-slate-100 gap-4 mt-auto">
|
||||
<div class="flex items-center gap-3">
|
||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data={{ urlencode(route('internship.verify', $application->certificate_code)) }}" alt="QR Code" class="w-14 h-14 border border-slate-200 p-0.5 rounded-lg bg-white">
|
||||
<div>
|
||||
<span class="font-bold text-slate-400 tracking-wider block verify-code-label">GÜVENLİ DOĞRULAMA KODU</span>
|
||||
<span class="font-mono font-bold text-slate-800 block verify-code">{{ $application->certificate_code }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right text-slate-400 footer-address">
|
||||
Trunçgil Teknoloji San. ve Tic. Ltd. Şti.<br>
|
||||
Gaziantep Üniversitesi Teknopark No: 4A
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -426,6 +504,7 @@
|
||||
function scaleAllDocuments() {
|
||||
scaleDocument('.certificate-container-wrapper', '#certificate-node', 1122, 794);
|
||||
scaleDocument('.transcript-container-wrapper', '#transcript-node', 794, 1122);
|
||||
scaleDocument('.notebook-container-wrapper', '#notebook-node', 794, 1122);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', scaleAllDocuments);
|
||||
@@ -455,6 +534,7 @@
|
||||
|
||||
const certEl = document.getElementById('certificate-node');
|
||||
const transEl = document.getElementById('transcript-node');
|
||||
const notebookEl = document.getElementById('notebook-node');
|
||||
|
||||
// Force elements to normal scale prior to capturing for PDF
|
||||
const origCertTransform = certEl.style.transform;
|
||||
@@ -466,6 +546,19 @@
|
||||
const origTransHeight = transEl.style.height;
|
||||
const origTransRadius = transEl.style.borderRadius;
|
||||
|
||||
let origNotebookTransform, origNotebookWidth, origNotebookHeight, origNotebookRadius;
|
||||
if (notebookEl) {
|
||||
origNotebookTransform = notebookEl.style.transform;
|
||||
origNotebookWidth = notebookEl.style.width;
|
||||
origNotebookHeight = notebookEl.style.height;
|
||||
origNotebookRadius = notebookEl.style.borderRadius;
|
||||
|
||||
notebookEl.style.transform = 'none';
|
||||
notebookEl.style.width = '794px';
|
||||
notebookEl.style.height = '1122px';
|
||||
notebookEl.style.borderRadius = '0';
|
||||
}
|
||||
|
||||
certEl.style.transform = 'none';
|
||||
certEl.style.width = '1122px';
|
||||
certEl.style.height = '794px';
|
||||
@@ -509,6 +602,21 @@
|
||||
pdf.addPage('a4', 'portrait');
|
||||
pdf.addImage(transImgData, 'JPEG', 0, 0, transWidth, transHeight);
|
||||
|
||||
// Render Page 3 (Notebook) if it exists
|
||||
if (notebookEl) {
|
||||
const notebookCanvas = await html2canvas(notebookEl, {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
backgroundColor: '#ffffff',
|
||||
logging: false
|
||||
});
|
||||
const notebookImgData = notebookCanvas.toDataURL('image/jpeg', 1.0);
|
||||
const notebookHeight = (notebookCanvas.height * transWidth) / notebookCanvas.width;
|
||||
|
||||
pdf.addPage('a4', 'portrait');
|
||||
pdf.addImage(notebookImgData, 'JPEG', 0, 0, transWidth, notebookHeight);
|
||||
}
|
||||
|
||||
// Restore styles
|
||||
certEl.style.transform = origCertTransform;
|
||||
certEl.style.width = origCertWidth;
|
||||
@@ -519,6 +627,13 @@
|
||||
transEl.style.height = origTransHeight;
|
||||
transEl.style.borderRadius = origTransRadius;
|
||||
|
||||
if (notebookEl) {
|
||||
notebookEl.style.transform = origNotebookTransform;
|
||||
notebookEl.style.width = origNotebookWidth;
|
||||
notebookEl.style.height = origNotebookHeight;
|
||||
notebookEl.style.borderRadius = origNotebookRadius;
|
||||
}
|
||||
|
||||
// Trigger Save File
|
||||
pdf.save('{{ str()->slug($application->name) }}-staj-belgesi.pdf');
|
||||
|
||||
|
||||
@@ -122,6 +122,8 @@ Route::get('/stajyer/panel', [\App\Http\Controllers\CareerController::class, 'in
|
||||
Route::post('/stajyer/form-yukle', [\App\Http\Controllers\CareerController::class, 'uploadInternshipForm'])->name('intern.upload-form');
|
||||
Route::post('/stajyer/github-kaydet', [\App\Http\Controllers\CareerController::class, 'saveGithubRepo'])->name('intern.save-github');
|
||||
Route::get('/stajyer/gunluk-indir', [\App\Http\Controllers\CareerController::class, 'downloadMarkdown'])->name('intern.download-journal');
|
||||
Route::post('/stajyer/defteri-kaydet', [\App\Http\Controllers\CareerController::class, 'saveJournalEntry'])->name('intern.save-journal');
|
||||
Route::get('/stajyer/defteri-yazdir', [\App\Http\Controllers\CareerController::class, 'printJournal'])->name('intern.print-journal');
|
||||
Route::get('/staj-dogrulama/{code}', [\App\Http\Controllers\CareerController::class, 'verifyCertificate'])->name('internship.verify');
|
||||
Route::post('/stajyer/cikis', [\App\Http\Controllers\CareerController::class, 'internLogout'])->name('intern.logout');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user