Feat: Role Middleware with CRUD Manajemen Pengguna
This commit is contained in:
parent
6255b83d95
commit
9fb75ac5cd
|
|
@ -5,6 +5,8 @@
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class UserController extends Controller
|
||||
|
|
@ -14,15 +16,49 @@ public function index()
|
|||
return Inertia::render('admin/users/index', [
|
||||
'users' => User::select('id', 'name', 'email', 'role', 'created_at')
|
||||
->latest()
|
||||
->get()
|
||||
->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)
|
||||
{
|
||||
// Validasi perpindahan role
|
||||
$validated = $request->validate([
|
||||
'role' => 'required|in:admin,employee',
|
||||
'role' => ['required', Rule::in(['admin', 'employee'])],
|
||||
]);
|
||||
|
||||
$user->update($validated);
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Responses;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
|
||||
|
||||
class LoginResponse implements LoginResponseContract
|
||||
{
|
||||
/**
|
||||
* Redirect user based on their role after a successful login.
|
||||
*/
|
||||
public function toResponse($request): RedirectResponse|JsonResponse
|
||||
{
|
||||
$role = $request->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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -18,12 +18,54 @@ public function run(): void
|
|||
'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',
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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,17 +13,17 @@ 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<SharedData>().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' ? [
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<AppLayout breadcrumbs={breadcrumbs}>
|
||||
<Head title="Tambah User Baru" />
|
||||
|
||||
<div className="p-4 md:p-8 w-full">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">Tambah User Baru</h2>
|
||||
<p className="text-muted-foreground">Buat akun pengguna baru beserta hak aksesnya.</p>
|
||||
</div>
|
||||
<Button variant="outline" asChild>
|
||||
<Link href="/admin/users">Kembali</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Form User</CardTitle>
|
||||
</CardHeader>
|
||||
<Separator />
|
||||
<CardContent className="pt-6">
|
||||
<form onSubmit={submit} className="space-y-6">
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Nama */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">
|
||||
Nama Lengkap <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="Contoh: Budi Santoso"
|
||||
value={data.name}
|
||||
onChange={(e) => setData('name', e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="text-red-500 text-xs">{errors.name}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">
|
||||
Email <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Contoh: budi@hris.com"
|
||||
value={data.email}
|
||||
onChange={(e) => setData('email', e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="text-red-500 text-xs">{errors.email}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Password */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">
|
||||
Password <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Minimal 8 karakter"
|
||||
value={data.password}
|
||||
onChange={(e) => setData('password', e.target.value)}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
{errors.password && (
|
||||
<p className="text-red-500 text-xs">{errors.password}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Konfirmasi Password */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password_confirmation">
|
||||
Konfirmasi Password <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="password_confirmation"
|
||||
type="password"
|
||||
placeholder="Ulangi password di atas"
|
||||
value={data.password_confirmation}
|
||||
onChange={(e) => setData('password_confirmation', e.target.value)}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
{errors.password_confirmation && (
|
||||
<p className="text-red-500 text-xs">{errors.password_confirmation}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Role */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="role">
|
||||
Role / Hak Akses <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
value={data.role}
|
||||
onValueChange={(val) => setData('role', val)}
|
||||
>
|
||||
<SelectTrigger id="role" className="w-full">
|
||||
<SelectValue placeholder="Pilih role..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="employee">Employee</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.role && (
|
||||
<p className="text-red-500 text-xs">{errors.role}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-4 pt-4">
|
||||
<Button type="button" variant="ghost" asChild>
|
||||
<Link href="/admin/users">Batal</Link>
|
||||
</Button>
|
||||
<Button type="submit" disabled={processing}>
|
||||
{processing ? 'Menyimpan...' : 'Simpan User'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,48 +1,112 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Head, useForm } from '@inertiajs/react';
|
||||
import React from 'react';
|
||||
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 { 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';
|
||||
|
||||
export default function Index({ users }: { users: any[] }) {
|
||||
const { patch, processing } = useForm();
|
||||
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<any>().props;
|
||||
const [processingId, setProcessingId] = useState<number | null>(null);
|
||||
|
||||
const updateRole = (id: number, role: string) => {
|
||||
patch(route('admin.users.update', id), {
|
||||
data: { role: role },
|
||||
setProcessingId(id);
|
||||
router.patch(
|
||||
`/admin/users/${id}`,
|
||||
{ role },
|
||||
{
|
||||
preserveScroll: true,
|
||||
});
|
||||
onFinish: () => setProcessingId(null),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
<AppLayout breadcrumbs={breadcrumbs}>
|
||||
<Head title="Manajemen Pengguna" />
|
||||
|
||||
<div className="p-4 md:p-8 w-full space-y-4">
|
||||
|
||||
{/* Flash Messages */}
|
||||
{flash?.success && (
|
||||
<div className="p-4 bg-green-50 text-green-700 border border-green-200 rounded-md text-sm">
|
||||
{flash.success}
|
||||
</div>
|
||||
)}
|
||||
{flash?.error && (
|
||||
<div className="p-4 bg-red-50 text-red-700 border border-red-200 rounded-md text-sm">
|
||||
{flash.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">Manajemen Akses User</h2>
|
||||
<p className="text-muted-foreground">Kelola akun dan hak akses pengguna sistem.</p>
|
||||
</div>
|
||||
<Button asChild>
|
||||
<Link href="/admin/users/create">+ Tambah User Baru</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Tabel */}
|
||||
<Card>
|
||||
<CardHeader><CardTitle>Daftar Pengguna</CardTitle></CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<CardHeader className="pb-4">
|
||||
<CardTitle>Daftar Pengguna</CardTitle>
|
||||
</CardHeader>
|
||||
<Separator />
|
||||
<CardContent className="pt-0">
|
||||
<div className="relative w-full overflow-auto">
|
||||
<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>
|
||||
<thead className="text-muted-foreground bg-zinc-50/50">
|
||||
<tr className="border-b">
|
||||
<th className="h-12 px-4 font-medium">Nama</th>
|
||||
<th className="h-12 px-4 font-medium">Email</th>
|
||||
<th className="h-12 px-4 font-medium text-center">Role Saat Ini</th>
|
||||
<th className="h-12 px-4 font-medium text-right">Ubah Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr key={user.id} className="border-b">
|
||||
{users.length > 0 ? (
|
||||
users.map((user) => (
|
||||
<tr key={user.id} className="border-b hover:bg-zinc-50">
|
||||
<td className="p-4 font-medium">{user.name}</td>
|
||||
<td className="p-4 text-muted-foreground">{user.email}</td>
|
||||
<td className="p-4 text-center">
|
||||
<span className="bg-zinc-100 px-2 py-1 rounded text-[10px] font-bold">
|
||||
<span className="bg-zinc-100 px-2.5 py-1 rounded-full text-[11px] font-semibold tracking-wide">
|
||||
{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>
|
||||
<Select
|
||||
defaultValue={user.role}
|
||||
onValueChange={(val) => updateRole(user.id, val)}
|
||||
disabled={processingId === user.id}
|
||||
>
|
||||
<SelectTrigger className="w-[130px] ml-auto">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="employee">Employee</SelectItem>
|
||||
|
|
@ -50,9 +114,17 @@ export default function Index({ users }: { users: any[] }) {
|
|||
</Select>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={4} className="p-8 text-center text-muted-foreground">
|
||||
Belum ada data pengguna.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<SharedData>().props;
|
||||
const user = auth.user;
|
||||
|
||||
return (
|
||||
<AppLayout breadcrumbs={breadcrumbs}>
|
||||
<Head title="Portal Karyawan" />
|
||||
|
||||
<div className="flex flex-1 flex-col gap-6 p-4 md:p-8">
|
||||
{/* Header Sambutan */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Selamat datang, {user.name}!
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Ini adalah portal SDM Anda. Pantau informasi akun Anda di sini.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Kartu Info Akun */}
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Nama Lengkap</CardTitle>
|
||||
<User2 className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-lg font-semibold">{user.name}</p>
|
||||
<p className="text-xs text-muted-foreground">Nama akun terdaftar</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Alamat Email</CardTitle>
|
||||
<Mail className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-lg font-semibold truncate">{user.email}</p>
|
||||
<p className="text-xs text-muted-foreground">Email untuk login</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Hak Akses</CardTitle>
|
||||
<ShieldCheck className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-2">
|
||||
<Badge variant="secondary" className="w-fit capitalize">
|
||||
{user.role}
|
||||
</Badge>
|
||||
<p className="text-xs text-muted-foreground">Role akun Anda saat ini</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
Loading…
Reference in New Issue