- Mengubah `user_id` menjadi nullable dan menambah kolom `email` pada tabel employees
- Memperbarui EmployeeController agar pembuatan data karyawan tidak otomatis membuat akun user - Menambahkan sinkronisasi update data karyawan ke akun user (jika sudah tertaut) - Menyesuaikan UI Frontend (Create, Edit, Index) agar membaca data langsung dari objek employee - Menambahkan icon Settings dan tombol Logout pada AdminLayout - Menyesuaikan host localhost pada konfigurasi Vite
This commit is contained in:
parent
285db59bde
commit
df88547cc3
|
|
@ -3,13 +3,12 @@
|
|||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreEmployeeRequest;
|
||||
use App\Http\Requests\UpdateEmployeeRequest;
|
||||
use App\Models\Department;
|
||||
use App\Models\Employee;
|
||||
use App\Models\Position;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class EmployeeController extends Controller
|
||||
|
|
@ -20,9 +19,7 @@ public function index(Request $request)
|
|||
|
||||
if ($request->search) {
|
||||
$query->where('nip', 'like', '%' . $request->search . '%')
|
||||
->orWhereHas('user', function ($q) use ($request) {
|
||||
$q->where('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
->orWhere('name', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
if ($request->department_id) {
|
||||
|
|
@ -44,69 +41,28 @@ public function create()
|
|||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
public function store(StoreEmployeeRequest $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'nip' => 'required|string|unique:employees,nip',
|
||||
'gender' => 'required|in:L,P',
|
||||
'place_of_birth' => 'nullable|string|max:255',
|
||||
'birth_date' => 'required|date',
|
||||
'address' => 'nullable|string',
|
||||
'phone_number' => 'nullable|string|max:20',
|
||||
'department_id' => 'required|exists:departments,id',
|
||||
'position_id' => 'required|exists:positions,id',
|
||||
'status' => 'required|in:PKWT,PKWTT,Magang',
|
||||
'join_date' => 'required|date',
|
||||
], [
|
||||
'name.required' => 'Nama lengkap wajib diisi.',
|
||||
'name.max' => 'Nama lengkap maksimal 255 karakter.',
|
||||
'email.required' => 'Email wajib diisi.',
|
||||
'email.email' => 'Format email tidak valid.',
|
||||
'email.unique' => 'Email sudah digunakan oleh karyawan lain.',
|
||||
'nip.required' => 'NIP wajib diisi.',
|
||||
'nip.unique' => 'NIP sudah terdaftar.',
|
||||
'gender.required' => 'Jenis kelamin wajib dipilih.',
|
||||
'gender.in' => 'Jenis kelamin hanya boleh Laki-laki atau Perempuan.',
|
||||
'place_of_birth.max' => 'Tempat lahir maksimal 255 karakter.',
|
||||
'birth_date.required' => 'Tanggal lahir wajib diisi.',
|
||||
'birth_date.date' => 'Format tanggal lahir tidak valid.',
|
||||
'phone_number.max' => 'Nomor telepon maksimal 20 karakter.',
|
||||
'department_id.required' => 'Departemen wajib dipilih.',
|
||||
'department_id.exists' => 'Departemen tidak ditemukan.',
|
||||
'position_id.required' => 'Jabatan wajib dipilih.',
|
||||
'position_id.exists' => 'Jabatan tidak ditemukan.',
|
||||
'status.required' => 'Status kepegawaian wajib dipilih.',
|
||||
'status.in' => 'Status hanya boleh PKWT, PKWTT, atau Magang.',
|
||||
'join_date.required' => 'Tanggal masuk wajib diisi.',
|
||||
'join_date.date' => 'Format tanggal masuk tidak valid.',
|
||||
]);
|
||||
$validated = $request->validated();
|
||||
|
||||
$user = User::create([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'password' => Hash::make('password'),
|
||||
'role' => 'employee',
|
||||
]);
|
||||
|
||||
$employee = Employee::create([
|
||||
'user_id' => $user->id,
|
||||
// Simpan hanya data karyawan ke tabel employees.
|
||||
// Pembuatan akun User dilakukan secara terpisah oleh admin
|
||||
// melalui menu Manajemen Pengguna.
|
||||
Employee::create([
|
||||
'nip' => $validated['nip'],
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'] ?? null,
|
||||
'gender' => $validated['gender'],
|
||||
'place_of_birth' => $validated['place_of_birth'],
|
||||
'place_of_birth' => $validated['place_of_birth'] ?? null,
|
||||
'birth_date' => $validated['birth_date'],
|
||||
'address' => $validated['address'],
|
||||
'phone_number' => $validated['phone_number'],
|
||||
'address' => $validated['address'] ?? null,
|
||||
'phone_number' => $validated['phone_number'] ?? null,
|
||||
'department_id' => $validated['department_id'],
|
||||
'position_id' => $validated['position_id'],
|
||||
'status' => $validated['status'],
|
||||
'join_date' => $validated['join_date'],
|
||||
]);
|
||||
|
||||
$user->update(['employee_id' => $employee->id]);
|
||||
|
||||
return redirect()->route('admin.employees.index')
|
||||
->with('success', 'Data karyawan berhasil ditambahkan.');
|
||||
}
|
||||
|
|
@ -122,59 +78,41 @@ public function edit(Employee $employee)
|
|||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Employee $employee)
|
||||
public function update(UpdateEmployeeRequest $request, Employee $employee)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => ['required', 'email', Rule::unique('users')->ignore($employee->user_id)],
|
||||
'nip' => ['required', 'string', Rule::unique('employees')->ignore($employee->id)],
|
||||
'gender' => 'required|in:L,P',
|
||||
'place_of_birth' => 'nullable|string|max:255',
|
||||
'birth_date' => 'required|date',
|
||||
'address' => 'nullable|string',
|
||||
'phone_number' => 'nullable|string|max:20',
|
||||
'department_id' => 'required|exists:departments,id',
|
||||
'position_id' => 'required|exists:positions,id',
|
||||
'status' => 'required|in:PKWT,PKWTT,Magang',
|
||||
'join_date' => 'required|date',
|
||||
], [
|
||||
'name.required' => 'Nama lengkap wajib diisi.',
|
||||
'name.max' => 'Nama lengkap maksimal 255 karakter.',
|
||||
'email.required' => 'Email wajib diisi.',
|
||||
'email.email' => 'Format email tidak valid.',
|
||||
'email.unique' => 'Email sudah digunakan oleh karyawan lain.',
|
||||
'nip.required' => 'NIP wajib diisi.',
|
||||
'nip.unique' => 'NIP sudah terdaftar.',
|
||||
'gender.required' => 'Jenis kelamin wajib dipilih.',
|
||||
'gender.in' => 'Jenis kelamin hanya boleh Laki-laki atau Perempuan.',
|
||||
'place_of_birth.max' => 'Tempat lahir maksimal 255 karakter.',
|
||||
'birth_date.required' => 'Tanggal lahir wajib diisi.',
|
||||
'birth_date.date' => 'Format tanggal lahir tidak valid.',
|
||||
'phone_number.max' => 'Nomor telepon maksimal 20 karakter.',
|
||||
'department_id.required' => 'Departemen wajib dipilih.',
|
||||
'department_id.exists' => 'Departemen tidak ditemukan.',
|
||||
'position_id.required' => 'Jabatan wajib dipilih.',
|
||||
'position_id.exists' => 'Jabatan tidak ditemukan.',
|
||||
'status.required' => 'Status kepegawaian wajib dipilih.',
|
||||
'status.in' => 'Status hanya boleh PKWT, PKWTT, atau Magang.',
|
||||
'join_date.required' => 'Tanggal masuk wajib diisi.',
|
||||
'join_date.date' => 'Format tanggal masuk tidak valid.',
|
||||
]);
|
||||
$validated = $request->validated();
|
||||
|
||||
// Sinkronisasi ke akun User jika karyawan sudah memiliki akun
|
||||
if ($employee->user) {
|
||||
$employee->user->update([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'email' => $validated['email'] ?? $employee->user->email,
|
||||
]);
|
||||
}
|
||||
|
||||
// Update data karyawan di tabel employees
|
||||
$employee->update([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'] ?? null,
|
||||
'nip' => $validated['nip'],
|
||||
'gender' => $validated['gender'],
|
||||
'place_of_birth' => $validated['place_of_birth'] ?? null,
|
||||
'birth_date' => $validated['birth_date'],
|
||||
'address' => $validated['address'] ?? null,
|
||||
'phone_number' => $validated['phone_number'] ?? null,
|
||||
'department_id' => $validated['department_id'],
|
||||
'position_id' => $validated['position_id'],
|
||||
'status' => $validated['status'],
|
||||
'join_date' => $validated['join_date'],
|
||||
]);
|
||||
|
||||
$employee->update($validated);
|
||||
|
||||
return redirect()->route('admin.employees.index')
|
||||
->with('success', 'Data karyawan diperbarui.');
|
||||
->with('success', 'Data karyawan berhasil diperbarui.');
|
||||
}
|
||||
|
||||
public function destroy(Employee $employee)
|
||||
{
|
||||
$employee->user->delete();
|
||||
return back()->with('success', 'Karyawan dihapus.');
|
||||
$employee->user?->delete();
|
||||
return back()->with('success', 'Karyawan berhasil dihapus.');
|
||||
}
|
||||
}
|
||||
|
|
@ -6,29 +6,77 @@
|
|||
|
||||
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',
|
||||
// ── Identitas ────────────────────────────────────────────────
|
||||
'name' => ['required', 'string', 'max:255', 'regex:/^[\pL\s]+$/u'],
|
||||
'email' => ['nullable', 'email:rfc,dns', 'max:255', 'unique:employees,email'],
|
||||
|
||||
// ── Kepegawaian ──────────────────────────────────────────────
|
||||
'nip' => ['required', 'digits:6', 'unique:employees,nip'],
|
||||
|
||||
// ── Data Pribadi ─────────────────────────────────────────────
|
||||
'gender' => ['required', 'in:L,P'],
|
||||
'place_of_birth'=> ['nullable', 'string', 'max:100'],
|
||||
'birth_date' => ['required', 'date', 'before:today'],
|
||||
'address' => ['nullable', 'string', 'max:500'],
|
||||
'phone_number' => ['nullable', 'digits_between:8,15'],
|
||||
|
||||
// ── Jabatan & Departemen ─────────────────────────────────────
|
||||
'department_id' => ['required', 'exists:departments,id'],
|
||||
'position_id' => ['required', 'exists:positions,id'],
|
||||
|
||||
// ── Status & Tanggal ─────────────────────────────────────────
|
||||
'status' => ['required', 'in:PKWT,PKWTT,Magang'],
|
||||
'join_date' => ['required', 'date'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
// Nama
|
||||
'name.required' => 'Nama lengkap wajib diisi.',
|
||||
'name.max' => 'Nama lengkap maksimal 255 karakter.',
|
||||
'name.regex' => 'Nama lengkap hanya boleh berisi huruf dan spasi.',
|
||||
|
||||
// Email
|
||||
'email.email' => 'Format email tidak valid.',
|
||||
'email.unique' => 'Email sudah digunakan oleh karyawan lain.',
|
||||
'email.max' => 'Email maksimal 255 karakter.',
|
||||
|
||||
// NIP
|
||||
'nip.required' => 'NIP wajib diisi.',
|
||||
'nip.digits' => 'NIP harus tepat 6 digit angka.',
|
||||
'nip.unique' => 'NIP sudah terdaftar pada karyawan lain.',
|
||||
|
||||
// Data Pribadi
|
||||
'gender.required' => 'Jenis kelamin wajib dipilih.',
|
||||
'gender.in' => 'Jenis kelamin tidak valid.',
|
||||
'place_of_birth.max' => 'Tempat lahir maksimal 100 karakter.',
|
||||
'birth_date.required' => 'Tanggal lahir wajib diisi.',
|
||||
'birth_date.date' => 'Format tanggal lahir tidak valid.',
|
||||
'birth_date.before' => 'Tanggal lahir harus sebelum hari ini.',
|
||||
'address.max' => 'Alamat maksimal 500 karakter.',
|
||||
'phone_number.digits_between' => 'Nomor HP harus berupa angka, minimal 8 dan maksimal 15 digit.',
|
||||
|
||||
// Jabatan & Departemen
|
||||
'department_id.required'=> 'Departemen wajib dipilih.',
|
||||
'department_id.exists' => 'Departemen tidak ditemukan.',
|
||||
'position_id.required' => 'Jabatan wajib dipilih.',
|
||||
'position_id.exists' => 'Jabatan tidak ditemukan.',
|
||||
|
||||
// Status & Tanggal
|
||||
'status.required' => 'Status kepegawaian wajib dipilih.',
|
||||
'status.in' => 'Status hanya boleh PKWT, PKWTT, atau Magang.',
|
||||
'join_date.required' => 'Tanggal masuk wajib diisi.',
|
||||
'join_date.date' => 'Format tanggal masuk tidak valid.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,36 +3,95 @@
|
|||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
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;
|
||||
$employee = $this->route('employee');
|
||||
$employeeId = $employee->id;
|
||||
$userId = $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',
|
||||
// ── Identitas ────────────────────────────────────────────────
|
||||
'name' => ['required', 'string', 'max:255', 'regex:/^[\pL\s]+$/u'],
|
||||
'email' => [
|
||||
'nullable',
|
||||
'email:rfc,dns',
|
||||
'max:255',
|
||||
// Abaikan email milik employee ini sendiri
|
||||
Rule::unique('employees', 'email')->ignore($employeeId),
|
||||
],
|
||||
|
||||
// ── Kepegawaian ──────────────────────────────────────────────
|
||||
'nip' => [
|
||||
'required',
|
||||
'digits:6',
|
||||
Rule::unique('employees', 'nip')->ignore($employeeId),
|
||||
],
|
||||
|
||||
// ── Data Pribadi ─────────────────────────────────────────────
|
||||
'gender' => ['required', 'in:L,P'],
|
||||
'place_of_birth'=> ['nullable', 'string', 'max:100'],
|
||||
'birth_date' => ['required', 'date', 'before:today'],
|
||||
'address' => ['nullable', 'string', 'max:500'],
|
||||
'phone_number' => ['nullable', 'digits_between:8,15'],
|
||||
|
||||
// ── Jabatan & Departemen ─────────────────────────────────────
|
||||
'department_id' => ['required', 'exists:departments,id'],
|
||||
'position_id' => ['required', 'exists:positions,id'],
|
||||
|
||||
// ── Status & Tanggal ─────────────────────────────────────────
|
||||
'status' => ['required', 'in:PKWT,PKWTT,Magang'],
|
||||
'join_date' => ['required', 'date'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
// Nama
|
||||
'name.required' => 'Nama lengkap wajib diisi.',
|
||||
'name.max' => 'Nama lengkap maksimal 255 karakter.',
|
||||
'name.regex' => 'Nama lengkap hanya boleh berisi huruf dan spasi.',
|
||||
|
||||
// Email
|
||||
'email.email' => 'Format email tidak valid.',
|
||||
'email.unique' => 'Email sudah digunakan oleh karyawan lain.',
|
||||
'email.max' => 'Email maksimal 255 karakter.',
|
||||
|
||||
// NIP
|
||||
'nip.required' => 'NIP wajib diisi.',
|
||||
'nip.digits' => 'NIP harus tepat 6 digit angka.',
|
||||
'nip.unique' => 'NIP sudah terdaftar pada karyawan lain.',
|
||||
|
||||
// Data Pribadi
|
||||
'gender.required' => 'Jenis kelamin wajib dipilih.',
|
||||
'gender.in' => 'Jenis kelamin tidak valid.',
|
||||
'place_of_birth.max' => 'Tempat lahir maksimal 100 karakter.',
|
||||
'birth_date.required' => 'Tanggal lahir wajib diisi.',
|
||||
'birth_date.date' => 'Format tanggal lahir tidak valid.',
|
||||
'birth_date.before' => 'Tanggal lahir harus sebelum hari ini.',
|
||||
'address.max' => 'Alamat maksimal 500 karakter.',
|
||||
'phone_number.digits_between' => 'Nomor HP harus berupa angka, minimal 8 dan maksimal 15 digit.',
|
||||
|
||||
// Jabatan & Departemen
|
||||
'department_id.required'=> 'Departemen wajib dipilih.',
|
||||
'department_id.exists' => 'Departemen tidak ditemukan.',
|
||||
'position_id.required' => 'Jabatan wajib dipilih.',
|
||||
'position_id.exists' => 'Jabatan tidak ditemukan.',
|
||||
|
||||
// Status & Tanggal
|
||||
'status.required' => 'Status kepegawaian wajib dipilih.',
|
||||
'status.in' => 'Status hanya boleh PKWT, PKWTT, atau Magang.',
|
||||
'join_date.required' => 'Tanggal masuk wajib diisi.',
|
||||
'join_date.date' => 'Format tanggal masuk tidak valid.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ class Employee extends Model
|
|||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'user_id', // diisi oleh UserController saat akun digenerate
|
||||
'nip',
|
||||
'name',
|
||||
'email',
|
||||
'gender',
|
||||
'place_of_birth',
|
||||
'birth_date',
|
||||
|
|
@ -21,13 +22,13 @@ class Employee extends Model
|
|||
'department_id',
|
||||
'position_id',
|
||||
'status',
|
||||
'join_date'
|
||||
'join_date',
|
||||
];
|
||||
|
||||
// Relasi ke User
|
||||
// Relasi ke User (nullable: karyawan boleh belum punya akun)
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
return $this->belongsTo(User::class)->withDefault();
|
||||
}
|
||||
|
||||
// Relasi ke Departemen
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Ubah kolom user_id menjadi nullable agar data karyawan
|
||||
* dapat disimpan tanpa harus memiliki akun User terlebih dahulu.
|
||||
* Pembuatan akun User dilakukan secara manual oleh admin
|
||||
* melalui menu Manajemen Pengguna.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('employees', function (Blueprint $table) {
|
||||
// Drop foreign key constraint lama (NOT NULL + cascade delete)
|
||||
$table->dropForeign(['user_id']);
|
||||
|
||||
// Ubah kolom menjadi nullable dan pasang kembali foreign key
|
||||
// dengan nullOnDelete agar baris employee tidak ikut terhapus
|
||||
// jika akun User-nya dihapus.
|
||||
$table->foreignId('user_id')
|
||||
->nullable()
|
||||
->change();
|
||||
|
||||
$table->foreign('user_id')
|
||||
->references('id')
|
||||
->on('users')
|
||||
->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback: kembalikan user_id ke NOT NULL dengan cascade delete.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('employees', function (Blueprint $table) {
|
||||
$table->dropForeign(['user_id']);
|
||||
|
||||
$table->foreignId('user_id')
|
||||
->nullable(false)
|
||||
->change();
|
||||
|
||||
$table->foreign('user_id')
|
||||
->references('id')
|
||||
->on('users')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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
|
||||
{
|
||||
/**
|
||||
* Tambahkan kolom email ke tabel employees.
|
||||
* Sebelumnya email hanya disimpan di tabel users, namun sejak
|
||||
* pembuatan akun User dipisahkan dari proses tambah karyawan,
|
||||
* email perlu ikut disimpan di tabel employees sebagai data kontak.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('employees', function (Blueprint $table) {
|
||||
// Ditempatkan setelah kolom 'name', nullable karena
|
||||
// data karyawan lama mungkin belum memiliki email.
|
||||
$table->string('email')->nullable()->after('name');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('employees', function (Blueprint $table) {
|
||||
$table->dropColumn('email');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -2,12 +2,6 @@ import type { SVGAttributes } from 'react';
|
|||
|
||||
export default function AppLogoIcon(props: SVGAttributes<SVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 40 42" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17.2 5.63325L8.6 0.855469L0 5.63325V32.1434L16.2 41.1434L32.4 32.1434V23.699L40 19.4767V9.85547L31.4 5.07769L22.8 9.85547V18.2999L17.2 21.411V5.63325ZM38 18.2999L32.4 21.411V15.2545L38 12.1434V18.2999ZM36.9409 10.4439L31.4 13.5221L25.8591 10.4439L31.4 7.36561L36.9409 10.4439ZM24.8 18.2999V12.1434L30.4 15.2545V21.411L24.8 18.2999ZM23.8 20.0323L29.3409 23.1105L16.2 30.411L10.6591 27.3328L23.8 20.0323ZM7.6 27.9212L15.2 32.1434V38.2999L2 30.9666V7.92116L7.6 11.0323V27.9212ZM8.6 9.29991L3.05913 6.22165L8.6 3.14339L14.1409 6.22165L8.6 9.29991ZM30.4 24.8101L17.2 32.1434V38.2999L30.4 30.9666V24.8101ZM9.6 11.0323L15.2 7.92117V22.5221L9.6 25.6333V11.0323Z"
|
||||
/>
|
||||
</svg>
|
||||
<img src="/assets/logo-clear.png" alt="Logo" className="w-8 h-8"/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ import AppLogoIcon from './app-logo-icon';
|
|||
export default function AppLogo() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground">
|
||||
<AppLogoIcon className="size-5 fill-current text-white dark:text-black" />
|
||||
<div className="flex aspect-square size-8 items-center justify-center">
|
||||
<AppLogoIcon className="size-3" />
|
||||
</div>
|
||||
<div className="ml-1 grid flex-1 text-left text-sm">
|
||||
<span className="mb-0.5 truncate leading-tight font-semibold">
|
||||
SDM App
|
||||
HRIS App
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -52,11 +52,6 @@ export function AppSidebar() {
|
|||
href: '/admin/payrolls',
|
||||
icon: Wallet,
|
||||
},
|
||||
{
|
||||
title: 'Manajemen Absensi',
|
||||
href: '/admin/attendances',
|
||||
icon: CalendarClock,
|
||||
},
|
||||
{
|
||||
title: 'Manajemen Pengguna',
|
||||
href: '/admin/users',
|
||||
|
|
|
|||
|
|
@ -37,9 +37,6 @@ export default function EmployeeLayout({ children, title }: EmployeeLayoutProps)
|
|||
<div className="text-sm font-medium text-slate-700 hidden sm:block">
|
||||
Halo, {user.name}
|
||||
</div>
|
||||
<Link href="/profile" className="p-2 text-slate-500 hover:text-sky-600 transition-colors rounded-full hover:bg-slate-100">
|
||||
<User className="w-5 h-5" />
|
||||
</Link>
|
||||
<Link href="/logout" method="post" as="button" className="p-2 text-slate-500 hover:text-red-600 transition-colors rounded-full hover:bg-slate-100">
|
||||
<LogOut className="w-5 h-5" />
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export default function Create({ departments, positions }: PageProps) {
|
|||
<Label>Email</Label>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="contoh@email.com"
|
||||
value={data.email}
|
||||
onChange={e => setData('email', e.target.value)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import AppLayout from '@/layouts/app-layout';
|
|||
|
||||
interface Employee {
|
||||
id: number;
|
||||
name: string;
|
||||
nip: string;
|
||||
gender: string;
|
||||
birth_date: string;
|
||||
|
|
@ -24,7 +25,7 @@ interface Employee {
|
|||
user: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface PageProps {
|
||||
|
|
@ -37,8 +38,8 @@ interface PageProps {
|
|||
export default function Edit({ employee, departments, positions }: PageProps) {
|
||||
// Inisialisasi State
|
||||
const { data, setData, put, processing, errors } = useForm({
|
||||
name: employee.user.name,
|
||||
email: employee.user.email,
|
||||
name: employee.name,
|
||||
email: employee.user?.email ?? '',
|
||||
nip: employee.nip,
|
||||
gender: employee.gender,
|
||||
birth_date: employee.birth_date,
|
||||
|
|
@ -58,7 +59,7 @@ export default function Edit({ employee, departments, positions }: PageProps) {
|
|||
|
||||
return (
|
||||
<AppLayout>
|
||||
<Head title={`Edit Karyawan: ${employee.user.name}`} />
|
||||
<Head title={`Edit Karyawan: ${employee.name}`} />
|
||||
|
||||
<div className="p-4 md:p-8 max-w-5xl mx-auto">
|
||||
<Button variant="outline" asChild className="mb-6">
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import AppLayout from '@/layouts/app-layout';
|
|||
|
||||
interface Employee {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string | null;
|
||||
nip: string;
|
||||
status: string;
|
||||
join_date: string;
|
||||
|
|
@ -68,8 +70,8 @@ export default function Index({ employees, departments, filters }: PageProps) {
|
|||
const exportExcel = () => {
|
||||
const rows = employees.map((p, i) => ({
|
||||
No: i + 1,
|
||||
Nama: p.user?.name || '-',
|
||||
Email: p.user?.email || '-',
|
||||
Nama: p.name || '-',
|
||||
Email: p.email || '-',
|
||||
NIP: p.nip,
|
||||
Departemen: p.department?.name || '-',
|
||||
Jabatan: p.position?.name || '-',
|
||||
|
|
@ -150,8 +152,8 @@ export default function Index({ employees, departments, filters }: PageProps) {
|
|||
employees.map((employee) => (
|
||||
<tr key={employee.id} className="border-b hover:bg-zinc-50">
|
||||
<td className="p-4">
|
||||
<div className="font-bold">{employee.user?.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{employee.user?.email}</div>
|
||||
<div className="font-bold">{employee.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{employee.email || '-'}</div>
|
||||
<div className="text-xs text-zinc-400 mt-1">NIP: {employee.nip}</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import { useState } from 'react';
|
|||
import * as XLSX from 'xlsx';
|
||||
|
||||
|
||||
|
||||
interface Payroll {
|
||||
id: number;
|
||||
employee_name: string;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export default function Create({ employees }: PageProps) {
|
|||
<AppLayout>
|
||||
<Head title="Buat Akun User" />
|
||||
|
||||
<div className="p-4 md:p-8 w-full">
|
||||
<div className="p-4 md:p-8 w-full mx-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">Buat Akun User</h2>
|
||||
|
|
@ -60,7 +60,7 @@ export default function Create({ employees }: PageProps) {
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
<Card className="max-w-xl">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Form Akun Baru</CardTitle>
|
||||
</CardHeader>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
|
|||
import EmployeeLayout from '@/layouts/employee-layout';
|
||||
import { AttendanceMapModal } from '@/components/AttendanceMapModal';
|
||||
import { MapPin, AlertCircle, RefreshCw, CheckCircle2, LogOut } from 'lucide-react';
|
||||
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
|
||||
interface Attendance {
|
||||
id: number;
|
||||
|
|
@ -146,11 +148,12 @@ export default function AttendanceIndex({ attendances, todayAttendance }: PagePr
|
|||
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-20 md:-mt-24 relative z-10 pb-12 space-y-6 md:space-y-8">
|
||||
|
||||
<div className={`p-4 md:p-5 rounded-xl md:rounded-2xl text-sm flex items-start md:items-center gap-3 shadow-sm ${
|
||||
<div className={`p-4 md:p-5 rounded-xl md:rounded-2xl text-sm shadow-sm ${
|
||||
isLoadingLocation ? 'bg-sky-100 text-sky-800' :
|
||||
locationError ? 'bg-red-100 text-red-800' :
|
||||
'bg-green-100 text-green-800'
|
||||
}`}>
|
||||
<div className="flex items-start md:items-center gap-3">
|
||||
{isLoadingLocation ? (
|
||||
<RefreshCw className="w-5 h-5 md:w-6 md:h-6 animate-spin shrink-0 mt-0.5 md:mt-0" />
|
||||
) : locationError ? (
|
||||
|
|
@ -179,6 +182,35 @@ export default function AttendanceIndex({ attendances, todayAttendance }: PagePr
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Peta Preview - Hanya muncul jika koordinat berhasil didapatkan */}
|
||||
{coordinates && !isLoadingLocation && !locationError && (
|
||||
<div className="mt-4 h-48 md:h-64 w-full rounded-md overflow-hidden border-2 border-green-200 shadow-sm">
|
||||
<MapContainer
|
||||
center={[coordinates.lat, coordinates.lng]}
|
||||
zoom={16}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
scrollWheelZoom={false}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<Marker position={[coordinates.lat, coordinates.lng]}>
|
||||
<Popup>
|
||||
<div className="text-center">
|
||||
<p className="font-semibold text-sm">📍 Lokasi Anda Saat Ini</p>
|
||||
<p className="text-xs text-slate-600 mt-1">
|
||||
Lat: {coordinates.lat.toFixed(6)}<br />
|
||||
Lng: {coordinates.lng.toFixed(6)}
|
||||
</p>
|
||||
</div>
|
||||
</Popup>
|
||||
</Marker>
|
||||
</MapContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8">
|
||||
<div className="lg:col-span-1">
|
||||
<Card className="border-slate-100 shadow-md h-full">
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import laravel from 'laravel-vite-plugin';
|
|||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
},
|
||||
plugins: [
|
||||
laravel({
|
||||
input: ['resources/css/app.css', 'resources/js/app.tsx'],
|
||||
|
|
|
|||
Loading…
Reference in New Issue