feat: implement internship form upload functionality for interns with database migration and UI integration

This commit is contained in:
Ümit Tunç
2026-06-01 07:53:59 +03:00
parent e0941fe931
commit 76081f0e85
6 changed files with 183 additions and 0 deletions
@@ -80,6 +80,13 @@ class CareerApplicationForm
->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')
+34
View File
@@ -128,6 +128,40 @@ class CareerController extends Controller
]);
}
public function uploadInternshipForm(Request $request)
{
if (!session()->has('intern_id')) {
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.');
}
$request->validate([
'internship_form' => 'required|file|mimes:pdf,docx,jpg,png,jpeg|max:51200',
], [
'internship_form.required' => 'Lütfen bir dosya seçin.',
'internship_form.file' => 'Yüklenen öğe geçerli bir dosya olmalıdır.',
'internship_form.mimes' => 'Yalnızca PDF, DOCX, JPG ve PNG formatındaki dosyalar kabul edilir.',
'internship_form.max' => 'Dosya boyutu en fazla 50MB olabilir.',
]);
$intern = CareerApplication::findOrFail(session('intern_id'));
if ($request->hasFile('internship_form')) {
// Delete old file if exists
if ($intern->to_be_signed_internship_form_path) {
Storage::disk('public')->delete($intern->to_be_signed_internship_form_path);
}
$path = $request->file('internship_form')->store('to_be_signed_interns', 'public');
$intern->update([
'to_be_signed_internship_form_path' => $path
]);
return redirect()->back()->with('success', 'İmzalanacak staj formunuz başarıyla yüklendi.');
}
return redirect()->back()->with('error', 'Dosya yüklenirken bir hata oluştu.');
}
public function internLogout()
{
session()->forget('intern_id');
+1
View File
@@ -24,6 +24,7 @@ class CareerApplication extends Model
'username',
'password',
'signed_internship_form_path',
'to_be_signed_internship_form_path',
];
/**
@@ -0,0 +1,28 @@
<?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->string('to_be_signed_internship_form_path')->nullable()->after('signed_internship_form_path');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('career_applications', function (Blueprint $table) {
$table->dropColumn('to_be_signed_internship_form_path');
});
}
};
@@ -21,6 +21,35 @@
</form>
</div>
<!-- Session & Validation Alerts -->
@if(session('success'))
<div class="bg-green-50 border border-green-200 text-green-700 px-6 py-4 rounded-3xl mb-8 flex items-center gap-3 shadow-sm">
<i class="uil uil-check-circle text-2xl flex-shrink-0"></i>
<span class="font-bold text-sm">{{ session('success') }}</span>
</div>
@endif
@if(session('error'))
<div class="bg-red-50 border border-red-200 text-[#e31e24] px-6 py-4 rounded-3xl mb-8 flex items-center gap-3 shadow-sm">
<i class="uil uil-exclamation-octagon text-2xl flex-shrink-0"></i>
<span class="font-bold text-sm">{{ session('error') }}</span>
</div>
@endif
@if($errors->any())
<div class="bg-red-50 border border-red-200 text-[#e31e24] px-6 py-4 rounded-3xl mb-8 shadow-sm">
<div class="flex items-center gap-3 mb-2">
<i class="uil uil-exclamation-octagon text-2xl flex-shrink-0"></i>
<span class="font-bold text-sm">Lütfen aşağıdaki hataları düzeltin:</span>
</div>
<ul class="list-disc list-inside text-xs font-semibold space-y-1 ml-1">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- Application Info & Document Download Grid -->
<div class="grid grid-cols-1 md:grid-cols-12 gap-8">
@@ -87,6 +116,64 @@
@endif
</div>
<!-- Intern Upload: To Be Signed Form Card -->
<div class="p-5 rounded-2xl border border-slate-100 bg-slate-50/50 hover:border-slate-200 transition-all">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-4">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-blue-50 text-blue-600 flex items-center justify-center text-2xl flex-shrink-0">
<i class="uil uil-file-upload-alt"></i>
</div>
<div>
<h4 class="font-bold text-slate-800 text-base">İmzalanacak Staj Formu</h4>
@if($intern->to_be_signed_internship_form_path)
<p class="text-xs text-green-600 font-semibold mt-0.5">Staj formunuz başarıyla yüklendi. İmza işlemleri için hazır.</p>
@else
<p class="text-xs text-slate-400 mt-0.5">İmzalanmasını istediğiniz staj formunu buradan yükleyebilirsiniz.</p>
@endif
</div>
</div>
<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">
<i class="uil uil-arrow-down-tray"></i>
<span>Görüntüle</span>
</a>
<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>
@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>
<!-- Form Upload Dropzone Area -->
<div id="upload-form-wrapper" class="{{ $intern->to_be_signed_internship_form_path ? 'hidden' : '' }} 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
<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>
<p class="text-sm font-bold text-slate-700">Dosya Seçin veya Sürükleyin</p>
<p class="text-xs text-slate-400 mt-1">PDF, DOCX, JPG veya PNG (Maks. 50MB)</p>
<p id="selected-file-name" class="text-xs font-bold text-blue-600 mt-3"></p>
</div>
<div class="flex justify-end">
<button type="submit" id="submit-upload-btn" disabled class="px-6 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-sm transition-all flex items-center gap-2 opacity-50 cursor-not-allowed">
<i class="uil uil-check"></i>
<span>Dosyayı Kaydet</span>
</button>
</div>
</form>
</div>
</div>
<!-- Signed Internship Form Card -->
<div class="p-5 rounded-2xl border border-slate-100 bg-slate-50/50 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 hover:border-slate-200 transition-all">
<div class="flex items-center gap-4">
@@ -128,6 +215,31 @@
@push('styles')
<script src="https://cdn.tailwindcss.com"></script>
<script>
function handleFileSelected(input) {
const fileNameElement = document.getElementById('selected-file-name');
const submitBtn = document.getElementById('submit-upload-btn');
if (input.files && input.files.length > 0) {
const file = input.files[0];
const fileSizeMB = file.size / (1024 * 1024);
if (fileSizeMB > 50) {
alert('Dosya boyutu 50MB\'ı aşamaz.');
input.value = '';
fileNameElement.textContent = '';
submitBtn.disabled = true;
submitBtn.classList.add('opacity-50', 'cursor-not-allowed');
return;
}
fileNameElement.textContent = 'Seçilen dosya: ' + file.name + ' (' + fileSizeMB.toFixed(2) + ' MB)';
submitBtn.disabled = false;
submitBtn.classList.remove('opacity-50', 'cursor-not-allowed');
} else {
fileNameElement.textContent = '';
submitBtn.disabled = true;
submitBtn.classList.add('opacity-50', 'cursor-not-allowed');
}
}
</script>
<style>
.shadow-xl {
box-shadow: 0 1rem 3rem rgba(30, 41, 59, 0.04) !important;
+1
View File
@@ -119,6 +119,7 @@ Route::post('/kariyer', [\App\Http\Controllers\CareerController::class, 'store']
Route::get('/stajyer/giris', [\App\Http\Controllers\CareerController::class, 'internLoginForm'])->name('intern.login');
Route::post('/stajyer/giris', [\App\Http\Controllers\CareerController::class, 'internLogin'])->name('intern.login.submit');
Route::get('/stajyer/panel', [\App\Http\Controllers\CareerController::class, 'internDashboard'])->name('intern.dashboard');
Route::post('/stajyer/form-yukle', [\App\Http\Controllers\CareerController::class, 'uploadInternshipForm'])->name('intern.upload-form');
Route::post('/stajyer/cikis', [\App\Http\Controllers\CareerController::class, 'internLogout'])->name('intern.logout');
// Payment Process