Merge branch 'main' of https://github.com/IlhamIslamy/HR_Management__App
This commit is contained in:
commit
7d4e9fe307
|
|
@ -49,6 +49,7 @@ public function store(Request $request)
|
|||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
'nip' => 'required|string|unique:employees,nip',
|
||||
'gender' => 'required|in:L,P',
|
||||
'place_of_birth' => 'nullable|string|max:255',
|
||||
|
|
@ -64,7 +65,7 @@ public function store(Request $request)
|
|||
$user = User::create([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'password' => Hash::make('password123'),
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
Employee::create([
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
use App\Models\Employee;
|
||||
use App\Models\Payroll;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class PayrollController extends Controller
|
||||
|
|
@ -58,12 +59,19 @@ public function store(Request $request)
|
|||
{
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:employees,id',
|
||||
'period' => 'required|string|max:7',
|
||||
'period' => [
|
||||
'required',
|
||||
'date_format:Y-m',
|
||||
Rule::unique('payrolls')->where(fn ($query) => $query->where('employee_id', $request->employee_id)),
|
||||
],
|
||||
'basic_salary' => 'required|integer|min:0',
|
||||
'details' => 'nullable|array',
|
||||
'details.*.name' => 'required|string|max:100',
|
||||
'details.*.type' => 'required|in:bonus,deduction',
|
||||
'details.*.amount' => 'required|integer|min:0',
|
||||
], [
|
||||
'period.date_format' => 'Format periode harus YYYY-MM.',
|
||||
'period.unique' => 'Payroll karyawan untuk periode ini sudah ada.',
|
||||
]);
|
||||
|
||||
// net_salary = basic_salary + Σbonus − Σpotongan, minimal 0
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public function store(Request $request)
|
|||
'unique:users,employee_id',
|
||||
],
|
||||
'email' => 'required|email|max:255|unique:users,email',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
'role' => ['required', Rule::in(['admin', 'employee'])],
|
||||
], [
|
||||
'employee_id.required' => 'Karyawan wajib dipilih.',
|
||||
|
|
@ -56,6 +57,9 @@ public function store(Request $request)
|
|||
'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.',
|
||||
]);
|
||||
|
|
@ -65,13 +69,13 @@ public function store(Request $request)
|
|||
User::create([
|
||||
'name' => $employee->name,
|
||||
'email' => $validated['email'],
|
||||
'password' => Hash::make('password'),
|
||||
'password' => Hash::make($validated['password']),
|
||||
'role' => $validated['role'],
|
||||
'employee_id' => $employee->id,
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.users.index')
|
||||
->with('success', "Akun untuk {$employee->name} berhasil dibuat. Password default: password");
|
||||
->with('success', "Akun untuk {$employee->name} berhasil dibuat.");
|
||||
}
|
||||
|
||||
public function update(Request $request, User $user)
|
||||
|
|
|
|||
|
|
@ -41,4 +41,9 @@ public function position()
|
|||
{
|
||||
return $this->belongsTo(Position::class);
|
||||
}
|
||||
|
||||
public function attendances()
|
||||
{
|
||||
return $this->hasMany(Attendance::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Duplicate migration kept as a no-op so existing schema is not recreated.
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Intentionally no-op.
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Duplicate migration kept as a no-op so existing schema is not recreated.
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Intentionally no-op.
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('payrolls', function (Blueprint $table) {
|
||||
$table->unique(['employee_id', 'period'], 'payrolls_employee_period_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('payrolls', function (Blueprint $table) {
|
||||
$table->dropUnique('payrolls_employee_period_unique');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('attendances', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('employee_id')->constrained()->onDelete('cascade');
|
||||
$table->date('date');
|
||||
$table->enum('shift', ['Pagi', 'Siang', 'Malam']);
|
||||
$table->dateTime('check_in')->nullable();
|
||||
$table->dateTime('check_out')->nullable();
|
||||
$table->enum('status', ['hadir', 'izin', 'sakit', 'alpha'])->default('hadir');
|
||||
$table->string('notes')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['employee_id', 'date'], 'attendances_employee_date_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('attendances');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
<mxfile host="app.diagrams.net" modified="2026-04-21T00:00:00.000Z" agent="GitHub Copilot" version="24.7.17" type="device">
|
||||
<diagram id="flowchart-aplikasi" name="Flowchart Aplikasi HR Management">
|
||||
<mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="2200" pageHeight="1400" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
|
||||
<mxCell id="2" value="User membuka aplikasi" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="40" y="60" width="160" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="/" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="250" y="60" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="4" value="Redirect ke /login" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="420" y="60" width="170" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="5" value="Login Fortify" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="630" y="60" width="150" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="6" value="Login valid?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="825" y="50" width="120" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="Email verified?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="1000" y="50" width="140" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8" value="Role user" style="rhombus;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="1200" y="50" width="120" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="9" value="Halaman verifikasi email" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="995" y="180" width="150" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="/dashboard" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="1200" y="180" width="120" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="/employee/index" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="1200" y="300" width="140" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="12" value="DashboardController@index" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1400" y="180" width="190" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="Role admin?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="1625" y="170" width="120" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" value="Dashboard admin" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="1800" y="180" width="150" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="Redirect ke employee.index" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1620" y="300" width="180" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="16" value="Statistik, grafik, latest employees" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1980" y="180" width="190" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="17" value="Departments" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1800" y="320" width="120" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="18" value="Positions" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1960" y="320" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="Employees" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="2110" y="320" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="Attendance" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1800" y="410" width="120" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" value="Payrolls" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1960" y="410" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="22" value="Users" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="2110" y="410" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" value="Settings" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1960" y="500" width="110" height="50" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="24" value="Profile / Password / Appearance / 2FA" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="1620" y="500" width="260" height="60" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="25" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="2" target="3">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="26" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="3" target="4">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="27" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="4" target="5">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="28" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="5" target="6">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="29" value="ya" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="6" target="7">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" value="tidak" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="6" target="5">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="800" y="140" as="sourcePoint"/>
|
||||
<mxPoint x="700" y="140" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="31" value="tidak" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="7" target="9">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="32" value="ya" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="7" target="8">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="33" value="admin" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="8" target="10">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="34" value="employee" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="8" target="11">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="1280" y="150"/>
|
||||
<mxPoint x="1280" y="270"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="35" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="10" target="12">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="36" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="12" target="13">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="37" value="ya" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="13" target="14">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="38" value="tidak" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="13" target="15">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="39" value="menampilkan ringkasan" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="14" target="16">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="40" value="menu admin" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="14" target="17">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="41" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="17" target="18">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="42" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="18" target="19">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="43" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="19" target="20">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="44" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="20" target="21">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="45" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="21" target="22">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="46" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="22" target="23">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="47" value="" style="endArrow=block;html=1;rounded=0;orthogonalLoop=1;jettySize=auto;" edge="1" parent="1" source="23" target="24">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
# Flowchart Aplikasi HR Management
|
||||
|
||||
Dokumen ini merangkum alur aplikasi berdasarkan route, controller, dan fitur yang saat ini aktif di project.
|
||||
|
||||
## Alur Utama
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[User membuka aplikasi] --> B[/ /]
|
||||
B --> C[Redirect ke /login]
|
||||
C --> D[Halaman login Fortify]
|
||||
D --> E{Login valid?}
|
||||
E -- Tidak --> D
|
||||
E -- Ya --> F{Email sudah verified?}
|
||||
F -- Tidak --> G[Halaman verifikasi email]
|
||||
G --> D
|
||||
F -- Ya --> H{Role user}
|
||||
H -- admin --> I[/dashboard]
|
||||
H -- employee --> J[/employee/index]
|
||||
|
||||
I --> K[DashboardController@index]
|
||||
K --> L{Role admin?}
|
||||
L -- Tidak --> J
|
||||
L -- Ya --> M[Dashboard admin]
|
||||
|
||||
M --> N[Statistik total karyawan, departemen, jabatan]
|
||||
M --> O[Grafik gender]
|
||||
M --> P[Grafik departemen]
|
||||
M --> Q[Daftar 5 karyawan terbaru]
|
||||
|
||||
M --> R[Menu admin]
|
||||
R --> S[Departments]
|
||||
R --> T[Positions]
|
||||
R --> U[Employees]
|
||||
R --> V[Attendance]
|
||||
R --> W[Payrolls]
|
||||
R --> X[Users]
|
||||
R --> Y[Settings]
|
||||
|
||||
J --> Z[Halaman employee/index]
|
||||
Z --> Y
|
||||
```
|
||||
|
||||
## Detail Alur per Modul
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph AUTH[Autentikasi]
|
||||
A1[Root /] --> A2[Redirect ke /login]
|
||||
A2 --> A3[Login / Register / Forgot Password / Reset Password]
|
||||
A3 --> A4{Login berhasil?}
|
||||
A4 -- Tidak --> A3
|
||||
A4 -- Ya --> A5{Email verified?}
|
||||
A5 -- Tidak --> A6[Verify email]
|
||||
A6 --> A3
|
||||
A5 -- Ya --> A7[Home /dashboard]
|
||||
end
|
||||
|
||||
subgraph DASH[Dashboard]
|
||||
D1[/dashboard/] --> D2[DashboardController@index]
|
||||
D2 --> D3[Jika role bukan admin, redirect ke employee.index]
|
||||
D2 --> D4[Jika admin, tampilkan ringkasan HR]
|
||||
D4 --> D5[Total employee, department, position]
|
||||
D4 --> D6[Gender chart]
|
||||
D4 --> D7[Department chart]
|
||||
D4 --> D8[Latest employees]
|
||||
end
|
||||
|
||||
subgraph DEPT[Departments]
|
||||
E1[Index] --> E2[Create]
|
||||
E2 --> E3[Store]
|
||||
E1 --> E4[Edit]
|
||||
E4 --> E5[Update]
|
||||
E1 --> E6[Destroy]
|
||||
E6 --> E7{Masih ada karyawan?}
|
||||
E7 -- Ya --> E8[Gagal hapus]
|
||||
E7 -- Tidak --> E9[Departemen terhapus]
|
||||
end
|
||||
|
||||
subgraph POS[Positions]
|
||||
P1[Index] --> P2[Create]
|
||||
P2 --> P3[Store]
|
||||
P1 --> P4[Edit]
|
||||
P4 --> P5[Update]
|
||||
P1 --> P6[Destroy]
|
||||
P6 --> P7{Masih ada karyawan?}
|
||||
P7 -- Ya --> P8[Gagal hapus]
|
||||
P7 -- Tidak --> P9[Jabatan terhapus]
|
||||
end
|
||||
|
||||
subgraph EMP[Employees]
|
||||
M1[Index + filter search dan department]
|
||||
M1 --> M2[Create]
|
||||
M2 --> M3[Isi biodata, departemen, jabatan, akun login]
|
||||
M3 --> M4[Store]
|
||||
M4 --> M5[Create user]
|
||||
M5 --> M6[Create employee]
|
||||
M1 --> M7[Edit]
|
||||
M7 --> M8[Update user dan employee]
|
||||
M1 --> M9[Destroy]
|
||||
M9 --> M10[Delete user terkait]
|
||||
end
|
||||
|
||||
subgraph ATT[Attendance]
|
||||
T1[Index + filter date, status, department, search]
|
||||
T1 --> T2[Create]
|
||||
T2 --> T3[Pilih employee]
|
||||
T3 --> T4[Store]
|
||||
T4 --> T5[Validasi unik per employee per tanggal]
|
||||
end
|
||||
|
||||
subgraph PAY[Payrolls]
|
||||
R1[Index payroll]
|
||||
R1 --> R2[Create]
|
||||
R2 --> R3[Pilih employee dan data salary]
|
||||
R3 --> R4[Store]
|
||||
R4 --> R5[Hitung net salary]
|
||||
R5 --> R6[Simpan status pending]
|
||||
R1 --> R7[Show detail payroll]
|
||||
end
|
||||
|
||||
subgraph USERS[Users]
|
||||
U1[Index user]
|
||||
U1 --> U2[Create]
|
||||
U2 --> U3[Pilih employee yang belum punya akun]
|
||||
U3 --> U4[Store]
|
||||
U4 --> U5[Create user + link employee_id]
|
||||
U1 --> U6[Update role]
|
||||
end
|
||||
|
||||
subgraph SETTING[Settings]
|
||||
S1[Profile] --> S2[Update profile]
|
||||
S1 --> S3[Delete profile]
|
||||
S4[Password] --> S5[Update password]
|
||||
S6[Appearance] --> S7[Theme page]
|
||||
S8[Two factor] --> S9[Two-factor page]
|
||||
end
|
||||
```
|
||||
|
||||
## Ringkasan Urutan Penggunaan
|
||||
|
||||
1. User membuka aplikasi, lalu diarahkan ke halaman login.
|
||||
2. User login melalui Fortify.
|
||||
3. Sistem mengecek verifikasi email.
|
||||
4. Jika role admin, user masuk ke dashboard admin dan mengelola master data, absensi, payroll, dan user akun.
|
||||
5. Jika role employee, user diarahkan ke halaman employee.
|
||||
6. Seluruh user tetap bisa mengakses menu settings sesuai autentikasi dan verifikasi.
|
||||
|
||||
Jika kamu mau, saya bisa lanjut ubah flowchart ini menjadi versi gambar yang lebih rapi untuk presentasi, atau saya buatkan versi khusus per modul dalam satu diagram terpisah.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "HRIS_App",
|
||||
"name": "HR_Management__App",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ export function AppSidebar() {
|
|||
href: '/admin/positions',
|
||||
icon: Briefcase,
|
||||
},
|
||||
{
|
||||
title: 'Manajemen Absensi',
|
||||
href: '/admin/attendance',
|
||||
icon: CalendarCheck2,
|
||||
},
|
||||
{
|
||||
title: 'Manajemen Gaji',
|
||||
href: '/admin/payrolls',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
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';
|
||||
|
||||
interface Employee {
|
||||
id: number;
|
||||
name: string;
|
||||
nip: string;
|
||||
department: string | null;
|
||||
}
|
||||
|
||||
interface PageProps {
|
||||
employees: Employee[];
|
||||
}
|
||||
|
||||
export default function Create({ employees }: PageProps) {
|
||||
const { data, setData, post, processing, errors } = useForm({
|
||||
employee_id: '',
|
||||
date: '',
|
||||
shift: '',
|
||||
status: 'hadir',
|
||||
check_in: '',
|
||||
check_out: '',
|
||||
notes: '',
|
||||
});
|
||||
|
||||
const selectedEmployee = employees.find((employee) => String(employee.id) === data.employee_id) ?? null;
|
||||
|
||||
const submit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
post('/admin/attendance', {
|
||||
transform: (formData) => ({
|
||||
...formData,
|
||||
check_in: formData.check_in || null,
|
||||
check_out: formData.check_out || null,
|
||||
notes: formData.notes || null,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<Head title="Input Absensi" />
|
||||
|
||||
<div className="p-4 md:p-8 max-w-3xl mx-auto">
|
||||
<Button variant="outline" asChild className="mb-6">
|
||||
<Link href="/admin/attendance">Kembali</Link>
|
||||
</Button>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Input Absensi Karyawan</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">Isi data absensi harian untuk 1 karyawan per tanggal.</p>
|
||||
</CardHeader>
|
||||
|
||||
<Separator />
|
||||
|
||||
<CardContent className="pt-6">
|
||||
<form onSubmit={submit} className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label>Karyawan</Label>
|
||||
<Select value={data.employee_id} onValueChange={(value) => setData('employee_id', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih karyawan" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{employees.map((employee) => (
|
||||
<SelectItem key={employee.id} value={String(employee.id)}>
|
||||
{employee.name} ({employee.nip})
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.employee_id && <p className="text-xs text-red-500">{errors.employee_id}</p>}
|
||||
</div>
|
||||
|
||||
{selectedEmployee && (
|
||||
<div className="rounded-md border bg-muted/40 px-4 py-3 text-sm">
|
||||
<span className="text-muted-foreground">Departemen: </span>
|
||||
<span className="font-medium">{selectedEmployee.department || '-'}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Tanggal</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={data.date}
|
||||
onChange={(e) => setData('date', e.target.value)}
|
||||
/>
|
||||
{errors.date && <p className="text-xs text-red-500">{errors.date}</p>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Shift</Label>
|
||||
<Select value={data.shift} onValueChange={(value) => setData('shift', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih shift" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Pagi">Pagi</SelectItem>
|
||||
<SelectItem value="Siang">Siang</SelectItem>
|
||||
<SelectItem value="Malam">Malam</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.shift && <p className="text-xs text-red-500">{errors.shift}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Jam Masuk</Label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={data.check_in}
|
||||
onChange={(e) => setData('check_in', e.target.value)}
|
||||
/>
|
||||
{errors.check_in && <p className="text-xs text-red-500">{errors.check_in}</p>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Jam Pulang</Label>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={data.check_out}
|
||||
onChange={(e) => setData('check_out', e.target.value)}
|
||||
/>
|
||||
{errors.check_out && <p className="text-xs text-red-500">{errors.check_out}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Status</Label>
|
||||
<Select value={data.status} onValueChange={(value) => setData('status', value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Pilih status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="hadir">Hadir</SelectItem>
|
||||
<SelectItem value="izin">Izin</SelectItem>
|
||||
<SelectItem value="sakit">Sakit</SelectItem>
|
||||
<SelectItem value="alpha">Alpha</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.status && <p className="text-xs text-red-500">{errors.status}</p>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Catatan</Label>
|
||||
<Input
|
||||
value={data.notes}
|
||||
onChange={(e) => setData('notes', e.target.value)}
|
||||
placeholder="Opsional"
|
||||
/>
|
||||
{errors.notes && <p className="text-xs text-red-500">{errors.notes}</p>}
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button type="submit" disabled={processing}>
|
||||
{processing ? 'Menyimpan...' : 'Simpan Absensi'}
|
||||
</Button>
|
||||
<Button type="button" variant="ghost" asChild>
|
||||
<Link href="/admin/attendance">Batal</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Head, Link, router, usePage } from '@inertiajs/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import AppLayout from '@/layouts/app-layout';
|
||||
|
||||
interface Attendance {
|
||||
id: number;
|
||||
date: string;
|
||||
shift: 'Pagi' | 'Siang' | 'Malam';
|
||||
status: 'hadir' | 'izin' | 'sakit' | 'alpha';
|
||||
check_in: string | null;
|
||||
check_out: string | null;
|
||||
notes: string | null;
|
||||
employee: {
|
||||
id: number;
|
||||
name: string;
|
||||
nip: string;
|
||||
department: {
|
||||
id: number;
|
||||
name: string;
|
||||
} | null;
|
||||
};
|
||||
}
|
||||
|
||||
interface PageProps {
|
||||
attendances: {
|
||||
data: Attendance[];
|
||||
links: any[];
|
||||
};
|
||||
departments: { id: number; name: string }[];
|
||||
filters: {
|
||||
search?: string;
|
||||
status?: string;
|
||||
date?: string;
|
||||
department_id?: string;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface SharedData {
|
||||
flash: {
|
||||
success?: string;
|
||||
error?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const statusClass: Record<Attendance['status'], string> = {
|
||||
hadir: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-100',
|
||||
izin: 'bg-blue-100 text-blue-700 hover:bg-blue-100',
|
||||
sakit: 'bg-amber-100 text-amber-700 hover:bg-amber-100',
|
||||
alpha: 'bg-rose-100 text-rose-700 hover:bg-rose-100',
|
||||
};
|
||||
|
||||
function formatDate(value: string): string {
|
||||
return new Date(value).toLocaleDateString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
function formatTime(value: string | null): string {
|
||||
if (!value) return '-';
|
||||
return new Date(value).toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
|
||||
export default function Index({ attendances, departments, filters }: PageProps) {
|
||||
const { flash } = usePage<any>().props as SharedData;
|
||||
|
||||
const [search, setSearch] = useState(filters.search || '');
|
||||
const [status, setStatus] = useState(filters.status || 'all');
|
||||
const [departmentId, setDepartmentId] = useState(filters.department_id || 'all');
|
||||
const [date, setDate] = useState(filters.date || '');
|
||||
const [debouncedSearch] = useDebounce(search, 500);
|
||||
|
||||
useEffect(() => {
|
||||
router.get(
|
||||
'/admin/attendance',
|
||||
{
|
||||
search: debouncedSearch || '',
|
||||
status: status === 'all' ? '' : status,
|
||||
department_id: departmentId === 'all' ? '' : departmentId,
|
||||
date,
|
||||
},
|
||||
{ preserveState: true, replace: true }
|
||||
);
|
||||
}, [debouncedSearch, status, departmentId, date]);
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<Head title="Manajemen Absensi" />
|
||||
|
||||
<div className="p-4 md:p-8 w-full space-y-4">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-2xl font-bold tracking-tight">Manajemen Absensi</h2>
|
||||
<p className="text-muted-foreground">Pantau absensi harian karyawan berdasarkan tanggal, status, dan departemen.</p>
|
||||
</div>
|
||||
|
||||
{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>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-4">
|
||||
<CardTitle>Daftar Absensi</CardTitle>
|
||||
<Button asChild>
|
||||
<Link href="/admin/attendance/create">+ Input Absensi</Link>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="p-4 grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Input
|
||||
placeholder="Cari nama / NIP"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => setDate(e.target.value)}
|
||||
/>
|
||||
|
||||
<Select value={status} onValueChange={setStatus}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Filter status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Semua Status</SelectItem>
|
||||
<SelectItem value="hadir">Hadir</SelectItem>
|
||||
<SelectItem value="izin">Izin</SelectItem>
|
||||
<SelectItem value="sakit">Sakit</SelectItem>
|
||||
<SelectItem value="alpha">Alpha</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={departmentId} onValueChange={setDepartmentId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Filter departemen" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Semua Departemen</SelectItem>
|
||||
{departments.map((dept) => (
|
||||
<SelectItem key={dept.id} value={String(dept.id)}>
|
||||
{dept.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<CardContent className="pt-0">
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table className="w-full text-sm text-left">
|
||||
<thead className="bg-zinc-50/50 text-muted-foreground">
|
||||
<tr className="border-b">
|
||||
<th className="h-10 px-4 font-medium">Tanggal</th>
|
||||
<th className="h-10 px-4 font-medium">Karyawan</th>
|
||||
<th className="h-10 px-4 font-medium">Shift</th>
|
||||
<th className="h-10 px-4 font-medium">Masuk</th>
|
||||
<th className="h-10 px-4 font-medium">Pulang</th>
|
||||
<th className="h-10 px-4 font-medium">Status</th>
|
||||
<th className="h-10 px-4 font-medium">Catatan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{attendances.data.length > 0 ? (
|
||||
attendances.data.map((attendance) => (
|
||||
<tr key={attendance.id} className="border-b hover:bg-zinc-50">
|
||||
<td className="p-4">{formatDate(attendance.date)}</td>
|
||||
<td className="p-4">
|
||||
<div className="font-semibold">{attendance.employee.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
NIP: {attendance.employee.nip} | {attendance.employee.department?.name || '-'}
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">{attendance.shift}</td>
|
||||
<td className="p-4">{formatTime(attendance.check_in)}</td>
|
||||
<td className="p-4">{formatTime(attendance.check_out)}</td>
|
||||
<td className="p-4">
|
||||
<Badge className={statusClass[attendance.status]}>
|
||||
{attendance.status.toUpperCase()}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-4 text-muted-foreground">{attendance.notes || '-'}</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={7} className="p-8 text-center text-muted-foreground">
|
||||
Belum ada data absensi.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -18,6 +18,8 @@ export default function Create({ departments, positions }: PageProps) {
|
|||
const { data, setData, post, processing, errors } = useForm({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
nip: '',
|
||||
gender: '',
|
||||
birth_date: '',
|
||||
|
|
@ -83,6 +85,25 @@ export default function Create({ departments, positions }: PageProps) {
|
|||
{errors.email && <div className="text-red-500 text-xs">{errors.email}</div>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Password Login</Label>
|
||||
<Input
|
||||
type="password"
|
||||
value={data.password}
|
||||
onChange={e => setData('password', e.target.value)}
|
||||
/>
|
||||
{errors.password && <div className="text-red-500 text-xs">{errors.password}</div>}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Konfirmasi Password</Label>
|
||||
<Input
|
||||
type="password"
|
||||
value={data.password_confirmation}
|
||||
onChange={e => setData('password_confirmation', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>No. Telepon</Label>
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ export default function Create({ employees }: PageProps) {
|
|||
const { data, setData, post, processing, errors } = useForm({
|
||||
employee_id: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
role: '',
|
||||
});
|
||||
|
||||
|
|
@ -104,9 +106,6 @@ export default function Create({ employees }: PageProps) {
|
|||
<div className="rounded-md bg-muted/40 border px-4 py-3 text-sm">
|
||||
<p className="text-muted-foreground text-xs mb-1">Nama akun akan dibuat sebagai:</p>
|
||||
<p className="font-semibold">{selectedEmployee.name}</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
Password default: <span className="font-mono font-medium">password</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -126,6 +125,32 @@ export default function Create({ employees }: PageProps) {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
Password <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Minimal 8 karakter"
|
||||
value={data.password}
|
||||
onChange={(e) => setData('password', e.target.value)}
|
||||
/>
|
||||
{errors.password && (
|
||||
<p className="text-xs text-red-500">{errors.password}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
Konfirmasi Password <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="password"
|
||||
value={data.password_confirmation}
|
||||
onChange={(e) => setData('password_confirmation', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Role */}
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
use App\Http\Controllers\Admin\DepartmentController;
|
||||
use App\Http\Controllers\Admin\EmployeeController;
|
||||
use App\Http\Controllers\Admin\AttendanceController;
|
||||
use App\Http\Controllers\Admin\PayrollController;
|
||||
use App\Http\Controllers\Admin\PositionController;
|
||||
use App\Http\Controllers\Admin\UserController;
|
||||
use App\Http\Controllers\DashboardController;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
// Redirect root ke halaman login
|
||||
|
|
@ -22,6 +24,7 @@
|
|||
Route::resource('departments', DepartmentController::class);
|
||||
Route::resource('positions', PositionController::class);
|
||||
Route::resource('employees', EmployeeController::class);
|
||||
Route::resource('attendance', AttendanceController::class)->only(['index', 'create', 'store']);
|
||||
Route::resource('payrolls', PayrollController::class)->only(['index', 'create', 'store', 'show']);
|
||||
Route::get('attendances', [\App\Http\Controllers\Admin\AttendanceController::class, 'index'])->name('attendances.index');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue