feat: add employee management features including create, edit, and list functionalities

- Implemented employee columns for data table with sorting and action buttons.
- Created employee creation form with validation and default password information.
- Developed employee editing form pre-filled with existing data.
- Added employee listing page with delete and reset password functionalities.
- Introduced leave request management with columns for status and actions.
- Created leave request form for adding and editing requests with date pickers.
- Implemented confirmation dialogs for delete, approve, and reject actions.
This commit is contained in:
Yoga Pangestu 2026-07-29 22:33:55 +07:00
parent 887526bd18
commit da30f12fc2
14 changed files with 39 additions and 39 deletions

View File

@ -1,11 +1,11 @@
<?php <?php
namespace App\Http\Controllers\Admin\SDM; namespace App\Http\Controllers\Admin\HR;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\SDM\EmployeeRequest; use App\Http\Requests\Admin\HR\EmployeeRequest;
use App\Models\User; use App\Models\User;
use App\Services\Admin\SDM\EmployeeService; use App\Services\Admin\HR\EmployeeService;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Inertia\Inertia; use Inertia\Inertia;
@ -19,14 +19,14 @@ public function __construct(
public function index(): Response public function index(): Response
{ {
return Inertia::render('admin/sdm/employee/index', [ return Inertia::render('admin/hr/employee/index', [
'employees' => $this->service->getAll(), 'employees' => $this->service->getAll(),
]); ]);
} }
public function create(): Response public function create(): Response
{ {
return Inertia::render('admin/sdm/employee/create'); return Inertia::render('admin/hr/employee/create');
} }
public function store(EmployeeRequest $request): RedirectResponse public function store(EmployeeRequest $request): RedirectResponse
@ -42,12 +42,12 @@ public function store(EmployeeRequest $request): RedirectResponse
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.employees.index'); return to_route('admin.hr.employees.index');
} }
public function edit(User $employee): Response public function edit(User $employee): Response
{ {
return Inertia::render('admin/sdm/employee/edit', [ return Inertia::render('admin/hr/employee/edit', [
'employee' => $this->service->getById($employee->id), 'employee' => $this->service->getById($employee->id),
]); ]);
} }
@ -65,7 +65,7 @@ public function update(EmployeeRequest $request, User $employee): RedirectRespon
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.employees.index'); return to_route('admin.hr.employees.index');
} }
public function destroy(User $employee): RedirectResponse public function destroy(User $employee): RedirectResponse
@ -81,7 +81,7 @@ public function destroy(User $employee): RedirectResponse
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.employees.index'); return to_route('admin.hr.employees.index');
} }
public function toggleActive(User $employee): RedirectResponse public function toggleActive(User $employee): RedirectResponse
@ -98,7 +98,7 @@ public function toggleActive(User $employee): RedirectResponse
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.employees.index'); return to_route('admin.hr.employees.index');
} }
public function resetPassword(User $employee): RedirectResponse public function resetPassword(User $employee): RedirectResponse
@ -114,6 +114,6 @@ public function resetPassword(User $employee): RedirectResponse
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.employees.index'); return to_route('admin.hr.employees.index');
} }
} }

View File

@ -1,11 +1,11 @@
<?php <?php
namespace App\Http\Controllers\Admin\SDM; namespace App\Http\Controllers\Admin\HR;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\SDM\LeaveRequestRequest; use App\Http\Requests\Admin\HR\LeaveRequestRequest;
use App\Models\LeaveRequest; use App\Models\LeaveRequest;
use App\Services\Admin\SDM\LeaveRequestService; use App\Services\Admin\HR\LeaveRequestService;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Inertia\Inertia; use Inertia\Inertia;
@ -19,7 +19,7 @@ public function __construct(
public function index(): Response public function index(): Response
{ {
return Inertia::render('admin/sdm/leave-request/index', [ return Inertia::render('admin/hr/leave-request/index', [
'leaveRequests' => $this->service->getAll(), 'leaveRequests' => $this->service->getAll(),
]); ]);
} }
@ -37,7 +37,7 @@ public function store(LeaveRequestRequest $request): RedirectResponse
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.leave-requests.index'); return to_route('admin.hr.leave-requests.index');
} }
public function update(LeaveRequestRequest $request, LeaveRequest $leaveRequest): RedirectResponse public function update(LeaveRequestRequest $request, LeaveRequest $leaveRequest): RedirectResponse
@ -53,7 +53,7 @@ public function update(LeaveRequestRequest $request, LeaveRequest $leaveRequest)
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.leave-requests.index'); return to_route('admin.hr.leave-requests.index');
} }
public function destroy(LeaveRequest $leaveRequest): RedirectResponse public function destroy(LeaveRequest $leaveRequest): RedirectResponse
@ -66,7 +66,7 @@ public function destroy(LeaveRequest $leaveRequest): RedirectResponse
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.leave-requests.index'); return to_route('admin.hr.leave-requests.index');
} }
public function approve(LeaveRequest $leaveRequest): RedirectResponse public function approve(LeaveRequest $leaveRequest): RedirectResponse
@ -79,7 +79,7 @@ public function approve(LeaveRequest $leaveRequest): RedirectResponse
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.leave-requests.index'); return to_route('admin.hr.leave-requests.index');
} }
public function reject(LeaveRequest $leaveRequest): RedirectResponse public function reject(LeaveRequest $leaveRequest): RedirectResponse
@ -92,6 +92,6 @@ public function reject(LeaveRequest $leaveRequest): RedirectResponse
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]); Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
} }
return to_route('admin.sdm.leave-requests.index'); return to_route('admin.hr.leave-requests.index');
} }
} }

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Requests\Admin\SDM; namespace App\Http\Requests\Admin\HR;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Requests\Admin\SDM; namespace App\Http\Requests\Admin\HR;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Services\Admin\SDM; namespace App\Services\Admin\HR;
use App\Models\User; use App\Models\User;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Services\Admin\SDM; namespace App\Services\Admin\HR;
use App\Enums\LeaveRequestStatus; use App\Enums\LeaveRequestStatus;
use App\Models\LeaveRequest; use App\Models\LeaveRequest;

View File

@ -41,8 +41,8 @@ import { index as suppliersIndex } from '@/routes/admin/master/suppliers';
import { index as cashAccountsIndex } from '@/routes/admin/finance/cash-accounts'; import { index as cashAccountsIndex } from '@/routes/admin/finance/cash-accounts';
import { index as expensesIndex } from '@/routes/admin/finance/expenses'; import { index as expensesIndex } from '@/routes/admin/finance/expenses';
import { index as employeeAdvancesIndex } from '@/routes/admin/finance/employee-advances'; import { index as employeeAdvancesIndex } from '@/routes/admin/finance/employee-advances';
import { index as employeesIndex } from '@/routes/admin/sdm/employees'; import { index as employeesIndex } from '@/routes/admin/hr/employees';
import { index as leaveRequestsIndex } from '@/routes/admin/sdm/leave-requests'; import { index as leaveRequestsIndex } from '@/routes/admin/hr/leave-requests';
type NavMenuItem = { title: string; href: string; icon: LucideIcon }; type NavMenuItem = { title: string; href: string; icon: LucideIcon };
@ -80,7 +80,7 @@ const keuanganItems: NavMenuItem[] = [
{ title: 'Gaji', href: '#', icon: DollarSign }, { title: 'Gaji', href: '#', icon: DollarSign },
]; ];
const sdmItems: NavMenuItem[] = [ const hrItems: NavMenuItem[] = [
{ title: 'Pegawai', href: employeesIndex.url(), icon: UserCircle }, { title: 'Pegawai', href: employeesIndex.url(), icon: UserCircle },
{ title: 'Presensi', href: '#', icon: CalendarCheck }, { title: 'Presensi', href: '#', icon: CalendarCheck },
{ title: 'Cuti', href: leaveRequestsIndex.url(), icon: CalendarDays }, { title: 'Cuti', href: leaveRequestsIndex.url(), icon: CalendarDays },
@ -158,7 +158,7 @@ export function AppSidebar() {
<MenuGroup label="Master" items={masterItems} /> <MenuGroup label="Master" items={masterItems} />
<MenuGroup label="Kelola" items={kelolaItems} /> <MenuGroup label="Kelola" items={kelolaItems} />
<MenuGroup label="Keuangan" items={keuanganItems} /> <MenuGroup label="Keuangan" items={keuanganItems} />
<MenuGroup label="SDM" items={sdmItems} /> <MenuGroup label="HR" items={hrItems} />
<MenuGroup label="Sistem" items={sistemItems} /> <MenuGroup label="Sistem" items={sistemItems} />
</SidebarContent> </SidebarContent>
</Sidebar> </Sidebar>

View File

@ -15,7 +15,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { index as employeeIndex, store } from '@/routes/admin/sdm/employees'; import { index as employeeIndex, store } from '@/routes/admin/hr/employees';
import { Form, Head } from '@inertiajs/react'; import { Form, Head } from '@inertiajs/react';
import { AlertCircle, ArrowLeft } from 'lucide-react'; import { AlertCircle, ArrowLeft } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
@ -222,7 +222,7 @@ export default function EmployeeCreate() {
EmployeeCreate.layout = { EmployeeCreate.layout = {
breadcrumbs: [ breadcrumbs: [
{ {
title: 'SDM', title: 'HR',
href: employeeIndex.url(), href: employeeIndex.url(),
}, },
{ {

View File

@ -14,7 +14,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { index as employeeIndex, update } from '@/routes/admin/sdm/employees'; import { index as employeeIndex, update } from '@/routes/admin/hr/employees';
import { Form, Head } from '@inertiajs/react'; import { Form, Head } from '@inertiajs/react';
import { ArrowLeft } from 'lucide-react'; import { ArrowLeft } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
@ -249,7 +249,7 @@ export default function EmployeeEdit({ employee }: Props) {
EmployeeEdit.layout = { EmployeeEdit.layout = {
breadcrumbs: [ breadcrumbs: [
{ {
title: 'SDM', title: 'HR',
href: employeeIndex.url(), href: employeeIndex.url(),
}, },
{ {

View File

@ -1,7 +1,7 @@
import { ConfirmDialog } from '@/components/confirm-dialog'; import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { destroy, create as employeeCreate, edit as employeeEdit, index as employeeIndex, toggleActive, resetPassword as resetPasswordRoute } from '@/routes/admin/sdm/employees'; import { destroy, create as employeeCreate, edit as employeeEdit, index as employeeIndex, toggleActive, resetPassword as resetPasswordRoute } from '@/routes/admin/hr/employees';
import { Head, router } from '@inertiajs/react'; import { Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react'; import { Plus } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
@ -106,7 +106,7 @@ export default function EmployeeIndex({ employees }: Props) {
EmployeeIndex.layout = { EmployeeIndex.layout = {
breadcrumbs: [ breadcrumbs: [
{ {
title: 'SDM', title: 'HR',
href: employeeIndex.url(), href: employeeIndex.url(),
}, },
{ {

View File

@ -11,7 +11,7 @@ import {
DialogTitle, DialogTitle,
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { approve, destroy, index as leaveRequestIndex, reject, store, update } from '@/routes/admin/sdm/leave-requests'; import { approve, destroy, index as leaveRequestIndex, reject, store, update } from '@/routes/admin/hr/leave-requests';
import { Form, Head, router } from '@inertiajs/react'; import { Form, Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react'; import { Plus } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@ -298,7 +298,7 @@ export default function LeaveRequestIndex({ leaveRequests }: Props) {
LeaveRequestIndex.layout = { LeaveRequestIndex.layout = {
breadcrumbs: [ breadcrumbs: [
{ {
title: 'SDM', title: 'HR',
href: leaveRequestIndex(), href: leaveRequestIndex(),
}, },
{ {

View File

@ -6,8 +6,8 @@
use App\Http\Controllers\Admin\Master\CategoryController; use App\Http\Controllers\Admin\Master\CategoryController;
use App\Http\Controllers\Admin\Master\CustomerController; use App\Http\Controllers\Admin\Master\CustomerController;
use App\Http\Controllers\Admin\Master\SupplierController; use App\Http\Controllers\Admin\Master\SupplierController;
use App\Http\Controllers\Admin\SDM\EmployeeController; use App\Http\Controllers\Admin\HR\EmployeeController;
use App\Http\Controllers\Admin\SDM\LeaveRequestController; use App\Http\Controllers\Admin\HR\LeaveRequestController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::inertia('/', 'welcome')->name('home'); Route::inertia('/', 'welcome')->name('home');
@ -35,7 +35,7 @@
Route::post('employee-advances/{employeeAdvance}/pay', [EmployeeAdvanceController::class, 'pay'])->name('employee-advances.pay'); Route::post('employee-advances/{employeeAdvance}/pay', [EmployeeAdvanceController::class, 'pay'])->name('employee-advances.pay');
}); });
Route::prefix('admin/sdm')->name('admin.sdm.')->group(function () { Route::prefix('admin/hr')->name('admin.hr.')->group(function () {
Route::resource('employees', EmployeeController::class)->except(['show']); Route::resource('employees', EmployeeController::class)->except(['show']);
Route::post('employees/{employee}/toggle-active', [EmployeeController::class, 'toggleActive'])->name('employees.toggle-active'); Route::post('employees/{employee}/toggle-active', [EmployeeController::class, 'toggleActive'])->name('employees.toggle-active');
Route::post('employees/{employee}/reset-password', [EmployeeController::class, 'resetPassword'])->name('employees.reset-password'); Route::post('employees/{employee}/reset-password', [EmployeeController::class, 'resetPassword'])->name('employees.reset-password');