diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 44cf6f7..8a8955d 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -1,32 +1,68 @@ - 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 + User::select('id', 'name', 'email', 'role', 'created_at') + ->latest() + ->get(), + ]); + } + + public function create() + { + return Inertia::render('admin/users/create'); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|email|max:255|unique:users,email', + 'password' => 'required|string|min:8|confirmed', + 'role' => ['required', Rule::in(['admin', 'employee'])], + ], [ + 'name.required' => 'Nama wajib diisi.', + 'email.required' => 'Email wajib diisi.', + 'email.email' => 'Format email tidak valid.', + 'email.unique' => 'Email sudah terdaftar.', + 'password.required' => 'Password wajib diisi.', + 'password.min' => 'Password minimal 8 karakter.', + 'password.confirmed' => 'Konfirmasi password tidak cocok.', + 'role.required' => 'Role wajib dipilih.', + 'role.in' => 'Role hanya boleh admin atau employee.', + ]); + + User::create([ + 'name' => $validated['name'], + 'email' => $validated['email'], + 'password' => Hash::make($validated['password']), + 'role' => $validated['role'], + ]); + + return redirect()->route('admin.users.index') + ->with('success', "User {$validated['name']} berhasil ditambahkan."); + } + + public function update(Request $request, User $user) + { + $validated = $request->validate([ + 'role' => ['required', Rule::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 c7202ab..2611e31 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -13,7 +13,7 @@ class DashboardController extends Controller public function index() { if (auth()->user()->role !== 'admin') { - return redirect()->route('employee.dashboard'); + return redirect()->route('employee.index'); } $stats = [ diff --git a/app/Http/Responses/LoginResponse.php b/app/Http/Responses/LoginResponse.php new file mode 100644 index 0000000..287cab8 --- /dev/null +++ b/app/Http/Responses/LoginResponse.php @@ -0,0 +1,30 @@ +user()->role; + + $redirectUrl = match ($role) { + 'admin' => route('dashboard'), + 'employee' => route('employee.index'), + default => route('dashboard'), + }; + + if ($request->wantsJson()) { + return new JsonResponse(['two_factor' => false], 200); + } + + return redirect()->intended($redirectUrl); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index eee3a69..7e76a2f 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,11 +2,13 @@ namespace App\Providers; +use App\Http\Responses\LoginResponse; use Carbon\CarbonImmutable; use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\DB; use Illuminate\Support\ServiceProvider; use Illuminate\Validation\Rules\Password; +use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract; class AppServiceProvider extends ServiceProvider { @@ -15,7 +17,7 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - // + $this->app->singleton(LoginResponseContract::class, LoginResponse::class); } /** diff --git a/database/seeders/MasterDataSeeder.php b/database/seeders/MasterDataSeeder.php index 4c2de4b..156848a 100644 --- a/database/seeders/MasterDataSeeder.php +++ b/database/seeders/MasterDataSeeder.php @@ -10,20 +10,34 @@ class MasterDataSeeder extends Seeder { public function run(): void { + // === DEPARTMENTS === $depts = [ ['name' => 'Information Technology', 'description' => 'Bagian IT & Development'], ['name' => 'Human Resources', 'description' => 'Bagian Kepegawaian'], - ['name' => 'Finance', 'description' => 'Bagian Keuangan'], + ['name' => 'Finance', 'description' => 'Bagian Keuangan & Akuntansi'], + ['name' => 'Marketing', 'description' => 'Bagian Pemasaran & Komunikasi'], + ['name' => 'Operations', 'description' => 'Bagian Operasional & Logistik'], ]; foreach ($depts as $dept) { Department::create($dept); } - $positions = ['Manager', 'Senior Developer', 'Staff Admin', 'HR Specialist']; + // === POSITIONS === + $positions = [ + 'Manager', + 'Senior Developer', + 'Junior Developer', + 'Staff Admin', + 'HR Specialist', + 'Finance Analyst', + 'Marketing Staff', + 'Operations Staff', + ]; 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 index e93f5a4..7b5a49a 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -12,18 +12,60 @@ public function run(): void { // Akun Admin Utama User::create([ - 'name' => 'admin', + 'name' => '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', ]); + User::create([ + 'name' => 'Siti Nurhaliza', + 'email' => 'siti.admin@hris.com', + 'password' => Hash::make('password'), + 'role' => 'admin', + ]); + + // --- Akun Karyawan (Employee) --- + User::create([ + 'name' => 'Budi Santoso', + 'email' => 'budi.santoso@hris.com', + 'password' => Hash::make('password'), + 'role' => 'employee', + ]); + + User::create([ + 'name' => 'Dewi Rahayu', + 'email' => 'dewi.rahayu@hris.com', + 'password' => Hash::make('password'), + 'role' => 'employee', + ]); + + User::create([ + 'name' => 'Rizky Firmansyah', + 'email' => 'rizky.firmansyah@hris.com', + 'password' => Hash::make('password'), + 'role' => 'employee', + ]); + + User::create([ + 'name' => 'Anisa Putri', + 'email' => 'anisa.putri@hris.com', + 'password' => Hash::make('password'), + 'role' => 'employee', + ]); + + User::create([ + 'name' => 'Hendra Kurniawan', + 'email' => 'hendra.kurniawan@hris.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 e4880e6..af459e0 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Link, usePage } from '@inertiajs/react'; // Tambahkan usePage +import { Link, usePage } from '@inertiajs/react'; import { LayoutGrid, Users, Briefcase, Building2, SquareUser } from 'lucide-react'; import { NavMain } from '@/components/nav-main'; import { NavUser } from '@/components/nav-user'; @@ -13,39 +13,39 @@ import { SidebarMenuItem, } from '@/components/ui/sidebar'; import { dashboard } from '@/routes'; -import type { NavItem } from '@/types'; +import type { NavItem, SharedData } from '@/types'; import AppLogo from './app-logo'; export function AppSidebar() { - const { auth } = usePage().props as any; + const { auth } = usePage().props; const userRole = auth.user.role; const mainNavItems: NavItem[] = [ { title: 'Dashboard', - href: userRole === 'admin' ? '/dashboard' : '/employee/dashboard', + href: userRole === 'admin' ? '/dashboard' : '/employee/index', icon: LayoutGrid, }, ...(userRole === 'admin' ? [ { title: 'Manajemen Karyawan', href: '/admin/employees', - icon: Users, + icon: Users, }, { title: 'Manajemen Departemen', href: '/admin/departments', - icon: Building2, + icon: Building2, }, { title: 'Manajemen Jabatan', href: '/admin/positions', - icon: Briefcase, + icon: Briefcase, }, { title: 'Manajemen Pengguna', href: '/admin/users', - icon: SquareUser, + icon: SquareUser, }, ] : []), ]; diff --git a/resources/js/pages/admin/users/create.tsx b/resources/js/pages/admin/users/create.tsx new file mode 100644 index 0000000..d6b3600 --- /dev/null +++ b/resources/js/pages/admin/users/create.tsx @@ -0,0 +1,164 @@ +import { Head, Link, useForm } from '@inertiajs/react'; +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, 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 { Separator } from '@/components/ui/separator'; +import AppLayout from '@/layouts/app-layout'; +import { type BreadcrumbItem } from '@/types'; + +const breadcrumbs: BreadcrumbItem[] = [ + { title: 'Manajemen Pengguna', href: '/admin/users' }, + { title: 'Tambah User', href: '/admin/users/create' }, +]; + +export default function Create() { + const { data, setData, post, processing, errors } = useForm({ + name: '', + email: '', + password: '', + password_confirmation: '', + role: '', + }); + + const submit = (e: React.FormEvent) => { + e.preventDefault(); + post('/admin/users'); + }; + + return ( + + + +
+
+
+

Tambah User Baru

+

Buat akun pengguna baru beserta hak aksesnya.

+
+ +
+ + + + Form User + + + +
+ +
+ {/* Nama */} +
+ + setData('name', e.target.value)} + autoComplete="off" + /> + {errors.name && ( +

{errors.name}

+ )} +
+ + {/* Email */} +
+ + setData('email', e.target.value)} + autoComplete="off" + /> + {errors.email && ( +

{errors.email}

+ )} +
+ + {/* Password */} +
+ + setData('password', e.target.value)} + autoComplete="new-password" + /> + {errors.password && ( +

{errors.password}

+ )} +
+ + {/* Konfirmasi Password */} +
+ + setData('password_confirmation', e.target.value)} + autoComplete="new-password" + /> + {errors.password_confirmation && ( +

{errors.password_confirmation}

+ )} +
+ + {/* Role */} +
+ + + {errors.role && ( +

{errors.role}

+ )} +
+
+ +
+ + +
+ +
+
+
+
+
+ ); +} diff --git a/resources/js/pages/admin/users/index.tsx b/resources/js/pages/admin/users/index.tsx index fe3ce11..11c03b1 100644 --- a/resources/js/pages/admin/users/index.tsx +++ b/resources/js/pages/admin/users/index.tsx @@ -1,61 +1,133 @@ -/* 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 +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Head, Link, router, usePage } from '@inertiajs/react'; +import React, { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Separator } from '@/components/ui/separator'; +import AppLayout from '@/layouts/app-layout'; +import { type BreadcrumbItem } from '@/types'; + +interface User { + id: number; + name: string; + email: string; + role: 'admin' | 'employee'; + created_at: string; +} + +interface PageProps { + users: User[]; + [key: string]: unknown; +} + +const breadcrumbs: BreadcrumbItem[] = [ + { title: 'Manajemen Pengguna', href: '/admin/users' }, +]; + +export default function Index({ users }: PageProps) { + const { flash } = usePage().props; + const [processingId, setProcessingId] = useState(null); + + const updateRole = (id: number, role: string) => { + setProcessingId(id); + router.patch( + `/admin/users/${id}`, + { role }, + { + preserveScroll: true, + onFinish: () => setProcessingId(null), + }, + ); + }; + + return ( + + + +
+ + {/* Flash Messages */} + {flash?.success && ( +
+ {flash.success} +
+ )} + {flash?.error && ( +
+ {flash.error} +
+ )} + + {/* Header */} +
+
+

Manajemen Akses User

+

Kelola akun dan hak akses pengguna sistem.

+
+ +
+ + {/* Tabel */} + + + Daftar Pengguna + + + +
+ + + + + + + + + + + {users.length > 0 ? ( + users.map((user) => ( + + + + + + + )) + ) : ( + + + + )} + +
NamaEmailRole Saat IniUbah Role
{user.name}{user.email} + + {user.role.toUpperCase()} + + + +
+ Belum ada data pengguna. +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/resources/js/pages/employee/index.tsx b/resources/js/pages/employee/index.tsx new file mode 100644 index 0000000..ca1f34b --- /dev/null +++ b/resources/js/pages/employee/index.tsx @@ -0,0 +1,73 @@ +import { Head } from '@inertiajs/react'; +import AppLayout from '@/layouts/app-layout'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { usePage } from '@inertiajs/react'; +import { User2, Mail, ShieldCheck } from 'lucide-react'; +import type { SharedData } from '@/types'; +import type { BreadcrumbItem } from '@/types'; + +const breadcrumbs: BreadcrumbItem[] = [ + { title: 'Portal Saya', href: '/employee/index' }, +]; + +export default function EmployeeIndex() { + const { auth } = usePage().props; + const user = auth.user; + + return ( + + + +
+ {/* Header Sambutan */} +
+

+ Selamat datang, {user.name}! +

+

+ Ini adalah portal SDM Anda. Pantau informasi akun Anda di sini. +

+
+ + {/* Kartu Info Akun */} +
+ + + Nama Lengkap + + + +

{user.name}

+

Nama akun terdaftar

+
+
+ + + + Alamat Email + + + +

{user.email}

+

Email untuk login

+
+
+ + + + Hak Akses + + + + + {user.role} + +

Role akun Anda saat ini

+
+
+
+
+
+ ); +} diff --git a/resources/js/types/auth.ts b/resources/js/types/auth.ts index 5ffc370..ae1b25a 100644 --- a/resources/js/types/auth.ts +++ b/resources/js/types/auth.ts @@ -2,6 +2,7 @@ export type User = { id: number; name: string; email: string; + role: 'admin' | 'employee'; avatar?: string; email_verified_at: string | null; two_factor_enabled?: boolean; diff --git a/routes/web.php b/routes/web.php index c9d4a96..9e0b12b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -26,14 +26,16 @@ Route::resource('employees', EmployeeController::class); Route::get('users', [UserController::class, 'index'])->name('users.index'); + Route::get('users/create', [UserController::class, 'create'])->name('users.create'); + Route::post('users', [UserController::class, 'store'])->name('users.store'); Route::patch('users/{user}', [UserController::class, 'update'])->name('users.update'); }); // KELOMPOK KARYAWAN Route::middleware(['auth', 'verified', 'role:employee'])->group(function () { - Route::get('/employee/dashboard', function () { - return Inertia::render('employee/dashboard'); - })->name('employee.dashboard'); + Route::get('/employee/index', function () { + return Inertia::render('employee/index'); + })->name('employee.index'); }); require __DIR__.'/settings.php'; \ No newline at end of file