'Staj Başvuruları']); } public static function getModelLabel(): string { return __('career.internship', ['default' => 'Staj Başvurusu']); } public static function getPluralModelLabel(): string { return __('career.internship_title', ['default' => 'Staj Başvuruları']); } public static function getEloquentQuery(): Builder { return parent::getEloquentQuery()->where('type', 'internship'); } public static function form(Schema $schema): Schema { return $schema ->components([ Tabs::make('Tabs') ->tabs([ Tab::make('Kişisel ve Başvuru Bilgileri') ->icon('heroicon-m-user') ->schema([ TextInput::make('name') ->label(__('career.name')) ->required() ->disabled(), TextInput::make('email') ->label(__('career.email')) ->email() ->required() ->disabled(), TextInput::make('phone') ->label(__('career.phone')) ->disabled(), Textarea::make('message') ->label(__('career.message')) ->disabled(), Radio::make('status') ->label('Stajyer Başvuru & İlerleme Aşaması') ->options([ 'pending' => '1. Aşama: Başvuru Alındı (Beklemede)', 'reviewed' => '2. Aşama: Ön Değerlendirme Yapıldı (İncelendi)', 'waiting_document' => '3. Aşama: Staj Formu Bekleniyor (Stajyer Form Yükleyecek)', 'accepted' => '4. Aşama: Kabul Edildi & Staj Aktif (İmzalı Form Onaylandı)', 'rejected' => 'Reddedildi (Başvuru İptal / Olumsuz)', ]) ->descriptions([ 'pending' => 'Stajyer yeni başvurdu. CV ve başvuru bilgileri incelenmeyi bekliyor.', 'reviewed' => 'CV ve ön başvuru incelendi, uygunluk değerlendirmesi tamamlandı.', 'waiting_document' => 'Stajyer kabul sürecine alındı. Okulundan alacağı staj formunu ve tarihlerini panelinden yüklemesi bekleniyor.', 'accepted' => 'İmzalı staj formu sisteme yüklendi/onaylandı ve staj defteri doldurma süreci başladı.', 'rejected' => 'Başvuru kriterlere uymadığı için olumsuz sonuçlandırıldı.', ]) ->columnSpanFull() ->required(), ])->columns(2), Tab::make('Staj Belgeleri & Giriş Bilgileri') ->icon('heroicon-m-document-text') ->schema([ FileUpload::make('cv_path') ->label(__('career.cv')) ->disk('public') ->directory('cvs') ->required() ->disabled() ->downloadable(), TextInput::make('username') ->label('Kullanıcı Adı') ->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)) ->placeholder('Şifreyi değiştirmek istemiyorsanız boş bırakın') ->nullable() ->suffixAction( \Filament\Actions\Action::make('generatePassword') ->icon('heroicon-m-arrow-path') ->action(fn (Set $set) => $set('password', Str::random(12))) ), FileUpload::make('to_be_signed_internship_form_path') ->label('İmzalanacak Staj Formu (Stajyerden)') ->disk('public') ->directory('to_be_signed_interns') ->downloadable() ->nullable(), FileUpload::make('signed_internship_form_path') ->label('İmzalı Staj Formu') ->disk('public') ->directory('signed_interns') ->downloadable() ->live() ->afterStateUpdated(function ($state, Set $set) { if ($state) { $set('status', 'accepted'); } }) ->nullable(), DatePicker::make('internship_start_date') ->label('Staj Başlangıç Tarihi') ->live() ->afterStateUpdated(function ($state, $get, Set $set) { if ($state && $get('internship_total_days')) { self::calculateEndDate($state, $get('internship_total_days'), $set); } elseif ($state && $get('internship_end_date')) { self::calculateTotalDays($state, $get('internship_end_date'), $set); } }) ->nullable(), DatePicker::make('internship_end_date') ->label('Staj Bitiş Tarihi') ->live() ->afterStateUpdated(fn ($state, $get, Set $set) => self::calculateTotalDays($get('internship_start_date'), $state, $set)) ->nullable(), TextInput::make('internship_total_days') ->label('Toplam Staj Süresi (İş Günü)') ->numeric() ->live() ->afterStateUpdated(fn ($state, $get, Set $set) => self::calculateEndDate($get('internship_start_date'), $state, $set)) ->nullable(), ])->columns(2), Tab::make('Staj Günlüğü') ->icon('heroicon-m-squares-plus') ->schema([ TextInput::make('github_repo') ->label('GitHub Depo URL\'si') ->url() ->nullable() ->live(), Livewire::make(\App\Livewire\InternJournalTimeline::class) ->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 = ''; $html .= '
'; foreach ($days as $d) { $dayNum = $d['day_number']; $dateF = $d['formatted_date']; $entry = $saved->get($dayNum); $content = $entry ? $entry->content : ''; $isRetro = $entry ? $entry->is_retroactive : false; $updatedAt = $entry ? $entry->updated_at->format('d.m.Y H:i') : null; $html .= '
'; $html .= '
'; $html .= ' ' . $dayNum . '. Gün Raporu'; if ($isRetro) { $html .= ' GERİYE DÖNÜK KAYIT'; } if ($updatedAt) { $html .= ' Son Güncelleme: ' . $updatedAt . ''; } else { $html .= ' ' . $dateF . ''; } $html .= '
'; $cleanContent = $content ? strip_tags($content, ['p', 'strong', 'ul', 'li', 'em', 'br', 'b', 'i', 'ol', 'span']) : 'Rapor yazılmamış'; $html .= '
' . $cleanContent . '
'; if ($entry && trim($content) !== '') { $supApproved = $entry->supervisor_approved; $supName = $entry->supervisor_name; $html .= '
'; // Supervisor approval $supBg = $supApproved ? '#d1fae5' : '#f1f5f9'; $supColor = $supApproved ? '#065f46' : '#64748b'; $supText = $supApproved ? 'Sorumlu Onayladı' . ($supName ? ' (Onaylayan: ' . e($supName) . ')' : '') : 'Sorumlu Onayı Bekliyor'; $html .= ' ' . $supText . ''; // Buttons $supBtnText = $supApproved ? 'Onayı Kaldır' : 'Onayla'; $supBtnBg = $supApproved ? '#ef4444' : '#2563eb'; $html .= ' '; $html .= '
'; } $html .= '
'; } $html .= '
'; // JS handler $html .= ' '; // Add preview buttons $html .= '
'; $html .= ' A4 Defteri Önizle / Yazdır'; $html .= ' A5 Defteri Önizle / Yazdır'; $html .= '
'; return new \Illuminate\Support\HtmlString($html); }) ->columnSpanFull(), \Filament\Schemas\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_approved') ->label('Staj Defterini Genel Olarak Onayla') ->columnSpanFull(), ])->columns(2), ]), Tab::make('Sertifika & Transkript') ->icon('heroicon-o-academic-cap') ->schema([ TextInput::make('certificate_code') ->label('Doğrulama Kodu') ->helperText('Belge kaydedildiğinde benzersiz doğrulama kodu otomatik olarak üretilir.') ->readonly() ->nullable(), MarkdownEditor::make('transcript_markdown') ->label('Akademik Transkript (Markdown)') ->columnSpanFull() ->default(function () { return "### STAJ AKADEMİK TRANSKRİPTİ VE PERFORMANS RAPORU\n\n" . "#### 🛠️ Deneyimlenen Teknolojiler ve Kazanımlar\n" . "| Modül / Çalışma Alanı | Kullanılan Teknolojiler / Araçlar | Değerlendirme |\n" . "| --- | --- | --- |\n" . "| Backend Mimari & API | Laravel framework, RESTful API, MySQL | Başarılı |\n" . "| Arayüz & UI/UX Uygulamaları | Flutter, CSS, Glassmorphic Tasarım Prensipleri | Üstün Başarı |\n" . "| Masaüstü & Sistem Entegrasyonu | Electron.js, Git / GitHub | Başarılı |\n" . "| Takım Çalışması & Proje Yönetimi | Agile / Scrum, Slack, JIRA | Başarılı |\n\n" . "#### 📊 Performans Değerlendirme Kriterleri\n" . "| Değerlendirme Kriteri | Puan (100 Üzerinden) | Harf Notu |\n" . "| --- | --- | --- |\n" . "| Teknik Sorumluluk ve Görev Bilinci | 95 | AA |\n" . "| Problem Çözme ve Analitik Düşünme | 90 | BA |\n" . "| Ekip Çalışması ve İletişim Uyum | 95 | AA |\n" . "| Öğrenme Hızı ve Adaptasyon | 100 | AA |\n" . "| **GENEL BAŞARI ORTALAMASI** | **95.00** | **AA (Mükemmel)** |\n\n" . "#### 📝 Danışman Görüşü ve Değerlendirme Notu\n" . "\"Stajyerimiz, staj süresi boyunca kendisine verilen görevleri büyük bir titizlikle yerine getirmiştir. Özellikle karşılaştığı teknik problemlere getirdiği pratik çözümler ve yeni teknolojileri öğrenme isteği takdir edilmeye değerdir. Kurumumuz bünyesinde yürüttüğümüz projelere sağladığı katkılardan ötürü teşekkür eder, profesyonel kariyerinde başarılar dileriz.\""; }), ])->columns(1), Tab::make('Stajyer Blog Yazıları') ->icon('heroicon-o-newspaper') ->schema([ \Filament\Forms\Components\Placeholder::make('intern_blogs_view') ->label('Yazılan Blog Yazıları') ->content(function ($record) { if (!$record) return 'Henüz kayıt bulunmuyor.'; $blogs = $record->blogs()->orderBy('created_at', 'desc')->get(); if ($blogs->isEmpty()) { return new \Illuminate\Support\HtmlString('
Bu stajyer henüz blog yazısı oluşturmadı.
'); } $html = '
'; foreach ($blogs as $b) { $catLabel = match ($b->intern_category) { 'experience' => '1. Staj Tecrübesi', 'technical_challenge' => '2. Teknik Zorluklar', 'product_showcase' => '3. Ürün Tanıtımı', default => $b->intern_category ?? '-', }; $statusBg = match ($b->status) { 'published' => '#d1fae5', 'pending' => '#fef3c7', 'rejected' => '#fee2e2', default => '#f1f5f9', }; $statusColor = match ($b->status) { 'published' => '#065f46', 'pending' => '#92400e', 'rejected' => '#991b1b', default => '#475569', }; $statusText = match ($b->status) { 'published' => 'Yayınlandı', 'pending' => 'Onay Bekliyor', 'rejected' => 'Revize İstendi', default => 'Taslak', }; $html .= '
'; $html .= '
'; $html .= '
'; $html .= ' ' . e($catLabel) . ''; $html .= ' ' . $statusText . ''; $html .= '
'; $html .= '

' . e($b->title) . '

'; if ($b->admin_feedback) { $html .= '

Revizyon Notu: ' . e($b->admin_feedback) . '

'; } $html .= '
'; $html .= '
'; if ($b->status === 'published') { $html .= ' Sitede Gör'; } $html .= '
'; $html .= '
'; } $html .= '
'; return new \Illuminate\Support\HtmlString($html); }) ->columnSpanFull(), ]) ])->columnSpanFull() ]); } public static function table(Table $table): Table { return $table ->columns([ TextColumn::make('name') ->label(__('career.name')) ->searchable() ->sortable(), TextColumn::make('email') ->label(__('career.email')) ->searchable() ->sortable(), TextColumn::make('phone') ->label(__('career.phone')) ->searchable(), TextColumn::make('status') ->label(__('career.status')) ->badge() ->color(fn (string $state): string => match ($state) { 'pending' => 'gray', 'reviewed' => 'info', 'rejected' => 'danger', 'accepted' => 'success', 'waiting_document' => 'warning', default => 'gray', }) ->formatStateUsing(fn (string $state): string => __("career.{$state}")), TextColumn::make('certificate_code') ->label('Sertifika Kodu') ->searchable() ->placeholder('Yok'), TextColumn::make('created_at') ->label(__('career.created_at')) ->dateTime('d.m.Y H:i') ->sortable(), ]) ->filters([ SelectFilter::make('status') ->label(__('career.status')) ->options([ 'pending' => '1. Aşama: Beklemede', 'reviewed' => '2. Aşama: İncelendi', 'waiting_document' => '3. Aşama: Staj Formu Bekleniyor', 'accepted' => '4. Aşama: Kabul Edildi', 'rejected' => 'Reddedildi', ]), ]) ->actions([ Action::make('view_certificate') ->label('Sertifika & Transkript') ->icon('heroicon-o-academic-cap') ->color('success') ->url(fn ($record) => $record->certificate_code ? route('internship.verify', $record->certificate_code) : null) ->visible(fn ($record) => !empty($record->certificate_code)) ->openUrlInNewTab(), Action::make('print_journal_a4') ->label('A4 Defter') ->icon('heroicon-o-printer') ->color('info') ->url(fn ($record) => route('intern.print-journal') . '?size=a4&intern_id=' . $record->id) ->visible(fn ($record) => !empty($record->internship_total_days)) ->openUrlInNewTab(), Action::make('print_journal_a5') ->label('A5 Defter') ->icon('heroicon-o-printer') ->color('gray') ->url(fn ($record) => route('intern.print-journal') . '?size=a5&intern_id=' . $record->id) ->visible(fn ($record) => !empty($record->internship_total_days)) ->openUrlInNewTab(), Action::make('download_markdown') ->label('Günlük (.md)') ->icon('heroicon-o-arrow-down-tray') ->color('warning') ->url(fn ($record) => route('intern.download-journal') . '?intern_id=' . $record->id) ->visible(fn ($record) => !empty($record->github_repo)) ->openUrlInNewTab(), Action::make('download_signed_form') ->label('İmzalı Form') ->icon('heroicon-o-document-check') ->url(fn ($record) => $record->signed_internship_form_path ? Storage::disk('public')->url($record->signed_internship_form_path) : null) ->visible(fn ($record) => !empty($record->signed_internship_form_path)) ->openUrlInNewTab(), Action::make('download_cv') ->label(__('career.download_cv')) ->icon('heroicon-o-arrow-down-tray') ->url(fn ($record) => Storage::disk('public')->url($record->cv_path)) ->openUrlInNewTab(), DeleteAction::make(), ]) ->bulkActions([ BulkActionGroup::make([ DeleteBulkAction::make(), ]), ]) ->defaultSort('created_at', 'desc'); } public static function calculateTotalDays($start, $end, Set $set): void { if (!$start || !$end) { $set('internship_total_days', null); return; } $startDate = \Carbon\Carbon::parse($start); $endDate = \Carbon\Carbon::parse($end); if ($startDate->gt($endDate)) { $set('internship_total_days', 0); return; } $days = 0; while ($startDate->lte($endDate)) { if (!$startDate->isWeekend() && !\App\Helpers\TurkeyHolidayHelper::isHoliday($startDate)) { $days++; } $startDate->addDay(); } $set('internship_total_days', $days); } public static function calculateEndDate($start, $totalDays, Set $set): void { if (!$start || !$totalDays || $totalDays <= 0) { return; } $startDate = \Carbon\Carbon::parse($start); $daysToAdd = intval($totalDays); $endDate = $startDate->copy(); $count = 0; $temp = $startDate->copy(); while ($count < $daysToAdd) { if ($temp->isWeekend() || \App\Helpers\TurkeyHolidayHelper::isHoliday($temp)) { $temp->addDay(); continue; } $endDate = $temp->copy(); $temp->addDay(); $count++; } $set('internship_end_date', $endDate->format('Y-m-d')); } public static function getPages(): array { return [ 'index' => Pages\ListInternApplications::route('/'), 'create' => Pages\CreateInternApplication::route('/create'), 'edit' => Pages\EditInternApplication::route('/{record}/edit'), ]; } }