4 Commits

5 changed files with 283 additions and 2 deletions
@@ -6,6 +6,7 @@ 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;
@@ -94,8 +95,85 @@ class CareerApplicationForm
->directory('signed_interns')
->downloadable()
->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(),
])
->columnSpanFull(),
]);
}
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()) {
$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()) {
$temp->addDay();
continue;
}
$endDate = $temp->copy();
$temp->addDay();
$count++;
}
$set('internship_end_date', $endDate->format('Y-m-d'));
}
}
+31 -2
View File
@@ -136,11 +136,18 @@ class CareerController extends Controller
$request->validate([
'internship_form' => 'required|file|mimes:pdf,docx,jpg,png,jpeg|max:51200',
'internship_start_date' => 'required|date',
'internship_total_days' => 'required|integer|min:1',
], [
'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.',
'internship_start_date.required' => 'Lütfen staj başlangıç tarihini girin.',
'internship_start_date.date' => 'Geçerli bir başlangıç tarihi girin.',
'internship_total_days.required' => 'Lütfen staj süresini girin.',
'internship_total_days.integer' => 'Staj süresi geçerli bir tam sayı olmalıdır.',
'internship_total_days.min' => 'Staj süresi en az 1 gün olmalıdır.',
]);
$intern = CareerApplication::findOrFail(session('intern_id'));
@@ -152,11 +159,33 @@ class CareerController extends Controller
}
$path = $request->file('internship_form')->store('to_be_signed_interns', 'public');
// Calculate end date based on weekdays and duration
$startDate = \Carbon\Carbon::parse($request->internship_start_date);
$daysToAdd = intval($request->internship_total_days);
$endDate = $startDate->copy();
$count = 0;
$temp = $startDate->copy();
while ($count < $daysToAdd) {
if ($temp->isWeekend()) {
$temp->addDay();
continue;
}
$endDate = $temp->copy();
$temp->addDay();
$count++;
}
$intern->update([
'to_be_signed_internship_form_path' => $path
'to_be_signed_internship_form_path' => $path,
'internship_start_date' => $request->internship_start_date,
'internship_end_date' => $endDate->format('Y-m-d'),
'internship_total_days' => $daysToAdd,
]);
return redirect()->back()->with('success', 'İmzalanacak staj formunuz başarıyla yüklendi.');
return redirect()->back()->with('success', 'İmzalanacak staj formunuz ve staj tarihleri başarıyla kaydedildi.');
}
return redirect()->back()->with('error', 'Dosya yüklenirken bir hata oluştu.');
+3
View File
@@ -25,6 +25,9 @@ class CareerApplication extends Model
'password',
'signed_internship_form_path',
'to_be_signed_internship_form_path',
'internship_start_date',
'internship_end_date',
'internship_total_days',
];
/**
@@ -0,0 +1,30 @@
<?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->date('internship_start_date')->nullable()->after('to_be_signed_internship_form_path');
$table->date('internship_end_date')->nullable()->after('internship_start_date');
$table->integer('internship_total_days')->nullable()->after('internship_end_date');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('career_applications', function (Blueprint $table) {
$table->dropColumn(['internship_start_date', 'internship_end_date', 'internship_total_days']);
});
}
};
@@ -86,6 +86,27 @@
@else Beklemede @endif
</span>
</div>
@if($intern->internship_start_date)
<div class="border-t border-slate-100 pt-4">
<span class="text-xs text-slate-400 font-semibold block">Staj Başlangıç Tarihi</span>
<span class="text-sm font-semibold text-slate-700 block mt-0.5">{{ \Carbon\Carbon::parse($intern->internship_start_date)->format('d.m.Y') }}</span>
</div>
@endif
@if($intern->internship_end_date)
<div>
<span class="text-xs text-slate-400 font-semibold block">Staj Bitiş Tarihi</span>
<span class="text-sm font-semibold text-slate-700 block mt-0.5">{{ \Carbon\Carbon::parse($intern->internship_end_date)->format('d.m.Y') }}</span>
</div>
@endif
@if($intern->internship_total_days)
<div>
<span class="text-xs text-slate-400 font-semibold block">Toplam Süre</span>
<span class="text-sm font-bold text-blue-600 block mt-0.5">{{ $intern->internship_total_days }} İş Günü <span class="text-xs text-slate-400 font-normal">(Haftada 5 Gün)</span></span>
</div>
@endif
</div>
</div>
</div>
@@ -158,6 +179,77 @@
<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
<!-- Company Legal Info Copy Box -->
<div class="bg-slate-50 border border-slate-200/60 rounded-2xl p-5 text-sm text-slate-600">
<div class="flex items-center gap-2 mb-3">
<i class="uil uil-info-circle text-blue-600 text-lg flex-shrink-0"></i>
<h5 class="font-extrabold text-slate-800 text-sm tracking-tight">Şirket Legal Bilgilerimiz</h5>
</div>
<p class="text-xs text-slate-400 mb-4 leading-normal">
Lütfen staj formunuzu doldururken şirketimize ait aşağıdaki resmi bilgileri kullanın. Sağdaki kopyalama butonlarını kullanarak bilgileri kolayca kopyalayabilirsiniz.
</p>
<div class="space-y-2.5">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 bg-white px-3 py-2 rounded-xl border border-slate-100/80">
<span class="text-xs text-slate-400 font-bold sm:w-1/4">Şirket Unvanı</span>
<div class="flex items-center justify-between sm:w-3/4 w-full gap-2">
<span id="legal-title" class="text-xs font-bold text-slate-700 select-all break-all">TRUNÇGİL TEKNOLOJİ SANAYİ VE TİCARET LİMİTED ŞİRKETİ</span>
<button type="button" onclick="copyToClipboard('legal-title', this)" class="text-slate-400 hover:text-blue-600 transition-colors p-1 flex-shrink-0" title="Kopyala">
<i class="uil uil-copy text-sm"></i>
</button>
</div>
</div>
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 bg-white px-3 py-2 rounded-xl border border-slate-100/80">
<span class="text-xs text-slate-400 font-bold sm:w-1/4">Adres</span>
<div class="flex items-center justify-between sm:w-3/4 w-full gap-2">
<span id="legal-address" class="text-xs font-bold text-slate-700 select-all break-all">Çamtepe Mah. Mahmut Tevfik Atay Bul. Gaziantep Teknopark No: 4A İç Kapı No: 1 Şahinbey / Gaziantep</span>
<button type="button" onclick="copyToClipboard('legal-address', this)" class="text-slate-400 hover:text-blue-600 transition-colors p-1 flex-shrink-0" title="Kopyala">
<i class="uil uil-copy text-sm"></i>
</button>
</div>
</div>
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 bg-white px-3 py-2 rounded-xl border border-slate-100/80">
<span class="text-xs text-slate-400 font-bold sm:w-1/4">Vergi Dairesi & No</span>
<div class="flex items-center justify-between sm:w-3/4 w-full gap-2">
<span id="legal-tax" class="text-xs font-bold text-slate-700 select-all break-all">Şahinbey V.D. / 8591492435</span>
<button type="button" onclick="copyToClipboard('legal-tax', this)" class="text-slate-400 hover:text-blue-600 transition-colors p-1 flex-shrink-0" title="Kopyala">
<i class="uil uil-copy text-sm"></i>
</button>
</div>
</div>
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 bg-white px-3 py-2 rounded-xl border border-slate-100/80">
<span class="text-xs text-slate-400 font-bold sm:w-1/4">MERSİS No</span>
<div class="flex items-center justify-between sm:w-3/4 w-full gap-2">
<span id="legal-mersis" class="text-xs font-bold text-slate-700 select-all break-all">0859149243500001</span>
<button type="button" onclick="copyToClipboard('legal-mersis', this)" class="text-slate-400 hover:text-blue-600 transition-colors p-1 flex-shrink-0" title="Kopyala">
<i class="uil uil-copy text-sm"></i>
</button>
</div>
</div>
</div>
</div>
<div class="space-y-4">
<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" />
</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" />
</div>
</div>
<div>
<label for="internship_end_date" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-1.5">Staj Bitiş Tarihi (Otomatik)</label>
<input type="date" name="internship_end_date" id="internship_end_date" readonly class="w-full px-4 py-3 rounded-xl border border-slate-200 bg-slate-50 text-slate-500 cursor-not-allowed focus:outline-none text-sm font-semibold transition-all" value="{{ $intern->internship_end_date }}" />
</div>
</div>
<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>
@@ -241,6 +333,55 @@
submitBtn.classList.add('opacity-50', 'cursor-not-allowed');
}
}
function calculateEndDateFrontend() {
const startDateVal = document.getElementById('internship_start_date').value;
const totalDaysVal = document.getElementById('internship_total_days').value;
const endDateInput = document.getElementById('internship_end_date');
if (!startDateVal || !totalDaysVal || totalDaysVal <= 0) {
return;
}
let date = new Date(startDateVal);
let daysToAdd = parseInt(totalDaysVal);
let count = 0;
let endDate = new Date(startDateVal);
while (count < daysToAdd) {
const dayOfWeek = date.getDay();
if (dayOfWeek === 6 || dayOfWeek === 0) { // 6 = Saturday, 0 = Sunday
date.setDate(date.getDate() + 1);
continue;
}
endDate = new Date(date);
date.setDate(date.getDate() + 1);
count++;
}
// Format to YYYY-MM-DD
const yyyy = endDate.getFullYear();
const mm = String(endDate.getMonth() + 1).padStart(2, '0');
const dd = String(endDate.getDate()).padStart(2, '0');
endDateInput.value = `${yyyy}-${mm}-${dd}`;
}
// Trigger calculation once on load if values exist
document.addEventListener('DOMContentLoaded', function() {
calculateEndDateFrontend();
});
function copyToClipboard(elementId, btn) {
const text = document.getElementById(elementId).textContent;
navigator.clipboard.writeText(text).then(() => {
const icon = btn.querySelector('i');
const originalClass = icon.className;
icon.className = 'uil uil-check text-green-500';
setTimeout(() => {
icon.className = originalClass;
}, 2000);
});
}
</script>
<style>
.shadow-xl {