feat: enhance leave request validation and fix timezone issues

This commit is contained in:
Yoga Pangestu 2026-08-07 15:36:44 +07:00
parent 14140c39cc
commit 4b70da6715
3 changed files with 73 additions and 13 deletions

View File

@ -2,6 +2,7 @@
namespace App\Http\Requests\Admin\HR;
use App\Models\LeaveRequest;
use Illuminate\Foundation\Http\FormRequest;
class LeaveRequestRequest extends FormRequest
@ -13,8 +14,33 @@ public function authorize(): bool
public function rules(): array
{
$leaveRequest = $this->route('leaveRequest');
return [
'start_date' => ['required', 'date', 'after_or_equal:today'],
'start_date' => [
'required',
'date',
'after:today',
function ($attribute, $value, $fail) use ($leaveRequest) {
$employee = $this->user()->employee;
if (! $employee) {
return;
}
$query = LeaveRequest::where('employee_id', $employee->id)
->where('start_date', $value)
->where('status', '!=', 'cancelled');
if ($leaveRequest) {
$query->where('id', '!=', $leaveRequest->id);
}
if ($query->exists()) {
$fail('Anda sudah mengajukan cuti pada tanggal ini.');
}
},
],
'end_date' => ['required', 'date', 'after_or_equal:start_date'],
];
}

View File

@ -0,0 +1,35 @@
# Fix: Validasi Pengajuan Cuti & Timezone
## Tujuan
Memperbaiki 3 masalah pada modul pengajuan cuti:
1. Timezone bug — tanggal yang dipilih mundur 1 hari (user pilih 8, ke-save 7)
2. Tidak bisa mengajukan cuti untuk hari ini
3. Tidak bisa mengajukan cuti di tanggal yang sama lebih dari 1x
## File yang Dibaca
- `resources/js/pages/admin/hr/leave-request/index.tsx` → Frontend form pengajuan cuti
- `app/Http/Requests/Admin/HR/LeaveRequestRequest.php` → Validasi backend
- `app/Services/Admin/HR/LeaveRequestService.php` → Service logic
- `app/Models/LeaveRequest.php` → Model dengan cast `date:Y-m-d`
## Pola yang Ditemukan
- Frontend menggunakan `toISOString().split('T')[0]` untuk format tanggal → **PROBLEM**: `toISOString()` konversi ke UTC, sehingga waktu WIB (UTC+7) mundur 1 hari
- Validasi backend hanya `after_or_equal:today` → bisa submit untuk hari ini
- Tidak ada pengecekan duplikat tanggal cuti per employee
## Perubahan
### 1. Frontend — Fix Timezone (`index.tsx`)
- Tambah helper function `toLocalDateString()` yang menggunakan `getFullYear()`, `getMonth()`, `getDate()` (local time) alih-alih `toISOString()` (UTC)
- Ganti semua `toISOString().split('T')[0]``toLocalDateString(...)`
### 2. Backend — Validasi Stricter (`LeaveRequestRequest.php`)
- `start_date`: `after_or_equal:today``after:today` (tidak bisa hari ini)
- Tambah custom closure rule: cek apakah employee sudah punya leave request dengan `start_date` yang sama (exclude status `cancelled` dan record sendiri saat update)
- Error message: "Anda sudah mengajukan cuti pada tanggal ini."
## Cara Kerja
1. User pilih tanggal 8 Agustus → DatePicker simpan sebagai `Date` object (local time)
2. `toLocalDateString()` format sebagai `"2026-08-08"` tanpa konversi UTC
3. Backend validasi: pastikan tanggal > hari ini + belum ada cuti di tanggal yang sama
4. Service simpan ke DB dengan cast `date:Y-m-d` → tetap `"2026-08-08"`

View File

@ -52,6 +52,13 @@ type Props = {
};
};
function toLocalDateString(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
export default function LeaveRequestIndex({ leaveRequests, filters, filterOptions }: Props) {
const { can } = useCan();
const [createOpen, setCreateOpen] = useState(false);
@ -225,9 +232,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
name="start_date"
value={
startDate
? startDate
.toISOString()
.split('T')[0]
? toLocalDateString(startDate)
: ''
}
/>
@ -248,9 +253,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
name="end_date"
value={
endDate
? endDate
.toISOString()
.split('T')[0]
? toLocalDateString(endDate)
: ''
}
/>
@ -312,9 +315,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
name="start_date"
value={
editingStartDate
? editingStartDate
.toISOString()
.split('T')[0]
? toLocalDateString(editingStartDate)
: ''
}
/>
@ -337,9 +338,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
name="end_date"
value={
editingEndDate
? editingEndDate
.toISOString()
.split('T')[0]
? toLocalDateString(editingEndDate)
: ''
}
/>