From 0cb93c4e05386af7c5dba1ed81407599f4f6b556 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sun, 21 Jun 2026 01:35:30 +0700 Subject: [PATCH] refactor: update AttendanceController and related components to use new CheckInRequest and CheckOutRequest classes, streamline attendance handling by removing location tags, and enhance UI components for better user experience --- .../Admin/Hr/AttendanceController.php | 20 ++--- .../AttendanceCheckInRequest.php | 6 +- .../AttendanceCheckOutRequest.php | 6 +- app/Services/Hr/AttendanceService.php | 59 +------------- database/factories/AttendanceFactory.php | 3 - ..._06_11_100000_create_attendances_table.php | 2 - .../admin/hr/attendances/AttendanceMap.vue | 78 ------------------- resources/js/composables/useGeolocation.ts | 10 --- resources/js/composables/useWebcamCapture.ts | 24 +----- resources/js/lib/field-limits.ts | 1 - .../js/pages/admin/hr/attendances/Index.vue | 13 +--- .../form}/AttendanceWebcamModal.vue | 19 +---- .../attendances/table}/AttendanceCalendar.vue | 60 ++++++++++++-- .../table}/AttendanceDetailDialog.vue | 12 +-- .../hr/attendances/table/AttendanceMap.vue | 54 +++++++++++++ .../table}/attendance-photo-cell.vue | 10 +-- .../admin/hr/attendances/table}/columns.ts | 6 +- .../attendances/table}/data-table-actions.vue | 0 resources/js/types/attendance.ts | 3 - 19 files changed, 136 insertions(+), 250 deletions(-) rename app/Http/Requests/Admin/Hr/{ => Attendance}/AttendanceCheckInRequest.php (80%) rename app/Http/Requests/Admin/Hr/{ => Attendance}/AttendanceCheckOutRequest.php (79%) delete mode 100644 resources/js/components/admin/hr/attendances/AttendanceMap.vue rename resources/js/{components/admin/hr/attendances => pages/admin/hr/attendances/form}/AttendanceWebcamModal.vue (91%) rename resources/js/{components/admin/hr/attendances => pages/admin/hr/attendances/table}/AttendanceCalendar.vue (90%) rename resources/js/{components/admin/hr/attendances => pages/admin/hr/attendances/table}/AttendanceDetailDialog.vue (85%) create mode 100644 resources/js/pages/admin/hr/attendances/table/AttendanceMap.vue rename resources/js/{components/admin/hr/attendances => pages/admin/hr/attendances/table}/attendance-photo-cell.vue (85%) rename resources/js/{components/admin/hr/attendances => pages/admin/hr/attendances/table}/columns.ts (87%) rename resources/js/{components/admin/hr/attendances => pages/admin/hr/attendances/table}/data-table-actions.vue (100%) diff --git a/app/Http/Controllers/Admin/Hr/AttendanceController.php b/app/Http/Controllers/Admin/Hr/AttendanceController.php index 2a62a43..3f31daa 100644 --- a/app/Http/Controllers/Admin/Hr/AttendanceController.php +++ b/app/Http/Controllers/Admin/Hr/AttendanceController.php @@ -3,10 +3,11 @@ namespace App\Http\Controllers\Admin\Hr; use App\Enums\Permission; +use App\Enums\Role; use App\Http\Controllers\Concerns\FlashesEntityMessage; use App\Http\Controllers\Controller; -use App\Http\Requests\Admin\Hr\AttendanceCheckInRequest; -use App\Http\Requests\Admin\Hr\AttendanceCheckOutRequest; +use App\Http\Requests\Admin\Hr\Attendance\CheckInRequest; +use App\Http\Requests\Admin\Hr\Attendance\CheckOutRequest; use App\Models\Attendance; use App\Services\Hr\AttendanceService; use Illuminate\Http\RedirectResponse; @@ -26,7 +27,7 @@ public function index(Request $request): Response { $user = $request->user(); $employee = $user?->employee; - $isManager = $user?->hasAnyRole(['owner', 'developer', 'direktur']) ?? false; + $isManager = $user?->hasAnyRole([Role::OWNER->value, Role::DEVELOPER->value, Role::DIREKTUR->value]) ?? false; $scopedEmployeeId = $isManager ? null : $employee?->id; $hasScopedAccess = $isManager || $employee !== null; @@ -57,7 +58,7 @@ public function index(Request $request): Response ]); } - public function checkIn(AttendanceCheckInRequest $request): RedirectResponse + public function checkIn(CheckInRequest $request): RedirectResponse { $this->attendanceService->checkIn($request->validated(), $request->user()); @@ -66,7 +67,7 @@ public function checkIn(AttendanceCheckInRequest $request): RedirectResponse return redirect()->route('admin.hr.attendances.index'); } - public function checkOut(AttendanceCheckOutRequest $request): RedirectResponse + public function checkOut(CheckOutRequest $request): RedirectResponse { $this->attendanceService->checkOut($request->validated(), $request->user()); @@ -77,16 +78,9 @@ public function checkOut(AttendanceCheckOutRequest $request): RedirectResponse public function destroy(Attendance $attendance): RedirectResponse { - $user = auth()->user(); - if ($user && ! $user->hasAnyRole(['owner', 'developer', 'direktur'])) { - if ($attendance->employee_id !== $user->employee?->id) { - abort(403, 'Anda tidak memiliki akses untuk menghapus data presensi ini.'); - } - } - $this->attendanceService->delete($attendance); - $this->flashDeleted('Data presensi'); + $this->flashDeleted('Presensi'); return redirect()->route('admin.hr.attendances.index'); } diff --git a/app/Http/Requests/Admin/Hr/AttendanceCheckInRequest.php b/app/Http/Requests/Admin/Hr/Attendance/AttendanceCheckInRequest.php similarity index 80% rename from app/Http/Requests/Admin/Hr/AttendanceCheckInRequest.php rename to app/Http/Requests/Admin/Hr/Attendance/AttendanceCheckInRequest.php index 552a227..907efdd 100644 --- a/app/Http/Requests/Admin/Hr/AttendanceCheckInRequest.php +++ b/app/Http/Requests/Admin/Hr/Attendance/AttendanceCheckInRequest.php @@ -1,11 +1,11 @@ ['required', 'string'], 'latitude' => ['required', 'numeric', 'between:-90,90'], 'longitude' => ['required', 'numeric', 'between:-180,180'], - 'location_tag' => ['required', 'string', 'max:255'], ]; } @@ -34,7 +33,6 @@ public function attributes(): array 'photo' => 'foto', 'latitude' => 'latitude', 'longitude' => 'longitude', - 'location_tag' => 'lokasi', ]; } } diff --git a/app/Http/Requests/Admin/Hr/AttendanceCheckOutRequest.php b/app/Http/Requests/Admin/Hr/Attendance/AttendanceCheckOutRequest.php similarity index 79% rename from app/Http/Requests/Admin/Hr/AttendanceCheckOutRequest.php rename to app/Http/Requests/Admin/Hr/Attendance/AttendanceCheckOutRequest.php index e304694..0c873b6 100644 --- a/app/Http/Requests/Admin/Hr/AttendanceCheckOutRequest.php +++ b/app/Http/Requests/Admin/Hr/Attendance/AttendanceCheckOutRequest.php @@ -1,11 +1,11 @@ ['required', 'string'], 'latitude' => ['required', 'numeric', 'between:-90,90'], 'longitude' => ['required', 'numeric', 'between:-180,180'], - 'location_tag' => ['required', 'string', 'max:255'], ]; } @@ -34,7 +33,6 @@ public function attributes(): array 'photo' => 'foto', 'latitude' => 'latitude', 'longitude' => 'longitude', - 'location_tag' => 'lokasi', ]; } } diff --git a/app/Services/Hr/AttendanceService.php b/app/Services/Hr/AttendanceService.php index 3a5374b..2d73f62 100644 --- a/app/Services/Hr/AttendanceService.php +++ b/app/Services/Hr/AttendanceService.php @@ -13,7 +13,6 @@ use Carbon\CarbonInterface; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Http; use Illuminate\Validation\ValidationException; class AttendanceService @@ -71,7 +70,7 @@ public function isOnLeaveToday(Employee $employee): bool } /** - * @param array{photo: string, latitude: float, longitude: float, location_tag: string} $validated + * @param array{photo: string, latitude: float, longitude: float} $validated */ public function checkIn(array $validated, User $user): void { @@ -88,19 +87,12 @@ public function checkIn(array $validated, User $user): void ]); } - $locationTag = $this->resolveLocationTag( - $validated['location_tag'], - (float) $validated['latitude'], - (float) $validated['longitude'], - ); - $attendance = Attendance::create([ 'employee_id' => $employee->id, 'attendance_date' => today(), 'check_in_at' => now(), 'check_in_latitude' => $validated['latitude'], 'check_in_longitude' => $validated['longitude'], - 'check_in_location_tag' => $locationTag, ]); $this->mediaService->addBase64Image( @@ -112,14 +104,14 @@ public function checkIn(array $validated, User $user): void $this->pushNotificationService->sendToRoles( '⏰ Presensi Masuk', - "Karyawan {$user->profile?->full_name} melakukan presensi masuk di {$locationTag}.", + "Karyawan {$user->profile?->full_name} melakukan presensi masuk.", ['owner', 'developer'], '/admin/hr/attendances', ); } /** - * @param array{photo: string, latitude: float, longitude: float, location_tag: string} $validated + * @param array{photo: string, latitude: float, longitude: float} $validated */ public function checkOut(array $validated, User $user): void { @@ -145,16 +137,9 @@ public function checkOut(array $validated, User $user): void $checkOutAt = now(); $workDurationMinutes = (int) $attendance->check_in_at->diffInMinutes($checkOutAt); - $locationTag = $this->resolveLocationTag( - $validated['location_tag'], - (float) $validated['latitude'], - (float) $validated['longitude'], - ); - $attendance->check_out_at = $checkOutAt; $attendance->check_out_latitude = $validated['latitude']; $attendance->check_out_longitude = $validated['longitude']; - $attendance->check_out_location_tag = $locationTag; $attendance->work_duration_minutes = $workDurationMinutes; $attendance->save(); @@ -167,7 +152,7 @@ public function checkOut(array $validated, User $user): void $this->pushNotificationService->sendToRoles( '⏰ Presensi Pulang', - "Karyawan {$user->profile?->full_name} melakukan presensi pulang di {$locationTag} (Durasi kerja: ".round($workDurationMinutes / 60, 1).' jam).', + "Karyawan {$user->profile?->full_name} melakukan presensi pulang (Durasi kerja: ".round($workDurationMinutes / 60, 1).' jam).', ['owner', 'developer'], '/admin/hr/attendances', ); @@ -179,40 +164,4 @@ public function delete(Attendance $attendance): void $attendance->clearMediaCollection('checkout'); $attendance->delete(); } - - private function resolveLocationTag(string $clientTag, float $latitude, float $longitude): string - { - if ($clientTag !== '') { - return mb_substr($clientTag, 0, 255); - } - - $geocoded = $this->reverseGeocode($latitude, $longitude); - - if ($geocoded !== null) { - return mb_substr($geocoded, 0, 255); - } - - return mb_substr(sprintf('%s, %s', $latitude, $longitude), 0, 255); - } - - private function reverseGeocode(float $latitude, float $longitude): ?string - { - try { - $response = Http::timeout(5) - ->withHeaders(['User-Agent' => config('app.name', 'DST Collection')]) - ->get('https://nominatim.openstreetmap.org/reverse', [ - 'lat' => $latitude, - 'lon' => $longitude, - 'format' => 'json', - ]); - - if (! $response->successful()) { - return null; - } - - return $response->json('display_name'); - } catch (\Throwable) { - return null; - } - } } diff --git a/database/factories/AttendanceFactory.php b/database/factories/AttendanceFactory.php index b30acdd..24efa21 100644 --- a/database/factories/AttendanceFactory.php +++ b/database/factories/AttendanceFactory.php @@ -25,8 +25,6 @@ public function definition(): array 'check_in_longitude' => fake()->longitude(106, 107), 'check_out_latitude' => fake()->latitude(-7, -6), 'check_out_longitude' => fake()->longitude(106, 107), - 'check_in_location_tag' => fake()->optional()->city(), - 'check_out_location_tag' => fake()->optional()->city(), 'work_duration_minutes' => fake()->numberBetween(240, 540), ]; } @@ -38,7 +36,6 @@ public function checkedInOnly(): static 'check_out_photo_path' => null, 'check_out_latitude' => null, 'check_out_longitude' => null, - 'check_out_location_tag' => null, 'work_duration_minutes' => null, ]); } diff --git a/database/migrations/2026_06_11_100000_create_attendances_table.php b/database/migrations/2026_06_11_100000_create_attendances_table.php index 6ead719..4f5b688 100644 --- a/database/migrations/2026_06_11_100000_create_attendances_table.php +++ b/database/migrations/2026_06_11_100000_create_attendances_table.php @@ -20,8 +20,6 @@ public function up(): void $table->decimal('check_in_longitude', 10, 7)->nullable(); $table->decimal('check_out_latitude', 10, 7)->nullable(); $table->decimal('check_out_longitude', 10, 7)->nullable(); - $table->string('check_in_location_tag', 255)->nullable(); - $table->string('check_out_location_tag', 255)->nullable(); $table->unsignedInteger('work_duration_minutes')->nullable(); $table->timestamp('created_at')->useCurrent(); diff --git a/resources/js/components/admin/hr/attendances/AttendanceMap.vue b/resources/js/components/admin/hr/attendances/AttendanceMap.vue deleted file mode 100644 index 2b67bcd..0000000 --- a/resources/js/components/admin/hr/attendances/AttendanceMap.vue +++ /dev/null @@ -1,78 +0,0 @@ - - -