feat: add filtering options for leave requests by status in LeaveRequestIndex component
This commit is contained in:
parent
3f12a65b05
commit
cc2b6c6b3a
@ -7,6 +7,7 @@
|
||||
use App\Models\LeaveRequest;
|
||||
use App\Services\Admin\HR\LeaveRequestService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
@ -16,10 +17,13 @@ public function __construct(
|
||||
private LeaveRequestService $service
|
||||
) {}
|
||||
|
||||
public function index(): Response
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
return Inertia::render('admin/hr/leave-request/index', [
|
||||
'leaveRequests' => $this->service->getAll(),
|
||||
'leaveRequests' => $this->service->getAll(
|
||||
$request->only(['status'])
|
||||
),
|
||||
'filters' => $request->only(['status']),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -10,10 +10,13 @@
|
||||
|
||||
class LeaveRequestService
|
||||
{
|
||||
public function getAll(): Collection
|
||||
public function getAll(array $filters = []): Collection
|
||||
{
|
||||
return LeaveRequest::select('id', 'employee_id', 'start_date', 'end_date', 'total_days', 'status', 'created_at')
|
||||
->with(['employee.user.userProfile'])
|
||||
->when($filters['status'] ?? null, function ($query, $status) {
|
||||
$query->where('status', $status);
|
||||
})
|
||||
->latest()
|
||||
->get();
|
||||
}
|
||||
|
||||
@ -11,18 +11,23 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { approve, destroy, index as leaveRequestIndex, reject, store, update } from '@/routes/admin/hr/leave-requests';
|
||||
import { Form, Head, router } from '@inertiajs/react';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { Filter, Plus, X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { LeaveRequest } from './columns';
|
||||
import { createLeaveRequestColumns } from './columns';
|
||||
|
||||
type Props = {
|
||||
leaveRequests: LeaveRequest[];
|
||||
filters: {
|
||||
status?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default function LeaveRequestIndex({ leaveRequests }: Props) {
|
||||
export default function LeaveRequestIndex({ leaveRequests, filters }: Props) {
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<LeaveRequest | null>(null);
|
||||
const [deleting, setDeleting] = useState<LeaveRequest | null>(null);
|
||||
@ -32,6 +37,9 @@ export default function LeaveRequestIndex({ leaveRequests }: Props) {
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
|
||||
const [editingStartDate, setEditingStartDate] = useState<Date | undefined>(undefined);
|
||||
const [editingEndDate, setEditingEndDate] = useState<Date | undefined>(undefined);
|
||||
const [filterOpen, setFilterOpen] = useState(false);
|
||||
|
||||
const hasActiveFilters = filters.status;
|
||||
|
||||
useEffect(() => {
|
||||
if (editing) {
|
||||
@ -43,6 +51,29 @@ export default function LeaveRequestIndex({ leaveRequests }: Props) {
|
||||
}
|
||||
}, [editing]);
|
||||
|
||||
function applyFilter(key: string, value: string) {
|
||||
const newFilters = { ...filters };
|
||||
|
||||
if (value === '' || value === 'all') {
|
||||
delete newFilters[key as keyof typeof newFilters];
|
||||
} else {
|
||||
newFilters[key as keyof typeof newFilters] = value;
|
||||
}
|
||||
|
||||
router.get(leaveRequestIndex(), newFilters, {
|
||||
preserveState: true,
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
router.get(leaveRequestIndex(), {}, {
|
||||
preserveState: true,
|
||||
replace: true,
|
||||
});
|
||||
setFilterOpen(false);
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
if (!deleting) {
|
||||
return;
|
||||
@ -80,6 +111,61 @@ export default function LeaveRequestIndex({ leaveRequests }: Props) {
|
||||
handleReject: (leaveRequest) => setRejecting(leaveRequest),
|
||||
});
|
||||
|
||||
const filterToolbar = (
|
||||
<Popover open={filterOpen} onOpenChange={setFilterOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<Filter className="h-4 w-4" />
|
||||
Filter
|
||||
{hasActiveFilters && (
|
||||
<span className="ml-1 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
|
||||
{Object.values(filters).filter(Boolean).length}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-64" align="end">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium">Filter</span>
|
||||
{hasActiveFilters && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
<X className="mr-1 h-3 w-3" />
|
||||
Hapus Semua
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-xs text-muted-foreground">
|
||||
Status
|
||||
</label>
|
||||
<Select
|
||||
value={filters.status ?? 'all'}
|
||||
onValueChange={(value) => applyFilter('status', value)}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Semua Status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Semua Status</SelectItem>
|
||||
<SelectItem value="pending">Menunggu</SelectItem>
|
||||
<SelectItem value="approved">Disetujui</SelectItem>
|
||||
<SelectItem value="rejected">Ditolak</SelectItem>
|
||||
<SelectItem value="cancelled">Dibatalkan</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head title="Cuti" />
|
||||
@ -175,6 +261,7 @@ export default function LeaveRequestIndex({ leaveRequests }: Props) {
|
||||
searchKey="employee.user.user_profile.full_name"
|
||||
searchPlaceholder="Cari nama karyawan..."
|
||||
emptyText="Belum ada data permohonan cuti."
|
||||
toolbar={filterToolbar}
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
|
||||
845
tests/Feature/Admin/HR/LeaveRequestTest.php
Normal file
845
tests/Feature/Admin/HR/LeaveRequestTest.php
Normal file
@ -0,0 +1,845 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\LeaveRequestStatus;
|
||||
use App\Models\Employee;
|
||||
use App\Models\LeaveRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| AUTHENTICATION
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('guests are redirected to the login page', function () {
|
||||
$response = $this->get(route('admin.hr.leave-requests.index'));
|
||||
$response->assertRedirect(route('login'));
|
||||
});
|
||||
|
||||
test('authenticated users can visit the leave request index page', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index'));
|
||||
$response->assertOk();
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| INDEX PAGE
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('leave request index page displays leave requests', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->count(3)->create();
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index'));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 3)
|
||||
);
|
||||
});
|
||||
|
||||
test('index page works when no leave requests exist', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index'));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 0)
|
||||
);
|
||||
});
|
||||
|
||||
test('index page displays correct count after delete', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->count(2)->create();
|
||||
|
||||
$leaveRequest = LeaveRequest::first();
|
||||
$this->delete(route('admin.hr.leave-requests.destroy', $leaveRequest));
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index'));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 1)
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| FILTER - STATUS
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('index can filter by status pending', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::PENDING]);
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]);
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::REJECTED]);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'pending']));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 2)
|
||||
);
|
||||
});
|
||||
|
||||
test('index can filter by status approved', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]);
|
||||
LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::APPROVED]);
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::REJECTED]);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'approved']));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 2)
|
||||
);
|
||||
});
|
||||
|
||||
test('index can filter by status rejected', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]);
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]);
|
||||
LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::REJECTED]);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'rejected']));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 2)
|
||||
);
|
||||
});
|
||||
|
||||
test('index can filter by status cancelled', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]);
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]);
|
||||
LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::CANCELLED]);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'cancelled']));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 2)
|
||||
);
|
||||
});
|
||||
|
||||
test('index without status filter shows all leave requests', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]);
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]);
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::REJECTED]);
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::CANCELLED]);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index'));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 4)
|
||||
);
|
||||
});
|
||||
|
||||
test('index passes status filter to view', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'approved']));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->where('filters.status', 'approved')
|
||||
);
|
||||
});
|
||||
|
||||
test('index passes empty filters when no status specified', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index'));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->where('filters', [])
|
||||
);
|
||||
});
|
||||
|
||||
test('index with invalid status filter shows no leave requests', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'invalid_status']));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 0)
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| STORE
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('leave request can be created', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$employee = Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('admin.hr.leave-requests.index'));
|
||||
|
||||
$this->assertDatabaseCount('leave_requests', 1);
|
||||
$this->assertDatabaseHas('leave_requests', [
|
||||
'employee_id' => $employee->id,
|
||||
'status' => LeaveRequestStatus::PENDING->value,
|
||||
'total_days' => 3,
|
||||
]);
|
||||
});
|
||||
|
||||
test('leave request creates with correct total_days for single day', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('leave_requests', [
|
||||
'total_days' => 1,
|
||||
]);
|
||||
});
|
||||
|
||||
test('leave request creates with correct total_days for multi day', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(5)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('leave_requests', [
|
||||
'total_days' => 5,
|
||||
]);
|
||||
});
|
||||
|
||||
test('leave request always creates with pending status', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('leave_requests', [
|
||||
'status' => LeaveRequestStatus::PENDING->value,
|
||||
]);
|
||||
});
|
||||
|
||||
test('store flashes success toast via Inertia', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| STORE VALIDATION
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('leave request start_date is required', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => '',
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('start_date');
|
||||
});
|
||||
|
||||
test('leave request start_date must be valid date', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => 'not-a-date',
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('start_date');
|
||||
});
|
||||
|
||||
test('leave request end_date is required', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => '',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('end_date');
|
||||
});
|
||||
|
||||
test('leave request end_date must be valid date', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => 'not-a-date',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('end_date');
|
||||
});
|
||||
|
||||
test('leave request end_date must be after or equal start_date', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(5)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('end_date');
|
||||
});
|
||||
|
||||
test('leave request end_date equal to start_date is accepted', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$date = now()->addDays(1)->format('Y-m-d');
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => $date,
|
||||
'end_date' => $date,
|
||||
]);
|
||||
|
||||
$response->assertSessionHasNoErrors();
|
||||
});
|
||||
|
||||
test('leave request with past start_date is rejected', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->subDay()->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors('start_date');
|
||||
});
|
||||
|
||||
test('guest cannot create leave request', function () {
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
$this->assertDatabaseCount('leave_requests', 0);
|
||||
});
|
||||
|
||||
test('user tries to create leave request without submitting any data', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.store'), []);
|
||||
|
||||
$response->assertSessionHasErrors(['start_date', 'end_date']);
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| UPDATE
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('leave request can be updated', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$employee = Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'employee_id' => $employee->id,
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
'total_days' => 3,
|
||||
]);
|
||||
|
||||
$response = $this->put(route('admin.hr.leave-requests.update', $leaveRequest), [
|
||||
'start_date' => now()->addDays(5)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(7)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('admin.hr.leave-requests.index'));
|
||||
|
||||
$leaveRequest->refresh();
|
||||
expect($leaveRequest->total_days)->toBe(3);
|
||||
});
|
||||
|
||||
test('update recalculates total_days correctly', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$employee = Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'employee_id' => $employee->id,
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
'total_days' => 3,
|
||||
]);
|
||||
|
||||
$this->put(route('admin.hr.leave-requests.update', $leaveRequest), [
|
||||
'start_date' => now()->addDays(10)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(12)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$leaveRequest->refresh();
|
||||
expect($leaveRequest->total_days)->toBe(3);
|
||||
});
|
||||
|
||||
test('update flashes success toast via Inertia', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create();
|
||||
|
||||
$response = $this->put(route('admin.hr.leave-requests.update', $leaveRequest), [
|
||||
'start_date' => now()->addDays(5)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(7)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
});
|
||||
|
||||
test('updating non-existent leave request returns 404', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->put(route('admin.hr.leave-requests.update', 999999), [
|
||||
'start_date' => now()->addDays(5)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(7)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response->assertStatus(404);
|
||||
});
|
||||
|
||||
test('guest cannot update leave request', function () {
|
||||
$leaveRequest = LeaveRequest::factory()->create();
|
||||
|
||||
$response = $this->put(route('admin.hr.leave-requests.update', $leaveRequest), [
|
||||
'start_date' => now()->addDays(5)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(7)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| DELETE
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('leave request can be deleted', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create();
|
||||
|
||||
$response = $this->delete(route('admin.hr.leave-requests.destroy', $leaveRequest));
|
||||
|
||||
$response
|
||||
->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('admin.hr.leave-requests.index'));
|
||||
|
||||
$this->assertSoftDeleted('leave_requests', ['id' => $leaveRequest->id]);
|
||||
});
|
||||
|
||||
test('delete flashes success toast via Inertia', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create();
|
||||
|
||||
$response = $this->delete(route('admin.hr.leave-requests.destroy', $leaveRequest));
|
||||
|
||||
$response->assertRedirect();
|
||||
});
|
||||
|
||||
test('deleting non-existent leave request returns 404', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->delete(route('admin.hr.leave-requests.destroy', 999999));
|
||||
|
||||
$response->assertStatus(404);
|
||||
});
|
||||
|
||||
test('guest cannot delete leave request', function () {
|
||||
$leaveRequest = LeaveRequest::factory()->create();
|
||||
|
||||
$response = $this->delete(route('admin.hr.leave-requests.destroy', $leaveRequest));
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
$this->assertDatabaseCount('leave_requests', 1);
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| APPROVE
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('pending leave request can be approved', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'status' => LeaveRequestStatus::PENDING,
|
||||
]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.approve', $leaveRequest));
|
||||
|
||||
$response
|
||||
->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('admin.hr.leave-requests.index'));
|
||||
|
||||
$leaveRequest->refresh();
|
||||
expect($leaveRequest->status)->toBe(LeaveRequestStatus::APPROVED);
|
||||
expect($leaveRequest->verified_by_id)->toBe($user->id);
|
||||
expect($leaveRequest->verified_at)->not->toBeNull();
|
||||
});
|
||||
|
||||
test('approve flashes success toast via Inertia', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'status' => LeaveRequestStatus::PENDING,
|
||||
]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.approve', $leaveRequest));
|
||||
|
||||
$response->assertRedirect();
|
||||
});
|
||||
|
||||
test('approving non-existent leave request returns 404', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.approve', 999999));
|
||||
|
||||
$response->assertStatus(404);
|
||||
});
|
||||
|
||||
test('guest cannot approve leave request', function () {
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'status' => LeaveRequestStatus::PENDING,
|
||||
]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.approve', $leaveRequest));
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
|
||||
$leaveRequest->refresh();
|
||||
expect($leaveRequest->status)->toBe(LeaveRequestStatus::PENDING);
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| REJECT
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('pending leave request can be rejected', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'status' => LeaveRequestStatus::PENDING,
|
||||
]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.reject', $leaveRequest));
|
||||
|
||||
$response
|
||||
->assertSessionHasNoErrors()
|
||||
->assertRedirect(route('admin.hr.leave-requests.index'));
|
||||
|
||||
$leaveRequest->refresh();
|
||||
expect($leaveRequest->status)->toBe(LeaveRequestStatus::REJECTED);
|
||||
expect($leaveRequest->verified_by_id)->toBe($user->id);
|
||||
expect($leaveRequest->verified_at)->not->toBeNull();
|
||||
});
|
||||
|
||||
test('reject flashes success toast via Inertia', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'status' => LeaveRequestStatus::PENDING,
|
||||
]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.reject', $leaveRequest));
|
||||
|
||||
$response->assertRedirect();
|
||||
});
|
||||
|
||||
test('rejecting non-existent leave request returns 404', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.reject', 999999));
|
||||
|
||||
$response->assertStatus(404);
|
||||
});
|
||||
|
||||
test('guest cannot reject leave request', function () {
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'status' => LeaveRequestStatus::PENDING,
|
||||
]);
|
||||
|
||||
$response = $this->post(route('admin.hr.leave-requests.reject', $leaveRequest));
|
||||
|
||||
$response->assertRedirect(route('login'));
|
||||
|
||||
$leaveRequest->refresh();
|
||||
expect($leaveRequest->status)->toBe(LeaveRequestStatus::PENDING);
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| DATA INTEGRITY
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('created leave request has correct timestamps', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$leaveRequest = LeaveRequest::first();
|
||||
expect($leaveRequest->created_at)->not->toBeNull();
|
||||
expect($leaveRequest->updated_at)->not->toBeNull();
|
||||
});
|
||||
|
||||
test('leave request factory creates valid leave request', function () {
|
||||
$leaveRequest = LeaveRequest::factory()->create();
|
||||
|
||||
expect($leaveRequest->employee)->not->toBeNull();
|
||||
expect($leaveRequest->start_date)->not->toBeNull();
|
||||
expect($leaveRequest->end_date)->not->toBeNull();
|
||||
expect($leaveRequest->total_days)->toBeGreaterThanOrEqual(1);
|
||||
expect($leaveRequest->status)->toBeInstanceOf(LeaveRequestStatus::class);
|
||||
});
|
||||
|
||||
test('approved leave request has verified_by_id and verified_at', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'status' => LeaveRequestStatus::PENDING,
|
||||
]);
|
||||
|
||||
$this->post(route('admin.hr.leave-requests.approve', $leaveRequest));
|
||||
|
||||
$leaveRequest->refresh();
|
||||
expect($leaveRequest->verified_by_id)->toBe($user->id);
|
||||
expect($leaveRequest->verified_at)->not->toBeNull();
|
||||
});
|
||||
|
||||
test('rejected leave request has verified_by_id and verified_at', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
$leaveRequest = LeaveRequest::factory()->create([
|
||||
'status' => LeaveRequestStatus::PENDING,
|
||||
]);
|
||||
|
||||
$this->post(route('admin.hr.leave-requests.reject', $leaveRequest));
|
||||
|
||||
$leaveRequest->refresh();
|
||||
expect($leaveRequest->verified_by_id)->toBe($user->id);
|
||||
expect($leaveRequest->verified_at)->not->toBeNull();
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| REALISTIC USER SCENARIOS
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
test('user creates leave request then immediately edits it', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$leaveRequest = LeaveRequest::first();
|
||||
|
||||
$this->put(route('admin.hr.leave-requests.update', $leaveRequest), [
|
||||
'start_date' => now()->addDays(5)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(7)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$leaveRequest->refresh();
|
||||
expect($leaveRequest->start_date->format('Y-m-d'))->toBe(now()->addDays(5)->format('Y-m-d'));
|
||||
expect($leaveRequest->end_date->format('Y-m-d'))->toBe(now()->addDays(7)->format('Y-m-d'));
|
||||
expect($leaveRequest->total_days)->toBe(3);
|
||||
});
|
||||
|
||||
test('user creates multiple leave requests and deletes one', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
Employee::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(1)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(3)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$this->post(route('admin.hr.leave-requests.store'), [
|
||||
'start_date' => now()->addDays(10)->format('Y-m-d'),
|
||||
'end_date' => now()->addDays(12)->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$toDelete = LeaveRequest::first();
|
||||
$this->delete(route('admin.hr.leave-requests.destroy', $toDelete));
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index'));
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->component('admin/hr/leave-request/index')
|
||||
->has('leaveRequests', 1)
|
||||
);
|
||||
});
|
||||
|
||||
test('user approves a leave request and it no longer shows as pending', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::PENDING]);
|
||||
|
||||
$pendingCount = LeaveRequest::where('status', LeaveRequestStatus::PENDING)->count();
|
||||
expect($pendingCount)->toBe(1);
|
||||
|
||||
$leaveRequest = LeaveRequest::first();
|
||||
$this->post(route('admin.hr.leave-requests.approve', $leaveRequest));
|
||||
|
||||
$pendingCount = LeaveRequest::where('status', LeaveRequestStatus::PENDING)->count();
|
||||
expect($pendingCount)->toBe(0);
|
||||
|
||||
$approvedCount = LeaveRequest::where('status', LeaveRequestStatus::APPROVED)->count();
|
||||
expect($approvedCount)->toBe(1);
|
||||
});
|
||||
|
||||
test('user filters by pending then approves a request', function () {
|
||||
$user = User::factory()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
LeaveRequest::factory()->count(2)->create(['status' => LeaveRequestStatus::PENDING]);
|
||||
LeaveRequest::factory()->create(['status' => LeaveRequestStatus::APPROVED]);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'pending']));
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->has('leaveRequests', 2)
|
||||
);
|
||||
|
||||
$leaveRequest = LeaveRequest::where('status', LeaveRequestStatus::PENDING)->first();
|
||||
$this->post(route('admin.hr.leave-requests.approve', $leaveRequest));
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'pending']));
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->has('leaveRequests', 1)
|
||||
);
|
||||
|
||||
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'approved']));
|
||||
$response->assertInertia(fn (Assert $page) => $page
|
||||
->has('leaveRequests', 2)
|
||||
);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user