From 082fb33af3f1fd87c01da78d276ba5e9a96a77d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cmit=20Tun=C3=A7?= Date: Mon, 8 Jun 2026 18:17:56 +0300 Subject: [PATCH] refactor: migrate career applications to separate job and intern Filament resources --- .../CareerApplicationResource.php | 55 ---- .../Pages/CreateCareerApplication.php | 11 - .../Pages/ListCareerApplications.php | 19 -- .../Tables/CareerApplicationsTable.php | 124 --------- .../InternApplicationResource.php} | 180 +++++++++++- .../Pages/CreateInternApplication.php | 11 + .../Pages/EditInternApplication.php} | 8 +- .../Pages/ListInternApplications.php | 11 + .../JobApplicationResource.php | 218 +++++++++++++++ .../Pages/CreateJobApplication.php | 11 + .../Pages/EditJobApplication.php | 19 ++ .../Pages/ListJobApplications.php | 11 + app/Http/Controllers/CareerController.php | 15 + app/Models/CareerApplication.php | 14 + ...te_fields_to_career_applications_table.php | 29 ++ resources/views/front/career/verify.blade.php | 260 ++++++++++++++++++ routes/web.php | 1 + 17 files changed, 773 insertions(+), 224 deletions(-) delete mode 100644 app/Filament/Admin/Resources/CareerApplications/CareerApplicationResource.php delete mode 100644 app/Filament/Admin/Resources/CareerApplications/Pages/CreateCareerApplication.php delete mode 100644 app/Filament/Admin/Resources/CareerApplications/Pages/ListCareerApplications.php delete mode 100644 app/Filament/Admin/Resources/CareerApplications/Tables/CareerApplicationsTable.php rename app/Filament/Admin/Resources/{CareerApplications/Schemas/CareerApplicationForm.php => InternApplications/InternApplicationResource.php} (50%) create mode 100644 app/Filament/Admin/Resources/InternApplications/Pages/CreateInternApplication.php rename app/Filament/Admin/Resources/{CareerApplications/Pages/EditCareerApplication.php => InternApplications/Pages/EditInternApplication.php} (50%) create mode 100644 app/Filament/Admin/Resources/InternApplications/Pages/ListInternApplications.php create mode 100644 app/Filament/Admin/Resources/JobApplications/JobApplicationResource.php create mode 100644 app/Filament/Admin/Resources/JobApplications/Pages/CreateJobApplication.php create mode 100644 app/Filament/Admin/Resources/JobApplications/Pages/EditJobApplication.php create mode 100644 app/Filament/Admin/Resources/JobApplications/Pages/ListJobApplications.php create mode 100644 database/migrations/2026_06_08_150004_add_certificate_fields_to_career_applications_table.php create mode 100644 resources/views/front/career/verify.blade.php diff --git a/app/Filament/Admin/Resources/CareerApplications/CareerApplicationResource.php b/app/Filament/Admin/Resources/CareerApplications/CareerApplicationResource.php deleted file mode 100644 index 0cfd16a..0000000 --- a/app/Filament/Admin/Resources/CareerApplications/CareerApplicationResource.php +++ /dev/null @@ -1,55 +0,0 @@ - ListCareerApplications::route('/'), - 'create' => CreateCareerApplication::route('/create'), - 'edit' => EditCareerApplication::route('/{record}/edit'), - ]; - } -} diff --git a/app/Filament/Admin/Resources/CareerApplications/Pages/CreateCareerApplication.php b/app/Filament/Admin/Resources/CareerApplications/Pages/CreateCareerApplication.php deleted file mode 100644 index e37512d..0000000 --- a/app/Filament/Admin/Resources/CareerApplications/Pages/CreateCareerApplication.php +++ /dev/null @@ -1,11 +0,0 @@ -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('type') - ->label(__('career.type')) - ->badge() - ->color(fn (string $state): string => match ($state) { - 'job' => 'success', - 'internship' => 'warning', - default => 'gray', - }) - ->formatStateUsing(fn (string $state): string => __("career.{$state}")), - - 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('git_knowledge') - ->label(__('career.git_knowledge')) - ->badge() - ->color(fn ($state) => $state ? 'success' : 'danger') - ->formatStateUsing(fn ($state) => $state ? 'Evet' : 'Hayır'), - - TextColumn::make('ai_usage') - ->label(__('career.ai_usage')) - ->badge() - ->color(fn ($state) => $state ? 'success' : 'danger') - ->formatStateUsing(fn ($state) => $state ? 'Evet' : 'Hayır'), - - TextColumn::make('created_at') - ->label(__('career.created_at')) - ->dateTime('d.m.Y H:i') - ->sortable(), - ]) - ->filters([ - SelectFilter::make('status') - ->label(__('career.status')) - ->options([ - 'pending' => __('career.pending'), - 'reviewed' => __('career.reviewed'), - 'rejected' => __('career.rejected'), - 'accepted' => __('career.accepted'), - 'waiting_document' => __('career.waiting_document'), - ]), - SelectFilter::make('type') - ->label(__('career.type')) - ->options([ - 'job' => __('career.job'), - 'internship' => __('career.internship'), - ]), - ]) - ->actions([ - 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(), - Action::make('download_signed_form') - ->label('İmzalı Form İndir') - ->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_nda') - ->label(__('career.nda')) - ->icon('heroicon-o-shield-check') - ->url(fn ($record) => $record->nda_path ? Storage::disk('public')->url($record->nda_path) : null) - ->visible(fn ($record) => $record->nda_path !== null) - ->openUrlInNewTab(), - Action::make('download_contract') - ->label(__('career.contract')) - ->icon('heroicon-o-document-text') - ->url(fn ($record) => $record->contract_path ? Storage::disk('public')->url($record->contract_path) : null) - ->visible(fn ($record) => $record->contract_path !== null) - ->openUrlInNewTab(), - DeleteAction::make(), - ]) - ->bulkActions([ - BulkActionGroup::make([ - DeleteBulkAction::make(), - ]), - ]) - ->defaultSort('created_at', 'desc'); - } -} diff --git a/app/Filament/Admin/Resources/CareerApplications/Schemas/CareerApplicationForm.php b/app/Filament/Admin/Resources/InternApplications/InternApplicationResource.php similarity index 50% rename from app/Filament/Admin/Resources/CareerApplications/Schemas/CareerApplicationForm.php rename to app/Filament/Admin/Resources/InternApplications/InternApplicationResource.php index 2cdcfd5..26db41e 100644 --- a/app/Filament/Admin/Resources/CareerApplications/Schemas/CareerApplicationForm.php +++ b/app/Filament/Admin/Resources/InternApplications/InternApplicationResource.php @@ -1,25 +1,59 @@ '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([ @@ -85,7 +119,7 @@ class CareerApplicationForm ->placeholder('Şifreyi değiştirmek istemiyorsanız boş bırakın') ->nullable() ->suffixAction( - Action::make('generatePassword') + \Filament\Actions\Action::make('generatePassword') ->icon('heroicon-m-arrow-path') ->action(fn (Set $set) => $set('password', Str::random(12))) ), @@ -141,11 +175,126 @@ class CareerApplicationForm Livewire::make(\App\Livewire\InternJournalTimeline::class) ->columnSpanFull() - ]) + ]), + + 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) ])->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' => __('career.pending'), + 'reviewed' => __('career.reviewed'), + 'rejected' => __('career.rejected'), + 'accepted' => __('career.accepted'), + 'waiting_document' => __('career.waiting_document'), + ]), + ]) + ->actions([ + 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(), + Action::make('download_signed_form') + ->label('İmzalı Form İndir') + ->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('view_certificate') + ->label('Sertifika Doğrulama') + ->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(), + DeleteAction::make(), + ]) + ->bulkActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ]), + ]) + ->defaultSort('created_at', 'desc'); + } + public static function calculateTotalDays($start, $end, Set $set): void { if (!$start || !$end) { @@ -197,4 +346,13 @@ class CareerApplicationForm $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'), + ]; + } } diff --git a/app/Filament/Admin/Resources/InternApplications/Pages/CreateInternApplication.php b/app/Filament/Admin/Resources/InternApplications/Pages/CreateInternApplication.php new file mode 100644 index 0000000..55de459 --- /dev/null +++ b/app/Filament/Admin/Resources/InternApplications/Pages/CreateInternApplication.php @@ -0,0 +1,11 @@ + 'İş Başvuruları']); + } + + public static function getModelLabel(): string + { + return __('career.job', ['default' => 'İş Başvurusu']); + } + + public static function getPluralModelLabel(): string + { + return __('career.job_application_title', ['default' => 'İş Başvuruları']); + } + + public static function getEloquentQuery(): Builder + { + return parent::getEloquentQuery()->where('type', 'job'); + } + + public static function form(Schema $schema): Schema + { + return $schema + ->components([ + TextInput::make('name') + ->label(__('career.name')) + ->required() + ->disabled(), + + TextInput::make('email') + ->label(__('career.email')) + ->email() + ->required() + ->disabled(), + + TextInput::make('phone') + ->label(__('career.phone')) + ->disabled(), + + Select::make('status') + ->label(__('career.status')) + ->options([ + 'pending' => __('career.pending'), + 'reviewed' => __('career.reviewed'), + 'rejected' => __('career.rejected'), + 'accepted' => __('career.accepted'), + 'waiting_document' => __('career.waiting_document'), + ]) + ->required(), + + FileUpload::make('cv_path') + ->label(__('career.cv')) + ->disk('public') + ->directory('cvs') + ->required() + ->disabled() + ->downloadable(), + + FileUpload::make('nda_path') + ->label(__('career.nda')) + ->disk('public') + ->directory('ndas') + ->disabled() + ->downloadable(), + + FileUpload::make('contract_path') + ->label(__('career.contract')) + ->disk('public') + ->directory('contracts') + ->disabled() + ->downloadable(), + + FileUpload::make('id_photocopy_path') + ->label(__('career.id_photocopy', ['default' => 'Kimlik Fotokopisi'])) + ->disk('public') + ->directory('id_photocopies') + ->disabled() + ->downloadable(), + + Toggle::make('git_knowledge') + ->label(__('career.git_knowledge')) + ->disabled(), + + Toggle::make('ai_usage') + ->label(__('career.ai_usage')) + ->disabled(), + + Textarea::make('message') + ->label(__('career.message')) + ->disabled() + ->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('git_knowledge') + ->label(__('career.git_knowledge')) + ->badge() + ->color(fn ($state) => $state ? 'success' : 'danger') + ->formatStateUsing(fn ($state) => $state ? 'Evet' : 'Hayır'), + + TextColumn::make('ai_usage') + ->label(__('career.ai_usage')) + ->badge() + ->color(fn ($state) => $state ? 'success' : 'danger') + ->formatStateUsing(fn ($state) => $state ? 'Evet' : 'Hayır'), + + TextColumn::make('created_at') + ->label(__('career.created_at')) + ->dateTime('d.m.Y H:i') + ->sortable(), + ]) + ->filters([ + SelectFilter::make('status') + ->label(__('career.status')) + ->options([ + 'pending' => __('career.pending'), + 'reviewed' => __('career.reviewed'), + 'rejected' => __('career.rejected'), + 'accepted' => __('career.accepted'), + 'waiting_document' => __('career.waiting_document'), + ]), + ]) + ->actions([ + 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(), + Action::make('download_nda') + ->label(__('career.nda')) + ->icon('heroicon-o-shield-check') + ->url(fn ($record) => $record->nda_path ? Storage::disk('public')->url($record->nda_path) : null) + ->visible(fn ($record) => $record->nda_path !== null) + ->openUrlInNewTab(), + Action::make('download_contract') + ->label(__('career.contract')) + ->icon('heroicon-o-document-text') + ->url(fn ($record) => $record->contract_path ? Storage::disk('public')->url($record->contract_path) : null) + ->visible(fn ($record) => $record->contract_path !== null) + ->openUrlInNewTab(), + DeleteAction::make(), + ]) + ->bulkActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ]), + ]) + ->defaultSort('created_at', 'desc'); + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListJobApplications::route('/'), + 'create' => Pages\CreateJobApplication::route('/create'), + 'edit' => Pages\EditJobApplication::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Admin/Resources/JobApplications/Pages/CreateJobApplication.php b/app/Filament/Admin/Resources/JobApplications/Pages/CreateJobApplication.php new file mode 100644 index 0000000..1292a4e --- /dev/null +++ b/app/Filament/Admin/Resources/JobApplications/Pages/CreateJobApplication.php @@ -0,0 +1,11 @@ +forget('intern_id'); return redirect()->route('intern.login')->with('success', 'Başarıyla çıkış yapıldı.'); } + + public function verifyCertificate($code) + { + $application = CareerApplication::where('certificate_code', $code) + ->where('type', 'internship') + ->firstOrFail(); + + return view('front.career.verify', [ + 'application' => $application, + 'meta' => [ + 'title' => 'Staj Bitirme Sertifikası Doğrulama - ' . $application->name, + 'description' => $application->name . ' isimli stajyerimizin staj bitirme sertifikası ve performans raporu doğrulama sayfası.', + ] + ]); + } } diff --git a/app/Models/CareerApplication.php b/app/Models/CareerApplication.php index 6c66df6..f217ade 100644 --- a/app/Models/CareerApplication.php +++ b/app/Models/CareerApplication.php @@ -30,8 +30,22 @@ class CareerApplication extends Model 'internship_end_date', 'internship_total_days', 'github_repo', + 'certificate_code', + 'transcript_markdown', ]; + 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; + } + }); + } + /** * Get the attributes that should be cast. * diff --git a/database/migrations/2026_06_08_150004_add_certificate_fields_to_career_applications_table.php b/database/migrations/2026_06_08_150004_add_certificate_fields_to_career_applications_table.php new file mode 100644 index 0000000..698fa64 --- /dev/null +++ b/database/migrations/2026_06_08_150004_add_certificate_fields_to_career_applications_table.php @@ -0,0 +1,29 @@ +string('certificate_code')->nullable()->unique(); + $table->text('transcript_markdown')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('career_applications', function (Blueprint $table) { + $table->dropColumn(['certificate_code', 'transcript_markdown']); + }); + } +}; diff --git a/resources/views/front/career/verify.blade.php b/resources/views/front/career/verify.blade.php new file mode 100644 index 0000000..dabefff --- /dev/null +++ b/resources/views/front/career/verify.blade.php @@ -0,0 +1,260 @@ +@extends('layouts.site') + +@section('content') + + +
+
+ +
+
+
+ +
+
+

Güvenli Belge Doğrulama

+

Bu belge, Trunçgil Teknoloji Belge Doğrulama Sistemi ile doğrulanmıştır.

+
+
+ +
+
+
+ + + + +@push('styles') + + +@endpush +@endsection diff --git a/routes/web.php b/routes/web.php index 7feef87..823ba76 100644 --- a/routes/web.php +++ b/routes/web.php @@ -122,6 +122,7 @@ 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::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'); // Payment Process