From 6255b83d954e894b4980a86543510edb5cda523e Mon Sep 17 00:00:00 2001 From: IlhamIslamy Date: Mon, 2 Mar 2026 04:41:07 +0700 Subject: [PATCH] Feat: Roles management fiture --- app/Http/Controllers/Admin/UserController.php | 32 ++++++++ app/Http/Controllers/DashboardController.php | 8 +- app/Http/Middleware/HandleInertiaRequests.php | 23 +++--- app/Http/Middleware/RoleMiddleware.php | 19 +++++ bootstrap/app.php | 4 + ...01_01_000001_create_departments_table.php} | 0 ...4_01_01_000002_create_positions_table.php} | 0 ...6_02_14_999999_create_employees_table.php} | 0 database/seeders/DatabaseSeeder.php | 28 +------ database/seeders/MasterDataSeeder.php | 29 +++++++ database/seeders/UserSeeder.php | 29 +++++++ resources/js/components/app-sidebar.tsx | 78 +++++++++---------- resources/js/pages/admin/users/index.tsx | 61 +++++++++++++++ resources/js/pages/employee/dashboard.tsx | 33 ++++++++ routes/web.php | 17 +++- 15 files changed, 280 insertions(+), 81 deletions(-) create mode 100644 app/Http/Controllers/Admin/UserController.php create mode 100644 app/Http/Middleware/RoleMiddleware.php rename database/migrations/{2026_02_25_184718_create_departments_table.php => 2024_01_01_000001_create_departments_table.php} (100%) rename database/migrations/{2026_02_25_184724_create_positions_table.php => 2024_01_01_000002_create_positions_table.php} (100%) rename database/migrations/{2026_02_14_124823_create_employees_table.php => 2026_02_14_999999_create_employees_table.php} (100%) create mode 100644 database/seeders/MasterDataSeeder.php create mode 100644 database/seeders/UserSeeder.php create mode 100644 resources/js/pages/admin/users/index.tsx create mode 100644 resources/js/pages/employee/dashboard.tsx diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100644 index 0000000..44cf6f7 --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,32 @@ + User::select('id', 'name', 'email', 'role', 'created_at') + ->latest() + ->get() + ]); + } + + public function update(Request $request, User $user) + { + // Validasi perpindahan role + $validated = $request->validate([ + 'role' => 'required|in:admin,employee', + ]); + + $user->update($validated); + + return back()->with('success', "Role {$user->name} berhasil diubah."); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index a652fe6..c7202ab 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -12,14 +12,16 @@ class DashboardController extends Controller { public function index() { - // Ringkasan Data + if (auth()->user()->role !== 'admin') { + return redirect()->route('employee.dashboard'); + } + $stats = [ 'total_employees' => Employee::count(), 'total_departments' => Department::count(), 'total_positions' => Position::count(), ]; - // Data Grafik Gender (Pie Chart) $genderData = Employee::selectRaw('gender, count(*) as total') ->groupBy('gender') ->get() @@ -29,7 +31,6 @@ public function index() 'fill' => $item->gender == 'L' ? '#3b82f6' : '#ec4899', ]); - // Data Grafik Departemen (Bar Chart) $deptData = Department::withCount('employees') ->having('employees_count', '>', 0) ->get() @@ -38,7 +39,6 @@ public function index() 'employees' => $item->employees_count, ]); - // Karyawan Terbaru (Table) $latestEmployees = Employee::with(['department', 'position', 'user']) ->latest('join_date') ->take(5) diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index f4cc770..652ec83 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -34,14 +34,17 @@ public function version(Request $request): ?string * @return array */ public function share(Request $request): array - { - return [ - ...parent::share($request), - 'name' => config('app.name'), - 'auth' => [ - 'user' => $request->user(), - ], - 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', - ]; - } +{ + return [ + ...parent::share($request), + 'auth' => [ + 'user' => $request->user() ? [ + 'id' => $request->user()->id, + 'name' => $request->user()->name, + 'email' => $request->user()->email, + 'role' => $request->user()->role, + ] : null, + ], + ]; +} } diff --git a/app/Http/Middleware/RoleMiddleware.php b/app/Http/Middleware/RoleMiddleware.php new file mode 100644 index 0000000..acf28cd --- /dev/null +++ b/app/Http/Middleware/RoleMiddleware.php @@ -0,0 +1,19 @@ +user() || $request->user()->role !== $role) { + abort(403, 'Akses Ditolak.'); + } + + return $next($request); + } +} \ No newline at end of file diff --git a/bootstrap/app.php b/bootstrap/app.php index c4f1cc5..3e9020e 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -21,6 +21,10 @@ HandleInertiaRequests::class, AddLinkHeadersForPreloadedAssets::class, ]); + + $middleware->alias([ + 'role' => \App\Http\Middleware\RoleMiddleware::class, + ]); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/database/migrations/2026_02_25_184718_create_departments_table.php b/database/migrations/2024_01_01_000001_create_departments_table.php similarity index 100% rename from database/migrations/2026_02_25_184718_create_departments_table.php rename to database/migrations/2024_01_01_000001_create_departments_table.php diff --git a/database/migrations/2026_02_25_184724_create_positions_table.php b/database/migrations/2024_01_01_000002_create_positions_table.php similarity index 100% rename from database/migrations/2026_02_25_184724_create_positions_table.php rename to database/migrations/2024_01_01_000002_create_positions_table.php diff --git a/database/migrations/2026_02_14_124823_create_employees_table.php b/database/migrations/2026_02_14_999999_create_employees_table.php similarity index 100% rename from database/migrations/2026_02_14_124823_create_employees_table.php rename to database/migrations/2026_02_14_999999_create_employees_table.php diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 46e792c..ff4e9eb 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,37 +2,15 @@ namespace Database\Seeders; -use App\Models\Department; -use App\Models\Position; -use App\Models\User; use Illuminate\Database\Seeder; -use Illuminate\Support\Facades\Hash; class DatabaseSeeder extends Seeder { - /** - * Seed the application's database. - */ public function run(): void { - User::create([ - 'name' => 'Super Admin', - 'email' => 'admin@zhanhui.com', - 'password' => Hash::make('password123'), - 'role' => 'admin', + $this->call([ + UserSeeder::class, + MasterDataSeeder::class, ]); - - // Seed departments - Department::create(['name' => 'Information Technology', 'description' => 'Bagian IT & Development']); - Department::create(['name' => 'Human Resources', 'description' => 'Bagian Kepegawaian']); - Department::create(['name' => 'Finance', 'description' => 'Bagian Keuangan']); - Department::create(['name' => 'Marketing', 'description' => 'Bagian Pemasaran']); - - // Seed positions - Position::create(['name' => 'General Manager']); - Position::create(['name' => 'Manager']); - Position::create(['name' => 'Supervisor']); - Position::create(['name' => 'Staff']); - Position::create(['name' => 'Intern']); } } \ No newline at end of file diff --git a/database/seeders/MasterDataSeeder.php b/database/seeders/MasterDataSeeder.php new file mode 100644 index 0000000..4c2de4b --- /dev/null +++ b/database/seeders/MasterDataSeeder.php @@ -0,0 +1,29 @@ + 'Information Technology', 'description' => 'Bagian IT & Development'], + ['name' => 'Human Resources', 'description' => 'Bagian Kepegawaian'], + ['name' => 'Finance', 'description' => 'Bagian Keuangan'], + ]; + + foreach ($depts as $dept) { + Department::create($dept); + } + + $positions = ['Manager', 'Senior Developer', 'Staff Admin', 'HR Specialist']; + + foreach ($positions as $pos) { + Position::create(['name' => $pos]); + } + } +} \ No newline at end of file diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php new file mode 100644 index 0000000..e93f5a4 --- /dev/null +++ b/database/seeders/UserSeeder.php @@ -0,0 +1,29 @@ + 'admin', + 'email' => 'admin@gmail.com', + 'password' => Hash::make('password'), + 'role' => 'admin', + ]); + + // Akun Contoh Karyawan + User::create([ + 'name' => 'Jono Joni', + 'email' => 'jono@gmail.com', + 'password' => Hash::make('password'), + 'role' => 'employee', + ]); + } +} \ No newline at end of file diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index 3520225..e4880e6 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -1,5 +1,6 @@ -import { Link } from '@inertiajs/react'; -import { LayoutGrid, Users, Briefcase, Building2} from 'lucide-react'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Link, usePage } from '@inertiajs/react'; // Tambahkan usePage +import { LayoutGrid, Users, Briefcase, Building2, SquareUser } from 'lucide-react'; import { NavMain } from '@/components/nav-main'; import { NavUser } from '@/components/nav-user'; import { @@ -15,49 +16,47 @@ import { dashboard } from '@/routes'; import type { NavItem } from '@/types'; import AppLogo from './app-logo'; -const mainNavItems: NavItem[] = [ - { - title: 'Dashboard', - href: '/dashboard', - icon: LayoutGrid, - }, - { - title: 'Manajemen Karyawan', - href: '/admin/employees', - icon: Users, - }, - { - title: 'Manajemen Departemen', - href: '/admin/departments', - icon: Building2, - }, - { - title: 'Manajemen Jabatan', - href: '/admin/positions', - icon: Briefcase, - }, -]; - -// 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() { + const { auth } = usePage().props as any; + const userRole = auth.user.role; + + const mainNavItems: NavItem[] = [ + { + title: 'Dashboard', + href: userRole === 'admin' ? '/dashboard' : '/employee/dashboard', + icon: LayoutGrid, + }, + ...(userRole === 'admin' ? [ + { + title: 'Manajemen Karyawan', + href: '/admin/employees', + icon: Users, + }, + { + title: 'Manajemen Departemen', + href: '/admin/departments', + icon: Building2, + }, + { + title: 'Manajemen Jabatan', + href: '/admin/positions', + icon: Briefcase, + }, + { + title: 'Manajemen Pengguna', + href: '/admin/users', + icon: SquareUser, + }, + ] : []), + ]; + return ( + @@ -71,9 +70,8 @@ export function AppSidebar() { - {/* */} ); -} +} \ No newline at end of file diff --git a/resources/js/pages/admin/users/index.tsx b/resources/js/pages/admin/users/index.tsx new file mode 100644 index 0000000..fe3ce11 --- /dev/null +++ b/resources/js/pages/admin/users/index.tsx @@ -0,0 +1,61 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Head, useForm } from '@inertiajs/react'; +import React from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import AppLayout from '@/layouts/app-layout'; + +export default function Index({ users }: { users: any[] }) { + const { patch, processing } = useForm(); + + const updateRole = (id: number, role: string) => { + patch(route('admin.users.update', id), { + data: { role: role }, + preserveScroll: true, + }); + }; + + return ( + + +
+

Manajemen Akses User

+ + Daftar Pengguna + + + + + + + + + + + {users.map((user) => ( + + + + + + ))} + +
NamaRoleAksi
{user.name} + + {user.role.toUpperCase()} + + + +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/resources/js/pages/employee/dashboard.tsx b/resources/js/pages/employee/dashboard.tsx new file mode 100644 index 0000000..c297cd0 --- /dev/null +++ b/resources/js/pages/employee/dashboard.tsx @@ -0,0 +1,33 @@ +import { Head, usePage } from '@inertiajs/react'; +import AppLayout from '@/layouts/app-layout'; + +export default function Dashboard() { + const { auth } = usePage().props as any; + + return ( + + + +
+
+

Halo, {auth.user.name}!

+

+ Selamat datang di portal SDM. Kamu login sebagai {auth.user.role}. +

+
+ +
+ {/* Kotak-kotak info simpel untuk karyawan */} +
+

Email

+

{auth.user.email}

+
+
+

Status Akun

+

Aktif

+
+
+
+
+ ); +} \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 8560066..c9d4a96 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,6 +3,7 @@ use App\Http\Controllers\Admin\DepartmentController; use App\Http\Controllers\Admin\EmployeeController; use App\Http\Controllers\Admin\PositionController; +use App\Http\Controllers\Admin\UserController; use App\Http\Controllers\DashboardController; use Illuminate\Support\Facades\Route; use Inertia\Inertia; @@ -18,9 +19,21 @@ ->middleware(['auth', 'verified']) ->name('dashboard'); -Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(function () { +// ADMIN +Route::middleware(['auth', 'verified', 'role:admin'])->prefix('admin')->name('admin.')->group(function () { Route::resource('departments', DepartmentController::class); Route::resource('positions', PositionController::class); Route::resource('employees', EmployeeController::class); + + Route::get('users', [UserController::class, 'index'])->name('users.index'); + Route::patch('users/{user}', [UserController::class, 'update'])->name('users.update'); }); -require __DIR__.'/settings.php'; + +// KELOMPOK KARYAWAN +Route::middleware(['auth', 'verified', 'role:employee'])->group(function () { + Route::get('/employee/dashboard', function () { + return Inertia::render('employee/dashboard'); + })->name('employee.dashboard'); +}); + +require __DIR__.'/settings.php'; \ No newline at end of file