refactor: migrate career applications to separate job and intern Filament resources

This commit is contained in:
Ümit Tunç
2026-06-08 18:17:56 +03:00
parent 5b07f385b1
commit 082fb33af3
17 changed files with 773 additions and 224 deletions
@@ -1,55 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CareerApplications;
use App\Filament\Admin\Resources\CareerApplications\Pages\CreateCareerApplication;
use App\Filament\Admin\Resources\CareerApplications\Pages\EditCareerApplication;
use App\Filament\Admin\Resources\CareerApplications\Pages\ListCareerApplications;
use App\Filament\Admin\Resources\CareerApplications\Schemas\CareerApplicationForm;
use App\Filament\Admin\Resources\CareerApplications\Tables\CareerApplicationsTable;
use App\Models\CareerApplication;
use BackedEnum;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
class CareerApplicationResource extends Resource
{
protected static ?string $model = CareerApplication::class;
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-user-group';
public static function getNavigationLabel(): string
{
return __('career.navigation_label');
}
public static function getModelLabel(): string
{
return __('career.model_label');
}
public static function getPluralModelLabel(): string
{
return __('career.plural_model_label');
}
public static function form(Schema $schema): Schema
{
return CareerApplicationForm::configure($schema);
}
public static function table(Table $table): Table
{
return CareerApplicationsTable::configure($table);
}
public static function getPages(): array
{
return [
'index' => ListCareerApplications::route('/'),
'create' => CreateCareerApplication::route('/create'),
'edit' => EditCareerApplication::route('/{record}/edit'),
];
}
}
@@ -1,11 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CareerApplications\Pages;
use App\Filament\Admin\Resources\CareerApplications\CareerApplicationResource;
use Filament\Resources\Pages\CreateRecord;
class CreateCareerApplication extends CreateRecord
{
protected static string $resource = CareerApplicationResource::class;
}
@@ -1,19 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CareerApplications\Pages;
use App\Filament\Admin\Resources\CareerApplications\CareerApplicationResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListCareerApplications extends ListRecords
{
protected static string $resource = CareerApplicationResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}
@@ -1,124 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\CareerApplications\Tables;
use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Storage;
class CareerApplicationsTable
{
public static function configure(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('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');
}
}
@@ -1,25 +1,59 @@
<?php
namespace App\Filament\Admin\Resources\CareerApplications\Schemas;
namespace App\Filament\Admin\Resources\InternApplications;
use App\Models\CareerApplication;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\DatePicker;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Illuminate\Support\Facades\Hash;
use Filament\Actions\Action;
use Filament\Schemas\Components\Utilities\Set;
use Illuminate\Support\Str;
use Filament\Forms\Components\MarkdownEditor;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Livewire;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Filament\Schemas\Components\Utilities\Set;
use Illuminate\Database\Eloquent\Builder;
class CareerApplicationForm
class InternApplicationResource extends Resource
{
public static function configure(Schema $schema): Schema
protected static ?string $model = CareerApplication::class;
protected static \BackedEnum|string|null $navigationIcon = 'heroicon-o-academic-cap';
public static function getNavigationLabel(): string
{
return __('career.internship_title', ['default' => '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'),
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Admin\Resources\InternApplications\Pages;
use App\Filament\Admin\Resources\InternApplications\InternApplicationResource;
use Filament\Resources\Pages\CreateRecord;
class CreateInternApplication extends CreateRecord
{
protected static string $resource = InternApplicationResource::class;
}
@@ -1,14 +1,14 @@
<?php
namespace App\Filament\Admin\Resources\CareerApplications\Pages;
namespace App\Filament\Admin\Resources\InternApplications\Pages;
use App\Filament\Admin\Resources\CareerApplications\CareerApplicationResource;
use App\Filament\Admin\Resources\InternApplications\InternApplicationResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditCareerApplication extends EditRecord
class EditInternApplication extends EditRecord
{
protected static string $resource = CareerApplicationResource::class;
protected static string $resource = InternApplicationResource::class;
protected function getHeaderActions(): array
{
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Admin\Resources\InternApplications\Pages;
use App\Filament\Admin\Resources\InternApplications\InternApplicationResource;
use Filament\Resources\Pages\ListRecords;
class ListInternApplications extends ListRecords
{
protected static string $resource = InternApplicationResource::class;
}
@@ -0,0 +1,218 @@
<?php
namespace App\Filament\Admin\Resources\JobApplications;
use App\Models\CareerApplication;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables\Table;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\Builder;
class JobApplicationResource extends Resource
{
protected static ?string $model = CareerApplication::class;
protected static \BackedEnum|string|null $navigationIcon = 'heroicon-o-briefcase';
public static function getNavigationLabel(): string
{
return __('career.job_application_title', ['default' => 'İş 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'),
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Admin\Resources\JobApplications\Pages;
use App\Filament\Admin\Resources\JobApplications\JobApplicationResource;
use Filament\Resources\Pages\CreateRecord;
class CreateJobApplication extends CreateRecord
{
protected static string $resource = JobApplicationResource::class;
}
@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\JobApplications\Pages;
use App\Filament\Admin\Resources\JobApplications\JobApplicationResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditJobApplication extends EditRecord
{
protected static string $resource = JobApplicationResource::class;
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Admin\Resources\JobApplications\Pages;
use App\Filament\Admin\Resources\JobApplications\JobApplicationResource;
use Filament\Resources\Pages\ListRecords;
class ListJobApplications extends ListRecords
{
protected static string $resource = JobApplicationResource::class;
}
+15
View File
@@ -312,4 +312,19 @@ class CareerController extends Controller
session()->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ı.',
]
]);
}
}
+14
View File
@@ -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.
*