feat: implement employee management module
This commit is contained in:
parent
d4fdf35a3c
commit
b62df688b7
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Employee;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Http\Requests\StoreEmployeeRequest;
|
||||||
|
use App\Http\Requests\UpdateEmployeeRequest;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
|
||||||
|
class EmployeeController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$employees = Employee::with('user')
|
||||||
|
->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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StoreEmployeeRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdateEmployeeRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class Employee extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'nip',
|
||||||
|
'position',
|
||||||
|
'status',
|
||||||
|
'phone',
|
||||||
|
'address',
|
||||||
|
'join_date'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ class User extends Authenticatable
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
|
'role',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,6 +37,16 @@ class User extends Authenticatable
|
||||||
'remember_token',
|
'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.
|
* Get the attributes that should be cast.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ public function up(): void
|
||||||
$table->string('email')->unique();
|
$table->string('email')->unique();
|
||||||
$table->timestamp('email_verified_at')->nullable();
|
$table->timestamp('email_verified_at')->nullable();
|
||||||
$table->string('password');
|
$table->string('password');
|
||||||
|
$table->enum('role', ['admin', 'employee'])->default('employee');
|
||||||
$table->rememberToken();
|
$table->rememberToken();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('employees', function (Blueprint $table) {
|
||||||
|
$table->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');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
|
|
@ -13,11 +13,12 @@ class DatabaseSeeder extends Seeder
|
||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// User::factory(10)->create();
|
User::create([
|
||||||
|
'name' => 'Super Admin',
|
||||||
User::factory()->create([
|
'email' => 'admin@zhanhui.com',
|
||||||
'name' => 'Test User',
|
'password' => Hash::make('password123'),
|
||||||
'email' => 'test@example.com',
|
'role' => 'admin',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ export default function AppLogo() {
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 grid flex-1 text-left text-sm">
|
<div className="ml-1 grid flex-1 text-left text-sm">
|
||||||
<span className="mb-0.5 truncate leading-tight font-semibold">
|
<span className="mb-0.5 truncate leading-tight font-semibold">
|
||||||
Laravel Starter Kit
|
SDM App
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Link } from '@inertiajs/react';
|
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 { NavFooter } from '@/components/nav-footer';
|
||||||
import { NavMain } from '@/components/nav-main';
|
import { NavMain } from '@/components/nav-main';
|
||||||
import { NavUser } from '@/components/nav-user';
|
import { NavUser } from '@/components/nav-user';
|
||||||
|
|
@ -22,20 +22,25 @@ const mainNavItems: NavItem[] = [
|
||||||
href: dashboard(),
|
href: dashboard(),
|
||||||
icon: LayoutGrid,
|
icon: LayoutGrid,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Manajemen Karyawan',
|
||||||
|
href: '/admin/employees',
|
||||||
|
icon: Users,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const footerNavItems: NavItem[] = [
|
// const footerNavItems: NavItem[] = [
|
||||||
{
|
// {
|
||||||
title: 'Repository',
|
// title: 'Repository',
|
||||||
href: 'https://github.com/laravel/react-starter-kit',
|
// href: 'https://github.com/laravel/react-starter-kit',
|
||||||
icon: Folder,
|
// icon: Folder,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
title: 'Documentation',
|
// title: 'Documentation',
|
||||||
href: 'https://laravel.com/docs/starter-kits#react',
|
// href: 'https://laravel.com/docs/starter-kits#react',
|
||||||
icon: BookOpen,
|
// icon: BookOpen,
|
||||||
},
|
// },
|
||||||
];
|
// ];
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -57,7 +62,7 @@ export function AppSidebar() {
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<NavFooter items={footerNavItems} className="mt-auto" />
|
{/* <NavFooter items={footerNavItems} className="mt-auto" /> */}
|
||||||
<NavUser />
|
<NavUser />
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
<AppLayout>
|
||||||
|
<Head title="Tambah Karyawan" />
|
||||||
|
<div className="p-8 max-w-2xl mx-auto">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Tambah Karyawan</CardTitle>
|
||||||
|
<CardDescription>Masukkan data detail karyawan baru.</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={submit} className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Nama Lengkap</Label>
|
||||||
|
<Input value={data.name} onChange={e => setData('name', e.target.value)} />
|
||||||
|
{errors.name && <p className="text-xs text-red-500">{errors.name}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Email</Label>
|
||||||
|
<Input type="email" value={data.email} onChange={e => setData('email', e.target.value)} />
|
||||||
|
{errors.email && <p className="text-xs text-red-500">{errors.email}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Password Default</Label>
|
||||||
|
<Input type="password" value={data.password} onChange={e => setData('password', e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>NIP</Label>
|
||||||
|
<Input value={data.nip} onChange={e => setData('nip', e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Tanggal Masuk</Label>
|
||||||
|
<Input type="date" value={data.join_date} onChange={e => setData('join_date', e.target.value)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Jabatan</Label>
|
||||||
|
<Select onValueChange={(val) => setData('position', val)}>
|
||||||
|
<SelectTrigger><SelectValue placeholder="Pilih" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Staff">Staff</SelectItem>
|
||||||
|
<SelectItem value="Supervisor">Supervisor</SelectItem>
|
||||||
|
<SelectItem value="Manager">Manager</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Status</Label>
|
||||||
|
<Select onValueChange={(val) => setData('status', val)}>
|
||||||
|
<SelectTrigger><SelectValue placeholder="Pilih" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="PKWT">PKWT</SelectItem>
|
||||||
|
<SelectItem value="PKWTT">PKWTT</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 pt-4">
|
||||||
|
<Button variant="outline" asChild><Link href="/admin/employees">Batal</Link></Button>
|
||||||
|
<Button type="submit" disabled={processing}>Simpan</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</AppLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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 (
|
||||||
|
<AppLayout>
|
||||||
|
<Head title={`Edit Karyawan - ${employee.user?.name}`} />
|
||||||
|
<div className="p-8 max-w-2xl mx-auto">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Edit Karyawan</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Perbarui informasi data diri dan kepegawaian karyawan.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={submit} className="space-y-4">
|
||||||
|
{/* Data Profil */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name">Nama Lengkap</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
value={data.name}
|
||||||
|
onChange={e => setData('name', e.target.value)}
|
||||||
|
/>
|
||||||
|
{errors.name && <p className="text-xs text-red-500">{errors.name}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={data.email}
|
||||||
|
onChange={e => setData('email', e.target.value)}
|
||||||
|
/>
|
||||||
|
{errors.email && <p className="text-xs text-red-500">{errors.email}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="password">Password Baru (Kosongkan jika tidak diganti)</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value={data.password}
|
||||||
|
onChange={e => setData('password', e.target.value)}
|
||||||
|
/>
|
||||||
|
{errors.password && <p className="text-xs text-red-500">{errors.password}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="my-4" />
|
||||||
|
|
||||||
|
{/* Data Kepegawaian */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="nip">NIP</Label>
|
||||||
|
<Input
|
||||||
|
id="nip"
|
||||||
|
value={data.nip}
|
||||||
|
onChange={e => setData('nip', e.target.value)}
|
||||||
|
/>
|
||||||
|
{errors.nip && <p className="text-xs text-red-500">{errors.nip}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="join_date">Tanggal Masuk</Label>
|
||||||
|
<Input
|
||||||
|
id="join_date"
|
||||||
|
type="date"
|
||||||
|
value={data.join_date}
|
||||||
|
onChange={e => setData('join_date', e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Jabatan</Label>
|
||||||
|
<Select
|
||||||
|
value={data.position}
|
||||||
|
onValueChange={(val) => setData('position', val)}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Pilih Jabatan" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Staff">Staff</SelectItem>
|
||||||
|
<SelectItem value="Supervisor">Supervisor</SelectItem>
|
||||||
|
<SelectItem value="Manager">Manager</SelectItem>
|
||||||
|
<SelectItem value="HRD">HRD</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Status Kerja</Label>
|
||||||
|
<Select
|
||||||
|
value={data.status}
|
||||||
|
onValueChange={(val) => setData('status', val)}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Pilih Status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="PKWT">PKWT</SelectItem>
|
||||||
|
<SelectItem value="PKWTT">PKWTT</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 pt-4">
|
||||||
|
<Button variant="outline" asChild>
|
||||||
|
<Link href="/admin/employees">Batal</Link>
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={processing}>
|
||||||
|
Simpan Perubahan
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</AppLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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 (
|
||||||
|
<AppLayout>
|
||||||
|
<Head title="Manajemen Karyawan" />
|
||||||
|
<div className="p-8">
|
||||||
|
{flash?.success && (
|
||||||
|
<div className="mb-4 p-4 bg-green-50 text-green-700 border rounded-md">
|
||||||
|
{flash.success}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
|
<CardTitle>Daftar Karyawan</CardTitle>
|
||||||
|
<Button asChild>
|
||||||
|
<Link href="/admin/employees/create">Tambah Karyawan</Link>
|
||||||
|
</Button>
|
||||||
|
</CardHeader>
|
||||||
|
<Separator />
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="text-left border-b">
|
||||||
|
<th className="pb-4 font-medium">Karyawan</th>
|
||||||
|
<th className="pb-4 font-medium">NIP & Jabatan</th>
|
||||||
|
<th className="pb-4 font-medium">Status</th>
|
||||||
|
<th className="pb-4 text-right font-medium">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{employees.data.map((employee) => (
|
||||||
|
<tr key={employee.id} className="border-b">
|
||||||
|
<td className="py-4">
|
||||||
|
<div className="font-bold">{employee.user?.name}</div>
|
||||||
|
<div className="text-xs text-muted-foreground">{employee.user?.email}</div>
|
||||||
|
</td>
|
||||||
|
<td className="py-4">
|
||||||
|
<div>{employee.nip}</div>
|
||||||
|
<Badge variant="outline">{employee.position}</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="py-4">
|
||||||
|
<Badge variant={employee.status === 'PKWTT' ? 'default' : 'secondary'}>
|
||||||
|
{employee.status}
|
||||||
|
</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 text-right">
|
||||||
|
<Button variant="ghost" asChild>
|
||||||
|
<Link href={`/admin/employees/${employee.id}/edit`}>Edit</Link>
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" className="text-red-600" asChild>
|
||||||
|
<Link href={`/admin/employees/${employee.id}`} method="delete" as="button">Hapus</Link>
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</AppLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Admin\EmployeeController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
use Laravel\Fortify\Features;
|
use Laravel\Fortify\Features;
|
||||||
|
|
@ -14,4 +15,7 @@
|
||||||
return Inertia::render('dashboard');
|
return Inertia::render('dashboard');
|
||||||
})->middleware(['auth', 'verified'])->name('dashboard');
|
})->middleware(['auth', 'verified'])->name('dashboard');
|
||||||
|
|
||||||
|
Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(function () {
|
||||||
|
Route::resource('employees', EmployeeController::class);
|
||||||
|
});
|
||||||
require __DIR__.'/settings.php';
|
require __DIR__.'/settings.php';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue