Feat: Roles management fiture
This commit is contained in:
parent
ae9e0a2eb0
commit
6255b83d95
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('admin/users/index', [
|
||||
'users' => 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.");
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -34,14 +34,17 @@ public function version(Request $request): ?string
|
|||
* @return array<string, mixed>
|
||||
*/
|
||||
public function share(Request $request): array
|
||||
{
|
||||
{
|
||||
return [
|
||||
...parent::share($request),
|
||||
'name' => config('app.name'),
|
||||
'auth' => [
|
||||
'user' => $request->user(),
|
||||
'user' => $request->user() ? [
|
||||
'id' => $request->user()->id,
|
||||
'name' => $request->user()->name,
|
||||
'email' => $request->user()->email,
|
||||
'role' => $request->user()->role,
|
||||
] : null,
|
||||
],
|
||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RoleMiddleware
|
||||
{
|
||||
public function handle(Request $request, Closure $next, string $role): Response
|
||||
{
|
||||
if (! $request->user() || $request->user()->role !== $role) {
|
||||
abort(403, 'Akses Ditolak.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,10 @@
|
|||
HandleInertiaRequests::class,
|
||||
AddLinkHeadersForPreloadedAssets::class,
|
||||
]);
|
||||
|
||||
$middleware->alias([
|
||||
'role' => \App\Http\Middleware\RoleMiddleware::class,
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Department;
|
||||
use App\Models\Position;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class MasterDataSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$depts = [
|
||||
['name' => '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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class UserSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
// Akun Admin Utama
|
||||
User::create([
|
||||
'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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,12 +16,17 @@ import { dashboard } from '@/routes';
|
|||
import type { NavItem } from '@/types';
|
||||
import AppLogo from './app-logo';
|
||||
|
||||
const mainNavItems: NavItem[] = [
|
||||
export function AppSidebar() {
|
||||
const { auth } = usePage().props as any;
|
||||
const userRole = auth.user.role;
|
||||
|
||||
const mainNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
href: '/dashboard',
|
||||
href: userRole === 'admin' ? '/dashboard' : '/employee/dashboard',
|
||||
icon: LayoutGrid,
|
||||
},
|
||||
...(userRole === 'admin' ? [
|
||||
{
|
||||
title: 'Manajemen Karyawan',
|
||||
href: '/admin/employees',
|
||||
|
|
@ -36,28 +42,21 @@ const mainNavItems: NavItem[] = [
|
|||
href: '/admin/positions',
|
||||
icon: Briefcase,
|
||||
},
|
||||
];
|
||||
{
|
||||
title: 'Manajemen Pengguna',
|
||||
href: '/admin/users',
|
||||
icon: SquareUser,
|
||||
},
|
||||
] : []),
|
||||
];
|
||||
|
||||
// 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 (
|
||||
<Sidebar collapsible="icon" variant="inset">
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" asChild>
|
||||
|
||||
<Link href={dashboard()} prefetch>
|
||||
<AppLogo />
|
||||
</Link>
|
||||
|
|
@ -71,7 +70,6 @@ export function AppSidebar() {
|
|||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
{/* <NavFooter items={footerNavItems} className="mt-auto" /> */}
|
||||
<NavUser />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<AppLayout>
|
||||
<Head title="Manajemen User" />
|
||||
<div className="p-4 md:p-8 w-full space-y-6">
|
||||
<h2 className="text-2xl font-bold">Manajemen Akses User</h2>
|
||||
<Card>
|
||||
<CardHeader><CardTitle>Daftar Pengguna</CardTitle></CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<table className="w-full text-sm text-left">
|
||||
<thead className="bg-zinc-50 border-b">
|
||||
<tr>
|
||||
<th className="p-4">Nama</th>
|
||||
<th className="p-4 text-center">Role</th>
|
||||
<th className="p-4 text-right">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr key={user.id} className="border-b">
|
||||
<td className="p-4 font-medium">{user.name}</td>
|
||||
<td className="p-4 text-center">
|
||||
<span className="bg-zinc-100 px-2 py-1 rounded text-[10px] font-bold">
|
||||
{user.role.toUpperCase()}
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-4 text-right">
|
||||
<Select defaultValue={user.role} onValueChange={(val) => updateRole(user.id, val)} disabled={processing}>
|
||||
<SelectTrigger className="w-[120px] ml-auto"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="employee">Employee</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<AppLayout>
|
||||
<Head title="Portal Karyawan" />
|
||||
|
||||
<div className="flex flex-1 flex-col gap-4 p-4 md:p-8">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-3xl font-bold">Halo, {auth.user.name}!</h1>
|
||||
<p className="text-muted-foreground italic">
|
||||
Selamat datang di portal SDM. Kamu login sebagai <span className="font-bold text-foreground underline">{auth.user.role}</span>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* Kotak-kotak info simpel untuk karyawan */}
|
||||
<div className="p-6 bg-white border rounded-xl shadow-sm space-y-2">
|
||||
<p className="text-xs font-bold text-zinc-400 uppercase">Email</p>
|
||||
<p className="font-medium">{auth.user.email}</p>
|
||||
</div>
|
||||
<div className="p-6 bg-white border rounded-xl shadow-sm space-y-2">
|
||||
<p className="text-xs font-bold text-zinc-400 uppercase">Status Akun</p>
|
||||
<p className="font-medium text-green-600">Aktif</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -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');
|
||||
});
|
||||
|
||||
// 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';
|
||||
Loading…
Reference in New Issue