From c2e2b85070f6a812c2936866cf6a0daab53154d5 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 24 Sep 2025 19:38:59 +0700 Subject: [PATCH] feat(user): fitur user dan employee -crud -membuat fungsi untuk generate kode pegawai di model Employee -menyesuaikan migrasi users dan employees -menyesuaikan value seeder dengan migrasi baru --- app/Livewire/Datatable/UsersTable.php | 141 ++++++++++++++++ app/Livewire/Forms/UserForm.php | 150 ++++++++++++++++++ app/Livewire/Studio/Master/User/Create.php | 38 +++++ app/Livewire/Studio/Master/User/Edit.php | 46 ++++++ app/Livewire/Studio/Master/User/Index.php | 37 +++++ app/Models/Employee.php | 21 +++ .../0001_01_01_000000_create_users_table.php | 3 +- ...25_09_13_034842_create_employees_table.php | 11 +- database/seeders/UserSeeder.php | 5 +- .../partials/studio/_sidebar.blade.php | 4 + .../studio/master/user/form.blade.php | 109 +++++++++++++ .../studio/master/user/index.blade.php | 19 +++ routes/web.php | 12 ++ 13 files changed, 586 insertions(+), 10 deletions(-) create mode 100644 app/Livewire/Datatable/UsersTable.php create mode 100644 app/Livewire/Forms/UserForm.php create mode 100644 app/Livewire/Studio/Master/User/Create.php create mode 100644 app/Livewire/Studio/Master/User/Edit.php create mode 100644 app/Livewire/Studio/Master/User/Index.php create mode 100644 resources/views/livewire/studio/master/user/form.blade.php create mode 100644 resources/views/livewire/studio/master/user/index.blade.php 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'); + }); });