From b62df688b7e6cb16b6fc1146d028aa305147b44d Mon Sep 17 00:00:00 2001 From: IlhamIslamy Date: Sun, 15 Feb 2026 00:56:25 +0700 Subject: [PATCH] feat: implement employee management module --- .../Controllers/Admin/EmployeeController.php | 95 ++++++++++ app/Http/Requests/StoreEmployeeRequest.php | 34 ++++ app/Http/Requests/UpdateEmployeeRequest.php | 38 ++++ app/Models/Employee.php | 24 +++ app/Models/User.php | 11 ++ .../0001_01_01_000000_create_users_table.php | 1 + ...26_02_14_124823_create_employees_table.php | 34 ++++ database/seeders/DatabaseSeeder.php | 15 +- resources/js/components/app-logo.tsx | 2 +- resources/js/components/app-sidebar.tsx | 33 ++-- resources/js/pages/admin/employees/create.tsx | 100 +++++++++++ resources/js/pages/admin/employees/edit.tsx | 163 ++++++++++++++++++ resources/js/pages/admin/employees/index.tsx | 97 +++++++++++ routes/web.php | 4 + 14 files changed, 629 insertions(+), 22 deletions(-) create mode 100644 app/Http/Controllers/Admin/EmployeeController.php create mode 100644 app/Http/Requests/StoreEmployeeRequest.php create mode 100644 app/Http/Requests/UpdateEmployeeRequest.php create mode 100644 app/Models/Employee.php create mode 100644 database/migrations/2026_02_14_124823_create_employees_table.php create mode 100644 resources/js/pages/admin/employees/create.tsx create mode 100644 resources/js/pages/admin/employees/edit.tsx create mode 100644 resources/js/pages/admin/employees/index.tsx diff --git a/app/Http/Controllers/Admin/EmployeeController.php b/app/Http/Controllers/Admin/EmployeeController.php new file mode 100644 index 0000000..17bde17 --- /dev/null +++ b/app/Http/Controllers/Admin/EmployeeController.php @@ -0,0 +1,95 @@ +latest() + ->paginate(10); + + return Inertia::render('admin/employees/index', [ + 'employees' => $employees + ]); + } + + public function create() + { + return Inertia::render('admin/employees/create'); + } + + public function store(StoreEmployeeRequest $request) + { + DB::transaction(function () use ($request) { + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + 'role' => 'employee', + ]); + + $user->employee()->create([ + 'nip' => $request->nip, + 'position' => $request->position, + 'status' => $request->status, + 'join_date' => $request->join_date, + ]); + }); + + return redirect()->route('admin.employees.index') + ->with('success', 'Karyawan berhasil ditambahkan.'); + } + + public function edit(Employee $employee) + { + $employee->load('user'); + return Inertia::render('admin/employees/edit', [ + 'employee' => $employee + ]); + } + + public function update(UpdateEmployeeRequest $request, Employee $employee) + { + DB::transaction(function () use ($request, $employee) { + // Update User Data + $userData = [ + 'name' => $request->name, + 'email' => $request->email, + ]; + + if ($request->filled('password')) { + $userData['password'] = Hash::make($request->password); + } + + $employee->user->update($userData); + + // Update Employee Data + $employee->update([ + 'nip' => $request->nip, + 'position' => $request->position, + 'status' => $request->status, + 'join_date' => $request->join_date, + ]); + }); + + return redirect()->route('admin.employees.index') + ->with('success', 'Data karyawan diperbarui.'); + } + + public function destroy(Employee $employee) + { + $employee->user->delete(); + return back()->with('success', 'Karyawan dihapus.'); + } +} diff --git a/app/Http/Requests/StoreEmployeeRequest.php b/app/Http/Requests/StoreEmployeeRequest.php new file mode 100644 index 0000000..cce0c82 --- /dev/null +++ b/app/Http/Requests/StoreEmployeeRequest.php @@ -0,0 +1,34 @@ +|string> + */ + public function rules(): array + { + return [ + 'name' => 'required|string|max:255', + 'email' => 'required|email|unique:users,email', + 'password' => 'required|string|min:8', + 'nip' => 'required|string|unique:employees,nip', + 'position' => 'required|string', + 'status' => 'required|in:PKWT,PKWTT', + 'join_date' => 'required|date', + ]; + } +} diff --git a/app/Http/Requests/UpdateEmployeeRequest.php b/app/Http/Requests/UpdateEmployeeRequest.php new file mode 100644 index 0000000..40bcee6 --- /dev/null +++ b/app/Http/Requests/UpdateEmployeeRequest.php @@ -0,0 +1,38 @@ +|string> + */ + public function rules(): array + { + $employeeId = $this->route('employee')->id; + $userId = $this->route('employee')->user_id; + + return [ + 'name' => 'required|string|max:255', + 'email' => 'required|email|unique:users,email,' . $userId, + 'nip' => 'required|string|unique:employees,nip,' . $employeeId, + 'position' => 'required|string', + 'status' => 'required|in:PKWT,PKWTT', + 'join_date' => 'required|date', + // Password opsional saat update + 'password' => 'nullable|string|min:8', + ]; + } +} diff --git a/app/Models/Employee.php b/app/Models/Employee.php new file mode 100644 index 0000000..cef1518 --- /dev/null +++ b/app/Models/Employee.php @@ -0,0 +1,24 @@ +belongsTo(User::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 05faee4..25c227f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -22,6 +22,7 @@ class User extends Authenticatable 'name', 'email', 'password', + 'role', ]; /** @@ -36,6 +37,16 @@ class User extends Authenticatable 'remember_token', ]; + public function employee() + { + return $this->hasOne(Employee::class); + } + + public function hasRole($role) + { + return $this->role === $role; + } + /** * Get the attributes that should be cast. * diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index 05fb5d9..23fe012 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -17,6 +17,7 @@ public function up(): void $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); + $table->enum('role', ['admin', 'employee'])->default('employee'); $table->rememberToken(); $table->timestamps(); }); diff --git a/database/migrations/2026_02_14_124823_create_employees_table.php b/database/migrations/2026_02_14_124823_create_employees_table.php new file mode 100644 index 0000000..d46523c --- /dev/null +++ b/database/migrations/2026_02_14_124823_create_employees_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('nip')->unique(); + $table->string('position'); + $table->enum('status', ['PKWT', 'PKWTT']); + $table->string('phone')->nullable(); + $table->text('address')->nullable(); + $table->date('join_date'); + $table->timestamps(); + }); +} + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('employees'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d01a0ef..c5f605e 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,8 +3,8 @@ namespace Database\Seeders; use App\Models\User; -// use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\Hash; class DatabaseSeeder extends Seeder { @@ -13,11 +13,12 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - // User::factory(10)->create(); - - User::factory()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', + User::create([ + 'name' => 'Super Admin', + 'email' => 'admin@zhanhui.com', + 'password' => Hash::make('password123'), + 'role' => 'admin', ]); + } -} +} \ No newline at end of file diff --git a/resources/js/components/app-logo.tsx b/resources/js/components/app-logo.tsx index 42d998f..7665bc5 100644 --- a/resources/js/components/app-logo.tsx +++ b/resources/js/components/app-logo.tsx @@ -8,7 +8,7 @@ export default function AppLogo() {
- Laravel Starter Kit + SDM App
diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index a2d04ac..c60553b 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -1,5 +1,5 @@ import { Link } from '@inertiajs/react'; -import { BookOpen, Folder, LayoutGrid } from 'lucide-react'; +import { BookOpen, Folder, LayoutGrid, Users } from 'lucide-react'; import { NavFooter } from '@/components/nav-footer'; import { NavMain } from '@/components/nav-main'; import { NavUser } from '@/components/nav-user'; @@ -22,20 +22,25 @@ const mainNavItems: NavItem[] = [ href: dashboard(), icon: LayoutGrid, }, + { + title: 'Manajemen Karyawan', + href: '/admin/employees', + icon: Users, + }, ]; -const footerNavItems: NavItem[] = [ - { - title: 'Repository', - href: 'https://github.com/laravel/react-starter-kit', - icon: Folder, - }, - { - title: 'Documentation', - href: 'https://laravel.com/docs/starter-kits#react', - icon: BookOpen, - }, -]; +// const footerNavItems: NavItem[] = [ +// { +// title: 'Repository', +// href: 'https://github.com/laravel/react-starter-kit', +// icon: Folder, +// }, +// { +// title: 'Documentation', +// href: 'https://laravel.com/docs/starter-kits#react', +// icon: BookOpen, +// }, +// ]; export function AppSidebar() { return ( @@ -57,7 +62,7 @@ export function AppSidebar() { - + {/* */} diff --git a/resources/js/pages/admin/employees/create.tsx b/resources/js/pages/admin/employees/create.tsx new file mode 100644 index 0000000..7f1e63e --- /dev/null +++ b/resources/js/pages/admin/employees/create.tsx @@ -0,0 +1,100 @@ +import { Head, useForm, Link } from '@inertiajs/react'; +import React from 'react'; +import type { FormEvent } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import AppLayout from '@/layouts/app-layout'; + +export default function Create() { + const { data, setData, post, processing, errors } = useForm({ + name: '', + email: '', + password: '', + nip: '', + position: '', + status: 'PKWT', + join_date: '', + }); + + const submit = (e: FormEvent) => { + e.preventDefault(); + post('/admin/employees'); + }; + + return ( + + +
+ + + Tambah Karyawan + Masukkan data detail karyawan baru. + + +
+
+ + setData('name', e.target.value)} /> + {errors.name &&

{errors.name}

} +
+ +
+ + setData('email', e.target.value)} /> + {errors.email &&

{errors.email}

} +
+ +
+ + setData('password', e.target.value)} /> +
+ +
+
+ + setData('nip', e.target.value)} /> +
+
+ + setData('join_date', e.target.value)} /> +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/resources/js/pages/admin/employees/edit.tsx b/resources/js/pages/admin/employees/edit.tsx new file mode 100644 index 0000000..9595738 --- /dev/null +++ b/resources/js/pages/admin/employees/edit.tsx @@ -0,0 +1,163 @@ +import { Head, useForm, Link } from '@inertiajs/react'; +import { Separator } from '@radix-ui/react-separator'; +import React from 'react'; +import type { FormEvent } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import AppLayout from '@/layouts/app-layout'; + +// Definisi tipe data props untuk TypeScript +interface EmployeeProps { + employee: { + id: number; + nip: string; + position: string; + status: string; + join_date: string; + user: { + name: string; + email: string; + } | null; + }; +} + +export default function Edit({ employee }: EmployeeProps) { + // Inisialisasi form dengan data dari database + const { data, setData, put, processing, errors } = useForm({ + name: employee.user?.name || '', + email: employee.user?.email || '', + password: '', // Kosongkan untuk keamanan + nip: employee.nip || '', + position: employee.position || '', + status: employee.status || 'PKWT', + join_date: employee.join_date || '', + }); + + const submit = (e: FormEvent) => { + e.preventDefault(); + put(`/admin/employees/${employee.id}`); + }; + + return ( + + +
+ + + Edit Karyawan + + Perbarui informasi data diri dan kepegawaian karyawan. + + + +
+ {/* Data Profil */} +
+ + setData('name', e.target.value)} + /> + {errors.name &&

{errors.name}

} +
+ +
+ + setData('email', e.target.value)} + /> + {errors.email &&

{errors.email}

} +
+ +
+ + setData('password', e.target.value)} + /> + {errors.password &&

{errors.password}

} +
+ + + + {/* Data Kepegawaian */} +
+
+ + setData('nip', e.target.value)} + /> + {errors.nip &&

{errors.nip}

} +
+
+ + setData('join_date', e.target.value)} + /> +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+
+
+ ); +} \ No newline at end of file diff --git a/resources/js/pages/admin/employees/index.tsx b/resources/js/pages/admin/employees/index.tsx new file mode 100644 index 0000000..0f021e1 --- /dev/null +++ b/resources/js/pages/admin/employees/index.tsx @@ -0,0 +1,97 @@ +import { Head, Link, usePage } from '@inertiajs/react'; +import React from 'react'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Separator } from '@/components/ui/separator'; +import AppLayout from '@/layouts/app-layout'; + +interface Employee { + id: number; + nip: string; + position: string; + status: string; + user: { + name: string; + email: string; + } | null; +} + +interface PageProps { + employees: { + data: Employee[]; + }; + [key: string]: unknown; +} + +interface SharedData { + flash: { + success?: string; + }; +} + +export default function Index({ employees }: PageProps) { + const { flash } = usePage().props as unknown as SharedData; + + return ( + + +
+ {flash?.success && ( +
+ {flash.success} +
+ )} + + + + Daftar Karyawan + + + + + + + + + + + + + + + {employees.data.map((employee) => ( + + + + + + + ))} + +
KaryawanNIP & JabatanStatusAksi
+
{employee.user?.name}
+
{employee.user?.email}
+
+
{employee.nip}
+ {employee.position} +
+ + {employee.status} + + + + +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 0a20f55..da76a9f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,6 @@ middleware(['auth', 'verified'])->name('dashboard'); +Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(function () { + Route::resource('employees', EmployeeController::class); +}); require __DIR__.'/settings.php';