diff --git a/app/Livewire/Datatable/UsersTable.php b/app/Livewire/Datatable/UsersTable.php
new file mode 100644
index 0000000..aa672de
--- /dev/null
+++ b/app/Livewire/Datatable/UsersTable.php
@@ -0,0 +1,141 @@
+label(function ($row) {
+ return <<
+
{$row->employee?->full_name}
+ {$row->employee?->code}
+
+ HTML;
+ })
+ ->searchable(function ($query, $searchTerm) {
+ $query->whereHas('employee', function ($query) use ($searchTerm) {
+ $query->where('full_name', 'like', "%{$searchTerm}%")
+ ->orWhere('code', 'like', "%{$searchTerm}%");
+ });
+ })
+ ->html(),
+ Column::make('Nomor Telepon', 'employee.phone_number')->searchable(),
+
+ Column::make('Email', 'email')->searchable(),
+
+ Column::make('Nama Pengguna', 'username')->searchable(),
+
+ Column::make('Jenis Kelamin', 'employee.gender')
+ ->format(fn ($value) => Blade::render(
+ ''
+ .Gender::from($value)->label()
+ .''
+ ))
+ ->html(),
+
+ Column::make('Gaji Pokok', 'employee.base_salary')
+ ->searchable()
+ ->sortable()
+ ->format(fn ($value) => formatCurrency($value)),
+
+ Column::make('Alamat', 'employee.address')->searchable(),
+
+ Column::make('Tanggal')
+ ->label(function ($row) {
+ $birthdate = formatDate($row->employee->birthdate);
+ $birthAgo = getAge($row->employee->birthdate);
+
+ $hireDate = formatDate($row->hire_date);
+ $openedAgo = timeAgo($row->hire_date);
+
+ $resigndate = formatDate($row->resign_date);
+ $resignAgo = timeAgo($row->resign_date);
+
+ return <<
+
+ Tgl Lahir:
+ {$birthdate}
+ ({$birthAgo})
+
+
+ Tgl Bergabung:
+ {$hireDate}
+ ({$openedAgo})
+
+
+ Tgl Resign:
+ {$resigndate}
+ ({$resignAgo})
+
+
+ HTML;
+ })
+ ->html(),
+
+ Column::make('status')
+ ->format(fn ($value) => Blade::render(''.$value->label().''))
+ ->html(),
+
+ Column::make('Aksi')
+ ->label(function ($row) {
+ $actions = '';
+
+ if (auth()->id() !== $row->id) {
+ $actions .= view('components.datatables.edit', [
+ 'id' => $row->id,
+ 'editRoute' => route('studio.master.user.edit', $row->id),
+ ])->render();
+
+ $actions .= view('components.datatables.delete', [
+ 'id' => $row->id,
+ 'deleteRoute' => route('studio.master.user.delete', $row->id),
+ ])->render();
+ }
+
+ return $actions;
+ })
+ ->html(),
+ ];
+ }
+
+ public function builder(): Builder
+ {
+ return User::select('users.id', 'email', 'username', 'users.status', 'employee.code', 'employee.full_name', 'employee.phone_number', 'employee.base_salary', 'employee.address', 'employee.hire_date', 'employee.resign_date', 'employee.birthdate', 'employee.gender')->with('employee');
+ }
+
+ public function filters(): array
+ {
+ return [
+ MultiSelectFilter::make('Status')
+ ->options(
+ collect(Userstatus::cases())
+ ->mapWithKeys(fn ($case) => [$case->value => $case->label()])
+ ->toArray()
+ )
+ ->filter(function (Builder $builder, array $values) {
+ $builder->whereIn('users.status', $values);
+ }),
+ ];
+ }
+}
diff --git a/app/Livewire/Forms/UserForm.php b/app/Livewire/Forms/UserForm.php
new file mode 100644
index 0000000..0cc31b4
--- /dev/null
+++ b/app/Livewire/Forms/UserForm.php
@@ -0,0 +1,150 @@
+ ['required', 'string', 'max:100'],
+ 'username' => ['required', 'alpha_dash', 'min:5', 'max:20', Rule::unique('users', 'username')->ignore($this->user)],
+ 'email' => ['required', 'email', 'max:100', Rule::unique('users', 'email')->ignore($this->user)],
+ 'phone_number' => ['required', 'string', new PhoneNumber],
+ 'base_salary' => ['required', 'regex:/^[0-9.]+$/', 'max:999999999'],
+ 'birthdate' => ['required', 'date', 'before:'.Carbon::now()->subYears(18)->format('Y-m-d')],
+ 'hire_date' => ['required', 'date'],
+ 'address' => ['required', 'string'],
+ 'gender' => ['required', Rule::in(Gender::cases())],
+ 'status' => ['required', Rule::in(UserStatus::cases())],
+ ];
+ }
+
+ public function validationAttributes(): array
+ {
+ return [
+ 'full_name' => 'nama lengkap',
+ 'username' => 'nama pengguna',
+ 'phone_number' => 'nomor telepon',
+ 'base_salary' => 'gaji pokok',
+ 'birthdate' => 'tanggal lahir',
+ 'hire_date' => 'tanggal bergabung',
+ 'address' => 'alamat',
+ 'gender' => 'jenis kelamin',
+ ];
+ }
+
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ $employee = $user->employee;
+
+ $this->full_name = $employee->full_name;
+ $this->username = $user->username;
+ $this->email = $user->email;
+ $this->phone_number = $employee->phone_number;
+ $this->base_salary = $employee->base_salary;
+ $this->birthdate = $employee->birthdate->toDateString();
+ $this->hire_date = $employee->hire_date->toDateString();
+ $this->address = $employee->address;
+ $this->gender = $employee->gender->value;
+ $this->status = $user->status->value;
+ }
+
+ public function store()
+ {
+ $this->validate();
+
+ $data = $this->all();
+
+ if ($data['status'] != UserStatus::ACTIVE) {
+ $data['resign_date'] = now();
+ } else {
+ $data['resign_date'] = null;
+ }
+
+ DB::transaction(function () use ($data) {
+ $user = User::create([
+ ...$data,
+ 'password' => Hash::make(config('myconfig.password_default')),
+ 'status' => $data['status'],
+ ]);
+
+ Employee::create([
+ 'user_id' => $user->id,
+ 'full_name' => $data['full_name'],
+ 'code' => Employee::generateEmployeeCode(),
+ 'phone_number' => $data['phone_number'],
+ 'base_salary' => replaceCurrency($data['base_salary']),
+ 'birthdate' => $data['birthdate'],
+ 'hire_date' => $data['hire_date'],
+ 'address' => $data['address'],
+ 'gender' => $data['gender'],
+ ]);
+ });
+ }
+
+ public function update()
+ {
+ $this->validate();
+
+ $data = $this->all();
+
+ if ($data['status'] == UserStatus::SUSPENDED->value) {
+ $data['closed_date'] = now()->toDateString();
+ } else {
+ $data['closed_date'] = null;
+ }
+
+ DB::transaction(function () use ($data) {
+ $this->user->update([
+ ...$data,
+ 'status' => $data['status'],
+ ]);
+
+ $this->user->employee->update([
+ 'user_id' => $this->user->id,
+ 'full_name' => $data['full_name'],
+ 'phone_number' => $data['phone_number'],
+ 'base_salary' => replaceCurrency($data['base_salary']),
+ 'birthdate' => $data['birthdate'],
+ 'hire_date' => $data['hire_date'],
+ 'address' => $data['address'],
+ 'gender' => $data['gender'],
+ ]);
+ });
+ }
+}
diff --git a/app/Livewire/Studio/Master/User/Create.php b/app/Livewire/Studio/Master/User/Create.php
new file mode 100644
index 0000000..1dfa610
--- /dev/null
+++ b/app/Livewire/Studio/Master/User/Create.php
@@ -0,0 +1,38 @@
+form->store();
+
+ Flux::toast(
+ heading: 'Berhasil',
+ text: 'Pegawai berhasil ditambahkan.',
+ variant: 'success',
+ duration: 3000
+ );
+
+ $this->redirectRoute('studio.master.user.index', navigate: true);
+ }
+
+ public function render()
+ {
+ return view('livewire.studio.master.user.form', [
+ 'pageTitle' => 'Tambah Pegawai',
+ ]);
+ }
+}
diff --git a/app/Livewire/Studio/Master/User/Edit.php b/app/Livewire/Studio/Master/User/Edit.php
new file mode 100644
index 0000000..869066f
--- /dev/null
+++ b/app/Livewire/Studio/Master/User/Edit.php
@@ -0,0 +1,46 @@
+form->setUser($user);
+ }
+
+ public function save()
+ {
+ $this->form->update();
+
+ $this->dispatch('refreshDatatable');
+
+ Flux::toast(
+ heading: 'Berhasil',
+ text: 'Pegawai berhasil diperbarui.',
+ variant: 'success',
+ duration: 3000
+ );
+
+ $this->redirectRoute('studio.master.user.index', navigate: true);
+ }
+
+ public function render()
+ {
+ return view('livewire.studio.master.user.form', [
+ 'pageTitle' => 'Ubah Pegawai',
+ ]);
+ }
+}
diff --git a/app/Livewire/Studio/Master/User/Index.php b/app/Livewire/Studio/Master/User/Index.php
new file mode 100644
index 0000000..c26bb93
--- /dev/null
+++ b/app/Livewire/Studio/Master/User/Index.php
@@ -0,0 +1,37 @@
+delete();
+
+ $this->dispatch('refreshDatatable');
+
+ Flux::toast(
+ heading: 'Berhasil',
+ text: 'Pegawai berhasil dihapus.',
+ variant: 'success',
+ );
+
+ Flux::modals()->close();
+ }
+
+ public function render()
+ {
+ return view('livewire.studio.master.user.index', [
+ 'pageTitle' => 'Pegawai',
+ ]);
+ }
+}
diff --git a/app/Models/Employee.php b/app/Models/Employee.php
index 06398b2..871634d 100644
--- a/app/Models/Employee.php
+++ b/app/Models/Employee.php
@@ -26,6 +26,27 @@ protected function casts(): array
];
}
+ public static function generateEmployeeCode(): string
+ {
+ $prefix = 'EMP';
+ $year = date('Y');
+
+ $lastEmployee = self::where('code', 'like', "$prefix$year%")
+ ->orderBy('code', 'desc')
+ ->first();
+
+ if ($lastEmployee) {
+ $lastNumber = (int) substr($lastEmployee->code, strlen($prefix) + 4);
+ $newNumber = $lastNumber + 1;
+ } else {
+ $newNumber = 1;
+ }
+
+ $numberFormatted = str_pad($newNumber, 4, '0', STR_PAD_LEFT);
+
+ return $prefix.$year.$numberFormatted;
+ }
+
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php
index 85c19e5..4ef026a 100644
--- a/database/migrations/0001_01_01_000000_create_users_table.php
+++ b/database/migrations/0001_01_01_000000_create_users_table.php
@@ -16,11 +16,10 @@ public function up(): void
$table->id();
$table->string('email', 100)->unique();
$table->string('username', 20)->unique();
- $table->string('phone_number', 20);
$table->string('password', 97);
+ $table->enum('status', [UserStatus::values()])->default(UserStatus::ACTIVE)->comment(UserStatus::comment());
$table->timestamp('last_login_at')->nullable();
$table->timestamp('last_password_change_at')->nullable();
- $table->enum('status', [UserStatus::values()])->default(UserStatus::ACTIVE);
$table->timestamps();
$table->softDeletes();
});
diff --git a/database/migrations/2025_09_13_034842_create_employees_table.php b/database/migrations/2025_09_13_034842_create_employees_table.php
index 56fefd0..fc3c2bf 100644
--- a/database/migrations/2025_09_13_034842_create_employees_table.php
+++ b/database/migrations/2025_09_13_034842_create_employees_table.php
@@ -16,15 +16,16 @@ public function up(): void
Schema::create('employees', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
- $table->string('code', 50)->unique();
$table->string('full_name', 100);
- $table->enum('gender', [Gender::values()]);
+ $table->string('code', 20)->unique();
+ $table->string('phone_number', 20);
+ $table->unsignedInteger('base_salary')->default(0);
+ $table->enum('gender', [Gender::values()])->comment(Gender::comment());
+ $table->text('address');
$table->date('birthdate');
$table->date('hire_date');
$table->date('resign_date')->nullable();
- $table->enum('status', [EmploymentStatus::values()])->default(EmploymentStatus::PERMANENT);
- $table->unsignedInteger('base_salary')->default(0);
- $table->text('address');
+ $table->enum('status', [EmploymentStatus::values()])->default(EmploymentStatus::PERMANENT)->comment(EmploymentStatus::comment());
$table->timestamps();
$table->softDeletes();
});
diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php
index ae6efb9..103b2a3 100644
--- a/database/seeders/UserSeeder.php
+++ b/database/seeders/UserSeeder.php
@@ -19,7 +19,6 @@ public function run(): void
$user = User::create([
'email' => 'project.pangestuyoga@gmail.com',
'username' => 'pangestu',
- 'phone_number' => '082121495806',
'password' => Hash::make(config('myconfig.password_default')),
]);
@@ -27,12 +26,12 @@ public function run(): void
'user_id' => $user->id,
'code' => '0001',
'full_name' => 'Yoga Pangestu',
+ 'phone_number' => '082121495806',
'gender' => Gender::MALE,
+ 'address' => 'Kp. Bakan Sampeu',
'birthdate' => '2005-03-13',
'hire_date' => now(),
'status' => EmploymentStatus::PERMANENT,
- 'base_salary' => 0,
- 'address' => 'Kp. Bakan Sampeu',
]);
}
}
diff --git a/resources/views/components/partials/studio/_sidebar.blade.php b/resources/views/components/partials/studio/_sidebar.blade.php
index b9a216e..e4c6394 100644
--- a/resources/views/components/partials/studio/_sidebar.blade.php
+++ b/resources/views/components/partials/studio/_sidebar.blade.php
@@ -17,6 +17,10 @@ class="px-2 hidden dark:flex" />
Outlet
+
+ Pegawai
+
diff --git a/resources/views/livewire/studio/master/user/form.blade.php b/resources/views/livewire/studio/master/user/form.blade.php
new file mode 100644
index 0000000..fdbeeda
--- /dev/null
+++ b/resources/views/livewire/studio/master/user/form.blade.php
@@ -0,0 +1,109 @@
+
+
+
+ {{ $pageTitle }}
+
+
+
+ Kembali
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Gaji Pokok
+
+
+ Rp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @foreach (\App\Enums\Gender::cases() as $gender)
+
+ {{ $gender->label() }}
+
+ @endforeach
+
+
+
+
+
+
+
+ @foreach (\App\Enums\UserStatus::cases() as $status)
+
+ {{ $status->label() }}
+
+ @endforeach
+
+
+
+
+
+
+
+
+ Simpan
+
+
+
+
+
+
diff --git a/resources/views/livewire/studio/master/user/index.blade.php b/resources/views/livewire/studio/master/user/index.blade.php
new file mode 100644
index 0000000..af4287e
--- /dev/null
+++ b/resources/views/livewire/studio/master/user/index.blade.php
@@ -0,0 +1,19 @@
+
+
+
+ {{ $pageTitle }}
+
+
+
+ Tambah
+
+
+
+
+
+
+
+
+ @include('components.confirmation.delete')
+
diff --git a/routes/web.php b/routes/web.php
index 69b6dff..8847a45 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -6,6 +6,9 @@
use App\Livewire\Studio\Master\Outlet\Create as OutletCreate;
use App\Livewire\Studio\Master\Outlet\Edit as OutletEdit;
use App\Livewire\Studio\Master\Outlet\Index as OutletIndex;
+use App\Livewire\Studio\Master\User\Create as UserCreate;
+use App\Livewire\Studio\Master\User\Edit as UserEdit;
+use App\Livewire\Studio\Master\User\Index as UserIndex;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
@@ -36,4 +39,13 @@
Route::get('outlets/{outlet}/edit', OutletEdit::class)->name('edit');
Route::get('outlets/{outlet}/delete', OutletCreate::class)->name('delete');
});
+
+ Route::prefix('master')
+ ->as('studio.master.user.')
+ ->group(function () {
+ Route::get('users', UserIndex::class)->name('index');
+ Route::get('users/create', UserCreate::class)->name('create');
+ Route::get('users/{user}/edit', UserEdit::class)->name('edit');
+ Route::get('users/{user}/delete', UserCreate::class)->name('delete');
+ });
});