From b2c5d494ab20a319eb09340f45830869a934fdd2 Mon Sep 17 00:00:00 2001 From: jouel88 Date: Tue, 7 Apr 2026 10:46:34 +0700 Subject: [PATCH] sedikit penyesuaian --- .../Api/FaceEnrollmentController.php | 21 +- app/Http/Controllers/Api/PoinController.php | 48 +++-- .../Controllers/Api/PresensiController.php | 11 +- app/Http/Controllers/HariLiburController.php | 24 +-- app/Http/Controllers/JadwalController.php | 47 ++--- app/Http/Controllers/LaporanController.php | 70 +++++-- .../Controllers/LaporanLemburController.php | 111 +++++----- app/Http/Controllers/LemburController.php | 51 ++++- app/Http/Controllers/PegawaiController.php | 18 +- .../Controllers/PengajuanIzinController.php | 84 +++++--- .../Controllers/PenggunaanPoinController.php | 11 +- app/Http/Controllers/PengumumanController.php | 24 ++- app/Http/Controllers/PresensiController.php | 16 +- app/Http/Controllers/ProfileController.php | 17 +- app/Http/Controllers/SuratIzinController.php | 2 +- app/Http/Controllers/TukarShiftController.php | 20 +- .../Requests/BulkDestroyJadwalRequest.php | 35 ++++ app/Http/Requests/StoreHariLiburRequest.php | 11 +- app/Http/Requests/StoreJadwalRequest.php | 38 ++++ app/Http/Requests/StorePegawaiRequest.php | 12 +- .../Requests/StorePengajuanIzinRequest.php | 19 +- app/Http/Requests/UpdatePegawaiRequest.php | 34 ++- app/Http/Requests/UpdateProfileRequest.php | 35 ++++ app/Jobs/RetrainAllModels.php | 6 +- app/Models/PengajuanIzin.php | 2 +- app/Models/User.php | 7 + app/Services/FaceRecognitionService.php | 11 +- app/Services/PoinService.php | 13 +- ...3339_create_log_verifikasi_wajah_table.php | 12 +- database/seeders/PengajuanIzinSeeder.php | 3 + public/js/notifications.js | 10 +- python_scripts/requirements.txt | 1 - python_scripts/verify_face_svm.py | 2 +- .../views/components/date-input.blade.php | 29 +-- resources/views/cuti/index.blade.php | 7 +- resources/views/dashboard/index.blade.php | 4 +- resources/views/divisi/index.blade.php | 42 ++-- resources/views/face-approval/index.blade.php | 8 +- resources/views/hari_libur/index.blade.php | 195 ++++++++++++++++-- resources/views/izin/index.blade.php | 81 +++++--- resources/views/jabatan/index.blade.php | 40 ++-- resources/views/jadwal/create.blade.php | 82 ++------ resources/views/jadwal/index.blade.php | 72 ++++--- .../views/jadwal/test_isolation.blade.php | 29 --- .../views/laporan-lembur/index.blade.php | 2 +- resources/views/laporan/index.blade.php | 4 +- resources/views/laporan/izin.blade.php | 9 +- resources/views/lembur/create.blade.php | 4 +- resources/views/lembur/index.blade.php | 13 +- resources/views/pegawai/create.blade.php | 12 +- resources/views/pegawai/edit.blade.php | 12 +- resources/views/pegawai/show.blade.php | 6 +- .../views/penggunaan-poin/index.blade.php | 40 ++-- resources/views/pengumuman/create.blade.php | 4 +- resources/views/pengumuman/edit.blade.php | 4 +- resources/views/pengumuman/index.blade.php | 14 +- resources/views/presensi/index.blade.php | 12 +- resources/views/profile/edit.blade.php | 74 +++---- resources/views/role/index.blade.php | 2 +- resources/views/surat-izin/show.blade.php | 48 ++--- resources/views/tukar-shift/create.blade.php | 7 +- 61 files changed, 1009 insertions(+), 673 deletions(-) create mode 100644 app/Http/Requests/BulkDestroyJadwalRequest.php create mode 100644 app/Http/Requests/StoreJadwalRequest.php create mode 100644 app/Http/Requests/UpdateProfileRequest.php delete mode 100644 resources/views/jadwal/test_isolation.blade.php diff --git a/app/Http/Controllers/Api/FaceEnrollmentController.php b/app/Http/Controllers/Api/FaceEnrollmentController.php index 2180074a..5eea1b71 100644 --- a/app/Http/Controllers/Api/FaceEnrollmentController.php +++ b/app/Http/Controllers/Api/FaceEnrollmentController.php @@ -99,23 +99,24 @@ public function verifyFace(Request $request) 'tipe' => 'nullable|string|in:presensi,test', ]); + $tipe = $request->input('tipe', 'presensi'); + try { set_time_limit(0); $user = $request->user(); - $tipe = $request->input('tipe', 'presensi'); $file = $request->file('foto'); $result = $this->faceService->verifyFace($user->id, $file); \DB::table('log_verifikasi_wajah')->insert([ 'id_user' => $user->id, - 'confidence' => $result['confidence'] ?? null, - 'svm_confidence' => $result['svm_confidence'] ?? null, - 'normalized_distance' => $result['normalized_distance'] ?? null, - 'verification_status' => $result['verification_status'] ?? null, - 'is_match' => $result['verified'] ?? false, - 'blur_score' => $result['blur_score'] ?? null, + 'skor_kepercayaan' => $result['confidence'] ?? null, + 'skor_svm' => $result['svm_confidence'] ?? null, + 'jarak_normalisasi' => $result['normalized_distance'] ?? null, + 'status_verifikasi' => $result['verification_status'] ?? null, + 'apakah_cocok' => $result['verified'] ?? false, + 'skor_blur' => $result['blur_score'] ?? null, 'tipe' => $tipe, 'created_at' => now(), 'updated_at' => now(), @@ -131,9 +132,9 @@ public function verifyFace(Request $request) if (isset($user)) { \DB::table('log_verifikasi_wajah')->insert([ 'id_user' => $user->id, - 'confidence' => 0, - 'verification_status' => 'ERROR', - 'is_match' => false, + 'skor_kepercayaan' => 0, + 'status_verifikasi' => 'ERROR', + 'apakah_cocok' => false, 'tipe' => $tipe ?? 'presensi', 'created_at' => now(), 'updated_at' => now(), diff --git a/app/Http/Controllers/Api/PoinController.php b/app/Http/Controllers/Api/PoinController.php index 0fe53a40..b0ab7bc2 100644 --- a/app/Http/Controllers/Api/PoinController.php +++ b/app/Http/Controllers/Api/PoinController.php @@ -2,26 +2,34 @@ namespace App\Http\Controllers\Api; +use App\Enums\JenisPengurangan; use App\Enums\StatusPengajuan; use App\Helpers\ApiResponse; use App\Http\Controllers\Controller; -use App\Models\Poin; use App\Models\PenggunaanPoin; +use App\Services\NotifikasiService; use App\Services\PoinService; use Carbon\Carbon; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Validator; class PoinController extends Controller { + protected $poinService; + + public function __construct(PoinService $poinService) + { + $this->poinService = $poinService; + } + public function getExpiringPoints() { try { - $userId = auth()->id(); + $userId = Auth::id(); - $poinService = new PoinService(); - $poinExpiring = $poinService->getExpiringPoints($userId); - $activePoints = $poinService->getActivePoints($userId); + $poinExpiring = $this->poinService->getExpiringPoints($userId); + $activePoints = $this->poinService->getActivePoints($userId); $firstExpiring = $poinExpiring->first(); $formattedExpiring = null; @@ -46,10 +54,8 @@ public function getExpiringPoints() public function getPointHistory() { try { - $userId = auth()->id(); - - $poinService = new PoinService(); - $history = $poinService->getPointHistory($userId); + $userId = Auth::id(); + $history = $this->poinService->getPointHistory($userId); return ApiResponse::success(['history' => $history]); @@ -62,10 +68,10 @@ public function redeem(Request $request) { $validator = Validator::make($request->all(), [ 'jumlah' => 'required|integer|min:1', - 'keterangan' => 'required|string', + 'keterangan' => 'required|string|max:500', 'id_pengurangan' => 'required|integer', - 'jam_masuk_custom' => 'required_if:id_pengurangan,4|date_format:H:i|nullable', - 'jam_pulang_custom' => 'required_if:id_pengurangan,5|date_format:H:i|nullable', + 'jam_masuk_custom' => 'required_if:id_pengurangan,' . JenisPengurangan::MASUK_SIANG_POIN . '|date_format:H:i|nullable', + 'jam_pulang_custom' => 'required_if:id_pengurangan,' . JenisPengurangan::PULANG_CEPAT_POIN . '|date_format:H:i|nullable', ]); if ($validator->fails()) { @@ -73,18 +79,16 @@ public function redeem(Request $request) } try { - $userId = auth()->id(); + $userId = Auth::id(); $jumlah = $request->jumlah; - $keterangan = $request->keterangan; - $poinService = new PoinService(); - $totalPoin = $poinService->getActivePoints($userId); + $totalPoin = $this->poinService->getActivePoints($userId); if ($totalPoin < $jumlah) { - return ApiResponse::error('Saldo poin tidak mencukupi', 400); + return ApiResponse::error('Saldo poin tidak mencukupi. Saldo aktif: ' . $totalPoin . ', dibutuhkan: ' . $jumlah, 400); } - preg_match('/\[(\d{4}-\d{2}-\d{2})\]/', $keterangan, $matches); + preg_match('/\[(\d{4}-\d{2}-\d{2})\]/', $request->keterangan, $matches); $tanggalPenggunaan = isset($matches[1]) ? $matches[1] : now()->format('Y-m-d'); $jadwal = \App\Models\JadwalKerja::with('shift') @@ -113,6 +117,14 @@ public function redeem(Request $request) 'tanggal_diajukan' => now(), ]); + $user = Auth::user(); + app(NotifikasiService::class)->kirimKeRole( + 'hrd', + 'pengajuan_baru', + 'Pengajuan Penggunaan Poin', + $user->nama_lengkap . ' mengajukan penggunaan ' . $jumlah . ' poin.' + ); + return ApiResponse::success(null, 'Pengajuan penukaran poin berhasil disimpan'); } catch (\Exception $e) { return ApiResponse::error($e->getMessage(), 400); diff --git a/app/Http/Controllers/Api/PresensiController.php b/app/Http/Controllers/Api/PresensiController.php index 997eca89..7eaadff4 100644 --- a/app/Http/Controllers/Api/PresensiController.php +++ b/app/Http/Controllers/Api/PresensiController.php @@ -258,8 +258,15 @@ public function history(Request $request) try { $masuk = Carbon::parse($item->tanggal . ' ' . $item->jam_masuk); $pulang = Carbon::parse($item->tanggal . ' ' . $item->jam_pulang); - $diff = $masuk->diff($pulang); - $totalJam = $diff->h . 'j ' . $diff->i . 'm'; + + if ($pulang->lessThan($masuk)) { + $pulang->addDay(); + } + + $diffMinutes = $pulang->diffInMinutes($masuk); + $hours = floor($diffMinutes / 60); + $minutes = $diffMinutes % 60; + $totalJam = $hours . 'j ' . $minutes . 'm'; } catch (\Exception $e) { $totalJam = '-'; } diff --git a/app/Http/Controllers/HariLiburController.php b/app/Http/Controllers/HariLiburController.php index c02bc163..ca39f8e0 100644 --- a/app/Http/Controllers/HariLiburController.php +++ b/app/Http/Controllers/HariLiburController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Models\HariLibur; +use App\Http\Requests\StoreHariLiburRequest; use Illuminate\Http\Request; use Carbon\Carbon; @@ -27,15 +28,9 @@ public function index(Request $request) return view('hari_libur.index', compact('hariLiburs', 'kantors')); } - public function store(Request $request) + public function store(StoreHariLiburRequest $request) { - $request->validate([ - 'tanggal' => 'required|date', - 'keterangan' => 'required|string|max:255', - 'id_kantor' => 'nullable|exists:kantor,id_kantor', - ]); - - HariLibur::create($request->all()); + HariLibur::create($request->validated()); return redirect()->route('hari-libur.index') ->with('success', 'Hari libur berhasil ditambahkan.'); @@ -44,13 +39,13 @@ public function store(Request $request) public function update(Request $request, $id) { $request->validate([ - 'tanggal' => 'required|date', - 'keterangan' => 'required|string|max:255', - 'id_kantor' => 'nullable|exists:kantor,id_kantor', + 'tanggal' => 'required|date', + 'keterangan' => 'required|string|max:255', + 'id_kantor' => 'nullable|exists:kantor,id_kantor', ]); - $hariLibur = HariLibur::where('id', '=', $id)->first() ?? HariLibur::where('id_hari_libur', '=', $id)->firstOrFail(); - $hariLibur->update($request->all()); + $hariLibur = HariLibur::findOrFail($id); + $hariLibur->update($request->only(['tanggal', 'keterangan', 'id_kantor'])); return redirect()->route('hari-libur.index') ->with('success', 'Hari libur berhasil diperbarui.'); @@ -58,8 +53,7 @@ public function update(Request $request, $id) public function destroy($id) { - $hariLibur = HariLibur::where('id', '=', $id)->first() ?? HariLibur::where('id_hari_libur', '=', $id)->firstOrFail(); - $hariLibur->delete(); + HariLibur::findOrFail($id)->delete(); return redirect()->route('hari-libur.index') ->with('success', 'Hari libur berhasil dihapus.'); diff --git a/app/Http/Controllers/JadwalController.php b/app/Http/Controllers/JadwalController.php index 3bde9cb4..d0012689 100644 --- a/app/Http/Controllers/JadwalController.php +++ b/app/Http/Controllers/JadwalController.php @@ -13,6 +13,8 @@ use App\Models\PenggunaanPoin; use App\Services\PoinService; use Illuminate\Support\Facades\Auth; +use App\Http\Requests\StoreJadwalRequest; +use App\Http\Requests\BulkDestroyJadwalRequest; class JadwalController extends Controller { @@ -28,7 +30,7 @@ public function index() return $s; }); - $pegawaiQuery = User::where('status_aktif', 1); + $pegawaiQuery = User::where('status_aktif', 1)->bukanHrd(); if (!$isGlobalAdmin) { $pegawaiQuery->where('id_kantor', $user->id_kantor); } @@ -211,7 +213,7 @@ public function create() $kantor = $isGlobalAdmin ? Kantor::all() : Kantor::where('id_kantor', $user->id_kantor)->get(); $divisi = Divisi::all(); - $pegawaiQuery = User::where('status_aktif', 1)->with(['kantor', 'jabatan']); + $pegawaiQuery = User::where('status_aktif', 1)->bukanHrd()->with(['kantor', 'jabatan']); if (!$isGlobalAdmin) { $pegawaiQuery->where('id_kantor', $user->id_kantor); } @@ -220,15 +222,9 @@ public function create() return view('jadwal.create', compact('shifts', 'pegawai', 'kantor', 'divisi')); } - public function store(Request $request) + public function store(StoreJadwalRequest $request) { - $request->validate([ - 'tanggal_mulai' => 'required|date', - 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', - 'id_shift' => 'required|exists:shift_kerja,id_shift', - 'user_ids' => 'required|array', - 'user_ids.*' => 'exists:users,id', - ]); + $data = $request->validated(); // Guard: pastikan ada data shift sebelum memproses if (ShiftKerja::count() === 0) { @@ -249,12 +245,12 @@ public function store(Request $request) } } - $startDate = Carbon::parse($request->tanggal_mulai); - $endDate = Carbon::parse($request->tanggal_selesai); + $startDate = Carbon::parse($data['tanggal_mulai']); + $endDate = Carbon::parse($data['tanggal_selesai']); $period = \Carbon\CarbonPeriod::create($startDate, $endDate); - $shiftId = $request->id_shift; - $userIds = $request->user_ids; + $shiftId = $data['id_shift']; + $userIds = $data['user_ids']; $count = 0; $refundCount = 0; @@ -364,38 +360,33 @@ public function destroy($id) return response()->json(['success' => true, 'message' => 'Jadwal berhasil dihapus']); } - public function bulkDestroy(Request $request) + public function bulkDestroy(BulkDestroyJadwalRequest $request) { - $request->validate([ - 'tanggal_mulai' => 'required|date', - 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', - 'user_ids' => 'required|array', - 'user_ids.*' => 'exists:users,id', - ]); + $data = $request->validated(); $user = Auth::user(); $isGlobalAdmin = $user->isGlobalAdmin(); if (!$isGlobalAdmin) { - $invalidUsers = User::whereIn('id', $request->user_ids) + $invalidUsers = User::whereIn('id', $data['user_ids']) ->where('id_kantor', '!=', $user->id_kantor) ->exists(); if ($invalidUsers) { - return redirect()->back()->with('error', 'Anda tidak diizinkan menghapus jadwal karyawan di luar kantor Anda.'); + return redirect()->route('jadwal.index')->with('error', 'Anda tidak diizinkan menghapus jadwal karyawan di luar kantor Anda.'); } } try { - DB::transaction(function () use ($request) { - JadwalKerja::whereIn('id_user', $request->user_ids) - ->whereBetween('tanggal', [$request->tanggal_mulai, $request->tanggal_selesai]) + DB::transaction(function () use ($data) { + JadwalKerja::whereIn('id_user', $data['user_ids']) + ->whereBetween('tanggal', [$data['tanggal_mulai'], $data['tanggal_selesai']]) ->delete(); }); - return redirect()->back()->with('success', 'Jadwal kerja berhasil dihapus pada rentang tanggal tersebut.'); + return redirect()->route('jadwal.index')->with('success', 'Jadwal kerja berhasil dihapus pada rentang tanggal tersebut.'); } catch (\Exception $e) { - return redirect()->back()->with('error', 'Terjadi kesalahan saat menghapus jadwal: ' . $e->getMessage()); + return redirect()->route('jadwal.index')->with('error', 'Terjadi kesalahan saat menghapus jadwal: ' . $e->getMessage()); } } diff --git a/app/Http/Controllers/LaporanController.php b/app/Http/Controllers/LaporanController.php index e6de68b6..9b71c37a 100644 --- a/app/Http/Controllers/LaporanController.php +++ b/app/Http/Controllers/LaporanController.php @@ -6,6 +6,7 @@ use App\Models\User; use App\Models\Presensi; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Carbon\Carbon; use Maatwebsite\Excel\Facades\Excel; @@ -17,11 +18,15 @@ class LaporanController extends Controller { public function index(Request $request) { - $bulan = $request->input('bulan', date('m')); - $tahun = $request->input('tahun', date('Y')); + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); $divisiId = $request->input('id_divisi'); - $rekap = $this->buildRekap($bulan, $tahun, $divisiId); + $authUser = Auth::user(); + $isGlobalAdmin = $authUser->isGlobalAdmin(); + $kantorId = $isGlobalAdmin ? null : $authUser->id_kantor; + + $rekap = $this->buildRekap($bulan, $tahun, $divisiId, $kantorId); $divisiList = \App\Models\Divisi::all(); return view('laporan.index', compact('rekap', 'bulan', 'tahun', 'divisiList', 'divisiId')); @@ -29,15 +34,24 @@ public function index(Request $request) public function cuti(Request $request) { - $bulan = $request->input('bulan', date('m')); - $tahun = $request->input('tahun', date('Y')); - $search = $request->input('search'); + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); + $search = $request->input('search'); $divisiId = $request->input('id_divisi'); + $authUser = Auth::user(); + $isGlobalAdmin = $authUser->isGlobalAdmin(); + $query = \App\Models\PengajuanIzin::with(['user.divisi', 'jenisIzin']) ->whereYear('tanggal_mulai', $tahun) ->whereMonth('tanggal_mulai', $bulan); + if (!$isGlobalAdmin) { + $query->whereHas('user', function ($q) use ($authUser) { + $q->where('id_kantor', $authUser->id_kantor); + }); + } + if ($search) { $query->whereHas('user', function($q) use ($search) { $q->where('nama_lengkap', 'like', "%{$search}%") @@ -51,7 +65,7 @@ public function cuti(Request $request) }); } - $izinList = $query->orderBy('tanggal_mulai', 'desc')->paginate(15)->withQueryString(); + $izinList = $query->orderBy('tanggal_mulai', 'desc')->paginate(15)->withQueryString(); $divisiList = \App\Models\Divisi::all(); return view('laporan.izin', compact('izinList', 'bulan', 'tahun', 'search', 'divisiId', 'divisiList')); @@ -64,10 +78,19 @@ public function exportIzinExcel(Request $request) $search = $request->input('search'); $divisiId = $request->input('id_divisi'); + $authUser = Auth::user(); + $isGlobalAdmin = $authUser->isGlobalAdmin(); + $query = \App\Models\PengajuanIzin::with(['user.divisi', 'jenisIzin']) ->whereYear('tanggal_mulai', $tahun) ->whereMonth('tanggal_mulai', $bulan); + if (!$isGlobalAdmin) { + $query->whereHas('user', function ($q) use ($authUser) { + $q->where('id_kantor', $authUser->id_kantor); + }); + } + if ($search) { $query->whereHas('user', function ($q) use ($search) { $q->where('nama_lengkap', 'like', "%{$search}%") @@ -94,11 +117,14 @@ public function exportIzinExcel(Request $request) public function exportExcel(Request $request) { - $bulan = $request->input('bulan', date('m')); - $tahun = $request->input('tahun', date('Y')); + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); $divisiId = $request->input('id_divisi'); - $rekap = $this->buildRekap($bulan, $tahun, $divisiId); + $authUser = Auth::user(); + $kantorId = $authUser->isGlobalAdmin() ? null : $authUser->id_kantor; + + $rekap = $this->buildRekap($bulan, $tahun, $divisiId, $kantorId); if (empty($rekap)) { return redirect()->back()->with('error', 'Tidak ada data presensi untuk diekspor pada periode tersebut.'); @@ -111,11 +137,14 @@ public function exportExcel(Request $request) public function exportPdf(Request $request) { - $bulan = $request->input('bulan', date('m')); - $tahun = $request->input('tahun', date('Y')); + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); $divisiId = $request->input('id_divisi'); - $rekap = $this->buildRekap($bulan, $tahun, $divisiId); + $authUser = Auth::user(); + $kantorId = $authUser->isGlobalAdmin() ? null : $authUser->id_kantor; + + $rekap = $this->buildRekap($bulan, $tahun, $divisiId, $kantorId); $pdf = Pdf::loadView('laporan.pdf', compact('rekap', 'bulan', 'tahun')); $pdf->setPaper('a4', 'landscape'); @@ -123,19 +152,24 @@ public function exportPdf(Request $request) return $pdf->download("Laporan_Presensi_{$bulan}_{$tahun}.pdf"); } - private function buildRekap(int $bulan, int $tahun, $divisiId = null): array + private function buildRekap(int $bulan, int $tahun, $divisiId = null, $kantorId = null): array { $idTepatWaktu = StatusPresensi::TEPAT_WAKTU; - $idTerlambat = StatusPresensi::TERLAMBAT; - $idIzin = StatusPresensi::IZIN; - $idSakit = StatusPresensi::SAKIT; - $idAlpha = StatusPresensi::ALPHA; + $idTerlambat = StatusPresensi::TERLAMBAT; + $idIzin = StatusPresensi::IZIN; + $idSakit = StatusPresensi::SAKIT; + $idAlpha = StatusPresensi::ALPHA; $pegawaiQuery = User::with(['jabatan', 'divisi']) ->where('status_aktif', 1) + ->bukanHrd() ->select('id', 'nama_lengkap', 'nik', 'id_jabatan', 'id_divisi') ->orderBy('nama_lengkap', 'asc'); + if ($kantorId) { + $pegawaiQuery->where('id_kantor', $kantorId); + } + if ($divisiId) { $pegawaiQuery->where('id_divisi', $divisiId); } diff --git a/app/Http/Controllers/LaporanLemburController.php b/app/Http/Controllers/LaporanLemburController.php index 3a6c3a92..dedfd421 100644 --- a/app/Http/Controllers/LaporanLemburController.php +++ b/app/Http/Controllers/LaporanLemburController.php @@ -2,9 +2,11 @@ namespace App\Http\Controllers; +use App\Enums\StatusPengajuan; use App\Models\User; use App\Models\Lembur; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Maatwebsite\Excel\Facades\Excel; @@ -15,83 +17,80 @@ class LaporanLemburController extends Controller public function index(Request $request) { - $bulan = $request->input('bulan', date('m')); - $tahun = $request->input('tahun', date('Y')); - $search = $request->input('search'); + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); + $search = $request->input('search'); $divisiId = $request->input('id_divisi'); - $query = User::with(['divisi', 'jabatan', 'lemburs' => function($q) use ($bulan, $tahun) { - $q->whereYear('tanggal_lembur', $tahun) - ->whereMonth('tanggal_lembur', $bulan) - ->where('id_status', 2); - }]) - ->whereHas('roles', function($q) { - $q->where('nama_role', '!=', 'super_admin'); - }); + $authUser = Auth::user(); + $isGlobalAdmin = $authUser->isGlobalAdmin(); - if ($search) { - $query->where(function($q) use ($search) { - $q->where('nama_lengkap', 'like', '%' . $search . '%') - ->orWhere('nik', 'like', '%' . $search . '%'); - }); - } + [$pegawai, $rekap] = $this->buildRekap( + $bulan, $tahun, $divisiId, + $isGlobalAdmin ? null : $authUser->id_kantor, + $search + ); - if ($divisiId) { - $query->where('id_divisi', $divisiId); - } - - $pegawai = $query->orderBy('nama_lengkap', 'asc')->paginate(15)->withQueryString(); $divisiList = \App\Models\Divisi::all(); - $rekap = []; - foreach ($pegawai as $p) { - $totalMenit = $p->lemburs->sum('durasi_menit'); - $jam = floor($totalMenit / 60); - $menit = $totalMenit % 60; - - $rekap[$p->id] = [ - 'total_menit' => $totalMenit, - 'format_jam' => "{$jam}j {$menit}m", - 'jumlah_hari' => $p->lemburs->count(), - 'poin_diperoleh' => $p->lemburs->sum('jumlah_poin'), - ]; - } - return view('laporan-lembur.index', compact('pegawai', 'rekap', 'bulan', 'tahun', 'search', 'divisiId', 'divisiList')); } public function exportExcel(Request $request) { - $bulan = $request->input('bulan', date('m')); - $tahun = $request->input('tahun', date('Y')); + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); $divisiId = $request->input('id_divisi'); - $query = User::with(['divisi', 'jabatan', 'lemburs' => function($q) use ($bulan, $tahun) { - $q->whereYear('tanggal_lembur', $tahun) - ->whereMonth('tanggal_lembur', $bulan) - ->where('id_status', 2); - }]) - ->whereHas('roles', function($q) { - $q->where('nama_role', '!=', 'super_admin'); - }); + $authUser = Auth::user(); + $isGlobalAdmin = $authUser->isGlobalAdmin(); - if ($divisiId) { - $query->where('id_divisi', $divisiId); - } + [$pegawai, $rekap] = $this->buildRekap( + $bulan, $tahun, $divisiId, + $isGlobalAdmin ? null : $authUser->id_kantor + ); - $pegawai = $query->orderBy('nama_lengkap', 'asc')->get(); - - // Guard: cek apakah ada data lembur di periode tersebut $adaDataLembur = $pegawai->contains(fn($p) => $p->lemburs->isNotEmpty()); if (!$adaDataLembur) { return redirect()->back()->with('error', 'Tidak ada data lembur yang disetujui untuk diekspor pada periode tersebut.'); } + $filename = "Laporan_Lembur_{$bulan}_{$tahun}.xlsx"; + + return Excel::download(new LaporanLemburExport($pegawai, $rekap, $bulan, $tahun), $filename); + } + + private function buildRekap(string $bulan, string $tahun, $divisiId = null, $kantorId = null, $search = null): array + { + $query = User::with(['divisi', 'jabatan', 'lemburs' => function ($q) use ($bulan, $tahun) { + $q->whereYear('tanggal_lembur', $tahun) + ->whereMonth('tanggal_lembur', $bulan) + ->where('id_status', StatusPengajuan::DISETUJUI); + }]) + ->bukanHrd(); + + if ($kantorId) { + $query->where('id_kantor', $kantorId); + } + + if ($divisiId) { + $query->where('id_divisi', $divisiId); + } + + if ($search) { + $query->where(function ($q) use ($search) { + $q->where('nama_lengkap', 'like', '%' . $search . '%') + ->orWhere('nik', 'like', '%' . $search . '%'); + }); + } + + $pegawai = $query->orderBy('nama_lengkap', 'asc')->paginate(15)->withQueryString(); + $rekap = []; foreach ($pegawai as $p) { $totalMenit = $p->lemburs->sum('durasi_menit'); - $jam = floor($totalMenit / 60); - $menit = $totalMenit % 60; + $jam = floor($totalMenit / 60); + $menit = $totalMenit % 60; $rekap[$p->id] = [ 'total_menit' => $totalMenit, @@ -101,8 +100,6 @@ public function exportExcel(Request $request) ]; } - $filename = "Laporan_Lembur_{$bulan}_{$tahun}.xlsx"; - - return Excel::download(new LaporanLemburExport($pegawai, $rekap, $bulan, $tahun), $filename); + return [$pegawai, $rekap]; } } diff --git a/app/Http/Controllers/LemburController.php b/app/Http/Controllers/LemburController.php index 473875b8..9fd5f44f 100644 --- a/app/Http/Controllers/LemburController.php +++ b/app/Http/Controllers/LemburController.php @@ -10,6 +10,7 @@ use App\Services\LemburService; use App\Services\NotifikasiService; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; class LemburController extends Controller @@ -23,10 +24,10 @@ public function __construct(LemburService $lemburService) public function index() { - $user = \Illuminate\Support\Facades\Auth::user(); + $user = Auth::user(); $isGlobalAdmin = $user->isGlobalAdmin(); - $query = Lembur::with(['user', 'status', 'kompensasi']); + $query = Lembur::with(['user.jabatan', 'status', 'kompensasi']); if (!$isGlobalAdmin) { $query->whereHas('user', function ($q) use ($user) { @@ -43,7 +44,14 @@ public function index() public function create() { - $pegawai = User::where('status_aktif', 1)->orderBy('nama_lengkap', 'asc')->get(); + $user = Auth::user(); + $query = User::where('status_aktif', 1); + + if (!$user->isGlobalAdmin()) { + $query->where('id_kantor', $user->id_kantor); + } + + $pegawai = $query->orderBy('nama_lengkap', 'asc')->get(); $kompensasi = JenisKompensasi::all(); return view('lembur.create', compact('pegawai', 'kompensasi')); } @@ -53,19 +61,40 @@ public function store(Request $request) $request->validate([ 'id_user' => 'required|exists:users,id', 'tanggal_lembur' => 'required|date', - 'jam_mulai' => 'required', - 'jam_selesai' => 'required', + 'jam_mulai' => 'required|date_format:H:i', + 'jam_selesai' => 'required|date_format:H:i|different:jam_mulai', 'id_kompensasi' => 'required|exists:jenis_kompensasi,id_kompensasi', - 'keterangan' => 'nullable|string' + 'keterangan' => 'nullable|string|max:255' ]); + $userAuth = Auth::user(); + $targetUser = User::findOrFail($request->id_user); + + if (!$userAuth->isGlobalAdmin() && $targetUser->id_kantor != $userAuth->id_kantor) { + return redirect()->back()->with('error', 'Anda tidak diizinkan membuat lembur untuk pegawai kantor lain.')->withInput(); + } + try { - DB::transaction(function () use ($request) { - $user = User::findOrFail($request->id_user); - $lembur = $this->lemburService->createLembur($user, $request->all()); + $lembur = null; + DB::transaction(function () use ($request, $targetUser, &$lembur) { + $lembur = $this->lemburService->createLembur($targetUser, $request->all()); $this->lemburService->approve($lembur); }); + if ($lembur) { + $message = 'Lembur manual Anda pada tanggal ' . $lembur->tanggal_lembur->translatedFormat('d F Y') . ' telah ditambahkan dan disetujui oleh HRD.'; + if ($lembur->id_kompensasi == JenisKompensasiEnum::TAMBAHAN_POIN) { + $message .= ' Poin telah ditambahkan ke saldo Anda.'; + } + app(NotifikasiService::class)->kirim( + $targetUser->id, + 'lembur_disetujui', + 'Lembur Manual Disetujui', + $message, + ['id_lembur' => $lembur->id_lembur] + ); + } + return redirect()->route('lembur.index')->with('success', 'Data lembur manual berhasil ditambahkan dan disetujui otomatis.'); } catch (\Exception $e) { return redirect()->back()->with('error', 'Gagal menambahkan lembur: ' . $e->getMessage())->withInput(); @@ -76,11 +105,11 @@ public function update(Request $request, Lembur $lembur) { $request->validate([ 'action' => 'required|in:approve,reject', - 'alasan_penolakan' => 'required_if:action,reject|nullable|string' + 'alasan_penolakan' => 'required_if:action,reject|nullable|string|max:500' ]); $lembur->load('user'); - $userAuth = \Illuminate\Support\Facades\Auth::user(); + $userAuth = Auth::user(); if (!$userAuth->isGlobalAdmin() && $lembur->user->id_kantor != $userAuth->id_kantor) { return redirect()->back()->with('error', 'Anda tidak diizinkan memproses data dari kantor lain.'); } diff --git a/app/Http/Controllers/PegawaiController.php b/app/Http/Controllers/PegawaiController.php index 2aee1a8a..b72c1546 100644 --- a/app/Http/Controllers/PegawaiController.php +++ b/app/Http/Controllers/PegawaiController.php @@ -17,10 +17,7 @@ class PegawaiController extends Controller { public function index(Request $request) { - $query = User::with(['divisi', 'jabatan', 'kantor', 'roles']) - ->whereDoesntHave('roles', function ($q) { - $q->where('nama_role', 'hrd'); - }); + $query = User::with(['divisi', 'jabatan', 'kantor', 'roles'])->bukanHrd(); if ($request->filled('filter_jabatan')) { $query->where('id_jabatan', $request->filter_jabatan); @@ -48,21 +45,14 @@ public function index(Request $request) $allKantor = Kantor::withCount([ 'users as total_pegawai' => function ($q) { - $q->whereDoesntHave('roles', function ($sq) { - $sq->where('nama_role', 'hrd'); - }); + $q->bukanHrd(); } ])->get(); $stats = [ 'kantor_list' => $allKantor, - 'total' => User::whereDoesntHave('roles', function ($q) { - $q->where('nama_role', 'hrd'); - })->count(), - 'active' => User::where('status_aktif', 1) - ->whereDoesntHave('roles', function ($q) { - $q->where('nama_role', 'hrd'); - })->count(), + 'total' => User::bukanHrd()->count(), + 'active' => User::bukanHrd()->where('status_aktif', 1)->count(), ]; $allJabatan = Jabatan::all(); diff --git a/app/Http/Controllers/PengajuanIzinController.php b/app/Http/Controllers/PengajuanIzinController.php index 31593be0..37b0c39c 100644 --- a/app/Http/Controllers/PengajuanIzinController.php +++ b/app/Http/Controllers/PengajuanIzinController.php @@ -7,8 +7,13 @@ use App\Enums\StatusPresensi; use App\Enums\StatusSurat; use App\Enums\StatusValidasi; +use App\Http\Requests\StorePengajuanIzinRequest; +use App\Models\Divisi; +use App\Models\JenisIzin; +use App\Models\Kantor; use App\Models\PengajuanIzin; use App\Models\Presensi; +use App\Models\StatusPengajuan as StatusPengajuanModel; use App\Models\User; use App\Models\SuratIzin; use App\Models\TandaTangan; @@ -47,28 +52,31 @@ public function index(Request $request) $izin = $query->orderBy('created_at', 'desc')->paginate(15); - $statuses = \App\Models\StatusPengajuan::all(); - $jenisIzin = \App\Models\JenisIzin::all(); - $users = \App\Models\User::with(['kantor', 'divisi', 'jabatan'])->select('id', 'nama_lengkap', 'nik', 'id_kantor', 'id_divisi', 'id_jabatan')->orderBy('nama_lengkap', 'asc')->get(); - $kantor = \App\Models\Kantor::all(); - $divisi = \App\Models\Divisi::all(); + $statuses = StatusPengajuanModel::orderBy('id_status')->get(); + $jenisIzinList = JenisIzin::all(); - return view('izin.index', compact('izin', 'statusId', 'statuses', 'jenisIzin', 'users', 'kantor', 'divisi')); + $usersQuery = User::with(['kantor', 'divisi', 'jabatan']) + ->select('id', 'nama_lengkap', 'nik', 'id_kantor', 'id_divisi', 'id_jabatan') + ->where('status_aktif', 1) + ->bukanHrd() + ->orderBy('nama_lengkap', 'asc'); + + if (!$isGlobalAdmin) { + $usersQuery->where('id_kantor', $user->id_kantor); + } + + $users = $usersQuery->get(); + $kantor = $isGlobalAdmin ? Kantor::all() : Kantor::where('id_kantor', $user->id_kantor)->get(); + $divisi = Divisi::all(); + + return view('izin.index', compact('izin', 'statusId', 'statuses', 'jenisIzinList', 'users', 'kantor', 'divisi')); } - public function store(Request $request) + public function store(StorePengajuanIzinRequest $request) { - $request->validate([ - 'id_user' => 'required|exists:users,id', - 'id_jenis_izin' => 'required|exists:jenis_izin,id_jenis_izin', - 'tanggal_mulai' => 'required|date|after_or_equal:today', - 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', - 'alasan' => 'required|string', - 'bukti_file' => 'nullable|file|mimes:jpeg,png,jpg,pdf|max:2048' - ]); - $jenisIzin = \App\Models\JenisIzin::find($request->id_jenis_izin); - if ($jenisIzin && $jenisIzin->nama_izin == 'Cuti') { + $jenisIzinData = JenisIzin::find($request->id_jenis_izin); + if ($jenisIzinData && $jenisIzinData->id_jenis_izin == JenisIzinEnum::CUTI) { $pegawai = User::find($request->id_user); $tanggalMulai = Carbon::parse($request->tanggal_mulai); $tanggalSelesai = Carbon::parse($request->tanggal_selesai); @@ -88,12 +96,12 @@ public function store(Request $request) } } - if ($jenisIzin && $jenisIzin->id_jenis_izin == \App\Enums\JenisIzin::SAKIT) { + if ($jenisIzinData && $jenisIzinData->id_jenis_izin == JenisIzinEnum::SAKIT) { $tanggalMulai = Carbon::parse($request->tanggal_mulai); $jumlahHari = $tanggalMulai->diffInDays(Carbon::parse($request->tanggal_selesai)) + 1; $sudahSakitBulanIni = PengajuanIzin::where('id_user', $request->id_user) - ->where('id_jenis_izin', \App\Enums\JenisIzin::SAKIT) + ->where('id_jenis_izin', JenisIzinEnum::SAKIT) ->whereIn('id_status', [StatusPengajuan::PENDING, StatusPengajuan::DISETUJUI]) ->whereMonth('tanggal_mulai', $tanggalMulai->month) ->whereYear('tanggal_mulai', $tanggalMulai->year) @@ -127,7 +135,7 @@ public function store(Request $request) return back()->withInput()->with('error', 'Pegawai sudah memiliki pengajuan Izin/Cuti (Pending/Disetujui) pada rentang tanggal tersebut!'); } - $existingPresensi = \App\Models\Presensi::where('id_user', $userId) + $existingPresensi = Presensi::where('id_user', $userId) ->whereBetween('tanggal', [$tglMulai, $tglSelesai]) ->whereNotNull('jam_masuk') ->exists(); @@ -260,10 +268,9 @@ public function approve($id) public function reject(Request $request, $id) { - $izin = PengajuanIzin::with('user')->findOrFail($id); + $izin = PengajuanIzin::with(['user', 'jenisIzin'])->findOrFail($id); $user = Auth::user(); - // Security Check if (!$user->isGlobalAdmin() && $izin->user->id_kantor != $user->id_kantor) { return redirect()->back()->with('error', 'Anda tidak diizinkan menolak pengajuan dari kantor lain.'); } @@ -276,18 +283,29 @@ public function reject(Request $request, $id) return redirect()->back()->with('error', 'Pengajuan Cuti harus ditolak melalui menu Surat Izin.'); } - $izin->update([ - 'id_status' => StatusPengajuan::DITOLAK - ]); + try { + DB::beginTransaction(); - app(NotifikasiService::class)->kirim( - $izin->id_user, - 'izin_ditolak', - 'Pengajuan Izin Ditolak', - 'Pengajuan ' . ($izin->jenisIzin->nama_izin ?? 'Izin') . ' Anda ditolak.', - ['id_izin' => $izin->id_izin] - ); + $izin->update([ + 'id_status' => StatusPengajuan::DITOLAK, + 'alasan_penolakan' => $request->alasan_penolakan, + ]); - return redirect()->back()->with('success', 'Pengajuan izin berhasil ditolak.'); + DB::commit(); + + app(NotifikasiService::class)->kirim( + $izin->id_user, + 'izin_ditolak', + 'Pengajuan Izin Ditolak', + 'Pengajuan ' . ($izin->jenisIzin->nama_izin ?? 'Izin') . ' Anda ditolak. Catatan: ' . ($request->alasan_penolakan ?? '-'), + ['id_izin' => $izin->id_izin] + ); + + return redirect()->back()->with('success', 'Pengajuan izin berhasil ditolak.'); + + } catch (\Exception $e) { + DB::rollBack(); + return redirect()->back()->with('error', 'Gagal menolak pengajuan: ' . $e->getMessage()); + } } } diff --git a/app/Http/Controllers/PenggunaanPoinController.php b/app/Http/Controllers/PenggunaanPoinController.php index a7c864e0..3412c9aa 100644 --- a/app/Http/Controllers/PenggunaanPoinController.php +++ b/app/Http/Controllers/PenggunaanPoinController.php @@ -7,6 +7,7 @@ use App\Services\PoinService; use App\Services\NotifikasiService; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; class PenggunaanPoinController extends Controller @@ -20,10 +21,10 @@ public function __construct(PoinService $poinService) public function index() { - $user = \Illuminate\Support\Facades\Auth::user(); + $user = Auth::user(); $isGlobalAdmin = $user->isGlobalAdmin(); - $query = PenggunaanPoin::with(['user', 'jenisPengurangan', 'status']); + $query = PenggunaanPoin::with(['user.divisi', 'jenisPengurangan', 'status']); if (!$isGlobalAdmin) { $query->whereHas('user', function ($q) use ($user) { @@ -48,7 +49,7 @@ public function update(Request $request, $id) $penggunaan = PenggunaanPoin::findOrFail($id); $penggunaan->load('user'); - $userAuth = \Illuminate\Support\Facades\Auth::user(); + $userAuth = Auth::user(); if (!$userAuth->isGlobalAdmin() && $penggunaan->user->id_kantor != $userAuth->id_kantor) { return redirect()->back()->with('error', 'Anda tidak diizinkan memproses data dari kantor lain.'); } @@ -83,7 +84,7 @@ public function update(Request $request, $id) $penggunaan->id_user, 'poin_disetujui', 'Penggunaan Poin Disetujui', - 'Pengajuan penggunaan poin Anda pada tanggal ' . $penggunaan->tanggal_penggunaan . ' telah disetujui.', + 'Pengajuan penggunaan poin Anda pada tanggal ' . ($penggunaan->tanggal_penggunaan ? $penggunaan->tanggal_penggunaan->translatedFormat('d F Y') : '-') . ' telah disetujui.', ['id_penggunaan' => $penggunaan->id_penggunaan] ); @@ -103,7 +104,7 @@ public function update(Request $request, $id) $penggunaan->id_user, 'poin_ditolak', 'Penggunaan Poin Ditolak', - 'Pengajuan penggunaan poin Anda pada tanggal ' . $penggunaan->tanggal_penggunaan . ' ditolak.', + 'Pengajuan penggunaan poin Anda pada tanggal ' . ($penggunaan->tanggal_penggunaan ? $penggunaan->tanggal_penggunaan->translatedFormat('d F Y') : '-') . ' ditolak. Alasan: ' . ($request->alasan_penolakan ?? '-'), ['id_penggunaan' => $penggunaan->id_penggunaan] ); diff --git a/app/Http/Controllers/PengumumanController.php b/app/Http/Controllers/PengumumanController.php index 0085f058..c4d5867d 100644 --- a/app/Http/Controllers/PengumumanController.php +++ b/app/Http/Controllers/PengumumanController.php @@ -47,7 +47,7 @@ public function store(StorePengumumanRequest $request) 'pengumuman_baru', '📢 Pengumuman Baru', $pengumuman->judul, - ['id_pengumuman' => $pengumuman->id] + ['id_pengumuman' => $pengumuman->id_pengumuman] ); return redirect()->route('pengumuman.index') @@ -86,16 +86,20 @@ public function update(UpdatePengumumanRequest $request, Pengumuman $pengumuman) public function destroy(Pengumuman $pengumuman) { - if ($pengumuman->foto) { - Storage::disk('public')->delete($pengumuman->foto); - } - if ($pengumuman->lampiran) { - Storage::disk('public')->delete($pengumuman->lampiran); - } + try { + if ($pengumuman->foto) { + Storage::disk('public')->delete($pengumuman->foto); + } + if ($pengumuman->lampiran) { + Storage::disk('public')->delete($pengumuman->lampiran); + } - $pengumuman->delete(); + $pengumuman->delete(); - return redirect()->route('pengumuman.index') - ->with('success', 'Pengumuman berhasil dihapus.'); + return redirect()->route('pengumuman.index') + ->with('success', 'Pengumuman berhasil dihapus.'); + } catch (\Exception $e) { + return redirect()->back()->with('error', 'Gagal menghapus pengumuman: ' . $e->getMessage()); + } } } \ No newline at end of file diff --git a/app/Http/Controllers/PresensiController.php b/app/Http/Controllers/PresensiController.php index a765f2ee..0952981d 100644 --- a/app/Http/Controllers/PresensiController.php +++ b/app/Http/Controllers/PresensiController.php @@ -14,6 +14,7 @@ use App\Http\Requests\StoreManualPresensiRequest; use Carbon\Carbon; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; @@ -228,6 +229,7 @@ public function create() $isGlobalAdmin = $user->isGlobalAdmin(); $pegawaiQuery = \App\Models\User::with('divisi') + ->bukanHrd() ->orderBy('nama_lengkap', 'asc'); if (!$isGlobalAdmin) { @@ -312,7 +314,7 @@ public function updateManual(Request $request, $id) 'alasan_telat' => 'required|string|max:255' ], [ 'alasan_telat.required' => 'Catatan koreksi wajib diisi sebagai dokumentasi log.', - 'jam_masuk.required' => 'Jam masuk wajib diisi untuk kehadiran Hadir/Terlambat.' + 'jam_masuk.required' => 'Jam masuk wajib diisi untuk kehadiran Hadir/Terlambat.' ]); $presensi = Presensi::findOrFail($id); @@ -425,7 +427,7 @@ public function store(StorePresensiRequest $request) $faceMessage = null; try { - $faceService = new \App\Services\FaceRecognitionService(); + $faceService = app(\App\Services\FaceRecognitionService::class); $result = $faceService->verifyFace($user->id, $fotoFile); if (isset($result['verified']) && $result['verified'] === true) { @@ -434,6 +436,10 @@ public function store(StorePresensiRequest $request) $faceMessage = $result['message'] ?? null; } catch (\Exception $e) { + Log::warning('Verifikasi wajah masuk gagal', [ + 'user_id' => $user->id, + 'error' => $e->getMessage(), + ]); } $fotoFile->storeAs('uploads/absensi', $imageName, 'public'); @@ -486,12 +492,16 @@ private function handlePulang($request, $user, $hariIni, $jamSekarang) $verifikasiPulang = 0; try { - $faceService = new \App\Services\FaceRecognitionService(); + $faceService = app(\App\Services\FaceRecognitionService::class); $result = $faceService->verifyFace($user->id, $fotoFile); if (isset($result['verified']) && $result['verified'] === true) { $verifikasiPulang = 1; } } catch (\Exception $e) { + Log::warning('Verifikasi wajah pulang gagal', [ + 'user_id' => $user->id, + 'error' => $e->getMessage(), + ]); } $fotoFile->storeAs('uploads/absensi', $imageName, 'public'); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 90c4af1f..e30e62e7 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -2,34 +2,25 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; +use App\Http\Requests\UpdateProfileRequest; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Storage; -use Illuminate\Validation\Rule; class ProfileController extends Controller { public function edit() { - $user = Auth::user(); - return view('profile.edit', compact('user')); + return view('profile.edit'); } - public function update(Request $request) + public function update(UpdateProfileRequest $request) { $user = Auth::user(); - $request->validate([ - 'nama_lengkap' => 'required|string|max:255', - 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], - 'foto' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048', - 'password' => 'nullable|string|min:8|confirmed', - ]); - $data = [ 'nama_lengkap' => $request->nama_lengkap, - 'email' => $request->email, + 'email' => $request->email, ]; if ($request->filled('password')) { diff --git a/app/Http/Controllers/SuratIzinController.php b/app/Http/Controllers/SuratIzinController.php index e78aceaa..a5db0d07 100644 --- a/app/Http/Controllers/SuratIzinController.php +++ b/app/Http/Controllers/SuratIzinController.php @@ -181,7 +181,7 @@ public function approve(Request $request, $id) public function reject(Request $request, $id) { try { - $surat = SuratIzin::with('user')->findOrFail($id); + $surat = SuratIzin::with(['user', 'pengajuanIzin'])->findOrFail($id); $user = Auth::user(); $isGlobalAdmin = $user->isGlobalAdmin(); diff --git a/app/Http/Controllers/TukarShiftController.php b/app/Http/Controllers/TukarShiftController.php index ddc23ebd..e496cceb 100644 --- a/app/Http/Controllers/TukarShiftController.php +++ b/app/Http/Controllers/TukarShiftController.php @@ -18,18 +18,26 @@ class TukarShiftController extends Controller public function index() { - $riwayat = RiwayatTukarShift::with(['user1', 'user2', 'jadwal1.shift', 'jadwal2.shift', 'execAdmin']) - ->latest() - ->get(); + $user = Auth::user(); + + $query = RiwayatTukarShift::with(['user1', 'user2', 'jadwal1.shift', 'jadwal2.shift', 'execAdmin']) + ->latest(); + + if (!$user->isGlobalAdmin()) { + $query->where(function ($q) use ($user) { + $q->whereHas('user1', fn($u) => $u->where('id_kantor', $user->id_kantor)) + ->orWhereHas('user2', fn($u) => $u->where('id_kantor', $user->id_kantor)); + }); + } + + $riwayat = $query->get(); return view('tukar-shift.index', compact('riwayat')); } public function create() { - $pegawai = User::with('kantor')->whereDoesntHave('roles', function ($q) { - $q->where('nama_role', 'hrd'); - })->get(); + $pegawai = User::with('kantor')->bukanHrd()->get(); $kantor = \App\Models\Kantor::orderBy('nama_kantor', 'asc')->get(); diff --git a/app/Http/Requests/BulkDestroyJadwalRequest.php b/app/Http/Requests/BulkDestroyJadwalRequest.php new file mode 100644 index 00000000..fbf95e41 --- /dev/null +++ b/app/Http/Requests/BulkDestroyJadwalRequest.php @@ -0,0 +1,35 @@ + ['required', 'date'], + 'tanggal_selesai' => ['required', 'date', 'after_or_equal:tanggal_mulai'], + 'user_ids' => ['required', 'array', 'min:1'], + 'user_ids.*' => ['exists:users,id'], + ]; + } + + public function messages(): array + { + return [ + 'tanggal_mulai.required' => 'Tanggal mulai wajib diisi.', + 'tanggal_selesai.required' => 'Tanggal selesai wajib diisi.', + 'tanggal_selesai.after_or_equal' => 'Tanggal selesai tidak boleh sebelum tanggal mulai.', + 'user_ids.required' => 'Pilih minimal 1 pegawai.', + 'user_ids.min' => 'Pilih minimal 1 pegawai.', + 'user_ids.*.exists' => 'Salah satu pegawai yang dipilih tidak valid.', + ]; + } +} diff --git a/app/Http/Requests/StoreHariLiburRequest.php b/app/Http/Requests/StoreHariLiburRequest.php index 7e4786a6..4447336e 100644 --- a/app/Http/Requests/StoreHariLiburRequest.php +++ b/app/Http/Requests/StoreHariLiburRequest.php @@ -9,18 +9,15 @@ class StoreHariLiburRequest extends FormRequest public function authorize(): bool { - return false; + return true; } - /** - * Get the validation rules that apply to the request. - * - * @return array|string> - */ public function rules(): array { return [ - + 'tanggal' => ['required', 'date'], + 'keterangan' => ['required', 'string', 'max:255'], + 'id_kantor' => ['nullable', 'exists:kantor,id_kantor'], ]; } } diff --git a/app/Http/Requests/StoreJadwalRequest.php b/app/Http/Requests/StoreJadwalRequest.php new file mode 100644 index 00000000..942d7719 --- /dev/null +++ b/app/Http/Requests/StoreJadwalRequest.php @@ -0,0 +1,38 @@ + ['required', 'date'], + 'tanggal_selesai' => ['required', 'date', 'after_or_equal:tanggal_mulai'], + 'id_shift' => ['required', 'exists:shift_kerja,id_shift'], + 'user_ids' => ['required', 'array', 'min:1'], + 'user_ids.*' => ['exists:users,id'], + ]; + } + + public function messages(): array + { + return [ + 'tanggal_mulai.required' => 'Tanggal mulai wajib diisi.', + 'tanggal_selesai.required' => 'Tanggal selesai wajib diisi.', + 'tanggal_selesai.after_or_equal' => 'Tanggal selesai tidak boleh sebelum tanggal mulai.', + 'id_shift.required' => 'Shift kerja wajib dipilih.', + 'id_shift.exists' => 'Shift yang dipilih tidak valid.', + 'user_ids.required' => 'Pilih minimal 1 pegawai.', + 'user_ids.min' => 'Pilih minimal 1 pegawai.', + 'user_ids.*.exists' => 'Salah satu pegawai yang dipilih tidak valid.', + ]; + } +} diff --git a/app/Http/Requests/StorePegawaiRequest.php b/app/Http/Requests/StorePegawaiRequest.php index 1766e22f..d139fa51 100644 --- a/app/Http/Requests/StorePegawaiRequest.php +++ b/app/Http/Requests/StorePegawaiRequest.php @@ -17,7 +17,8 @@ public function rules(): array 'nama_lengkap' => ['required', 'string', 'max:255'], 'nik' => ['nullable', 'string', 'max:50', 'unique:users,nik'], 'email' => ['required', 'email', 'unique:users,email'], - 'no_telp' => ['nullable', 'string', 'max:20'], + 'no_telp' => + ['nullable', 'string', 'max:15', 'regex:/^(\+62|62|0)[0-9]{8,11}$/'], 'alamat' => ['nullable', 'string'], 'id_divisi' => ['required', 'exists:divisi,id_divisi'], 'id_jabatan' => ['required', 'exists:jabatan,id_jabatan'], @@ -32,10 +33,13 @@ public function rules(): array public function messages(): array { return [ - 'nik.unique' => 'NIK sudah terdaftar di sistem.', - 'email.unique' => 'Email ini sudah digunakan oleh pegawai lain.', + 'nik.unique' => 'NIK sudah terdaftar di sistem.', + 'email.unique' => 'Email ini sudah digunakan oleh pegawai lain.', 'id_divisi.exists' => 'Divisi yang dipilih tidak valid.', - 'id_role.exists' => 'Role yang dipilih tidak valid.', + 'id_role.exists' => 'Role yang dipilih tidak valid.', + 'no_telp.regex' => 'Format nomor telepon tidak valid. Gunakan format: 08xx, +62xx, atau 62xx.', + 'foto.max' => 'Ukuran foto tidak boleh lebih dari 5MB.', + 'sisa_cuti.min' => 'Sisa cuti tidak boleh negatif.', ]; } } \ No newline at end of file diff --git a/app/Http/Requests/StorePengajuanIzinRequest.php b/app/Http/Requests/StorePengajuanIzinRequest.php index 51cf9969..be39a121 100644 --- a/app/Http/Requests/StorePengajuanIzinRequest.php +++ b/app/Http/Requests/StorePengajuanIzinRequest.php @@ -20,11 +20,12 @@ public function authorize(): bool public function rules(): array { return [ - 'tanggal_mulai' => 'required|date|after_or_equal:today', - 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', - 'id_jenis_izin' => 'required|exists:jenis_izin,id_jenis_izin', - 'alasan' => 'required|string|max:150', - 'bukti_file' => 'nullable|file|mimes:pdf,jpg,jpeg,png|max:2048', + 'id_user' => 'required|exists:users,id', + 'id_jenis_izin' => 'required|exists:jenis_izin,id_jenis_izin', + 'tanggal_mulai' => 'required|date', + 'tanggal_selesai'=> 'required|date|after_or_equal:tanggal_mulai', + 'alasan' => 'required|string|max:500', + 'bukti_file' => 'nullable|file|mimes:pdf,jpg,jpeg,png|max:2048', ]; } @@ -36,10 +37,12 @@ public function rules(): array public function attributes() { return [ - 'tanggal_mulai' => 'tanggal mulai', + 'id_user' => 'pegawai', + 'id_jenis_izin' => 'jenis izin', + 'tanggal_mulai' => 'tanggal mulai', 'tanggal_selesai' => 'tanggal selesai', - 'id_jenis_izin' => 'jenis izin', - 'bukti_file' => 'bukti file', + 'alasan' => 'alasan', + 'bukti_file' => 'bukti file', ]; } diff --git a/app/Http/Requests/UpdatePegawaiRequest.php b/app/Http/Requests/UpdatePegawaiRequest.php index 520e0a45..ba9a67a4 100644 --- a/app/Http/Requests/UpdatePegawaiRequest.php +++ b/app/Http/Requests/UpdatePegawaiRequest.php @@ -22,18 +22,34 @@ public function rules(): array 'email' => [ 'required', 'email', - 'unique:users,email,' . $this->route('pegawai') + Rule::unique('users', 'email')->ignore($id), ], - 'no_telp' => ['nullable', 'string', 'max:20'], - 'alamat' => ['nullable', 'string'], - 'id_divisi' => ['required', 'exists:divisi,id_divisi'], - 'id_jabatan' => ['required', 'exists:jabatan,id_jabatan'], - 'id_kantor' => ['required', 'exists:kantor,id_kantor'], + 'no_telp' => ['nullable', 'string', 'max:15', 'regex:/^(\+62|62|0)[0-9]{8,11}$/'], + 'alamat' => ['nullable', 'string'], + 'id_divisi' => ['required', 'exists:divisi,id_divisi'], + 'id_jabatan' => ['required', 'exists:jabatan,id_jabatan'], + 'id_kantor' => ['required', 'exists:kantor,id_kantor'], 'status_aktif' => ['required', 'in:0,1'], 'tgl_bergabung' => ['required', 'date'], - 'foto' => ['nullable', 'image', 'max:5120'], - 'sisa_cuti' => ['nullable', 'integer', 'min:0'], - 'id_role' => ['required', 'exists:roles,id_role'], + 'foto' => ['nullable', 'image', 'max:5120'], + 'sisa_cuti' => ['nullable', 'integer', 'min:0'], + 'id_role' => ['required', 'exists:roles,id_role'], + ]; + } + + public function messages(): array + { + return [ + 'nik.unique' => 'NIK sudah terdaftar di sistem.', + 'email.unique' => 'Email ini sudah digunakan oleh pegawai lain.', + 'id_divisi.exists' => 'Divisi yang dipilih tidak valid.', + 'id_jabatan.exists' => 'Jabatan yang dipilih tidak valid.', + 'id_kantor.exists' => 'Kantor yang dipilih tidak valid.', + 'id_role.exists' => 'Role yang dipilih tidak valid.', + 'status_aktif.in' => 'Status pegawai tidak valid.', + 'foto.max' => 'Ukuran foto tidak boleh lebih dari 5MB.', + 'sisa_cuti.min' => 'Sisa cuti tidak boleh negatif.', + 'no_telp.regex' => 'Format nomor telepon tidak valid. Gunakan format: 08xx, +62xx, atau 62xx.', ]; } } \ No newline at end of file diff --git a/app/Http/Requests/UpdateProfileRequest.php b/app/Http/Requests/UpdateProfileRequest.php new file mode 100644 index 00000000..2191d480 --- /dev/null +++ b/app/Http/Requests/UpdateProfileRequest.php @@ -0,0 +1,35 @@ + ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore(Auth::id())], + 'foto' => ['nullable', 'image', 'mimes:jpeg,png,jpg', 'max:2048'], + 'password' => ['nullable', 'string', 'min:8', 'confirmed'], + ]; + } + + public function attributes(): array + { + return [ + 'nama_lengkap' => 'Nama Lengkap', + 'email' => 'Alamat Email', + 'foto' => 'Foto Profil', + 'password' => 'Password Baru', + ]; + } +} diff --git a/app/Jobs/RetrainAllModels.php b/app/Jobs/RetrainAllModels.php index 0788b809..5c0a7d73 100644 --- a/app/Jobs/RetrainAllModels.php +++ b/app/Jobs/RetrainAllModels.php @@ -5,7 +5,9 @@ use App\Enums\StatusVerifikasiWajah; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Queue\Queueable as QueueableTrait; +use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; @@ -13,7 +15,7 @@ class RetrainAllModels implements ShouldQueue { - use Queueable; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $timeout = 3600; diff --git a/app/Models/PengajuanIzin.php b/app/Models/PengajuanIzin.php index c8295817..858a030c 100644 --- a/app/Models/PengajuanIzin.php +++ b/app/Models/PengajuanIzin.php @@ -65,7 +65,7 @@ protected static function booted() 'tanggal_selesai', 'alasan', 'bukti_file', - + 'alasan_penolakan', ]; public function user() diff --git a/app/Models/User.php b/app/Models/User.php index 3163acf5..d058f8eb 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -156,4 +156,11 @@ public function notifikasi(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Notifikasi::class, 'id_user', 'id'); } + + public function scopeBukanHrd($query) + { + return $query->whereDoesntHave('roles', function ($q) { + $q->where('nama_role', 'hrd'); + }); + } } diff --git a/app/Services/FaceRecognitionService.php b/app/Services/FaceRecognitionService.php index 81e1a6a2..1c70e51d 100644 --- a/app/Services/FaceRecognitionService.php +++ b/app/Services/FaceRecognitionService.php @@ -130,13 +130,12 @@ public function verifyFace($userId, UploadedFile $fotoMasuk) } return [ - 'verified' => ($output['match'] ?? false) === true, - 'confidence' => $output['confidence'] ?? null, - 'svm_confidence' => $output['svm_confidence'] ?? null, - 'normalized_distance' => $output['normalized_distance'] ?? null, + 'verified' => ($output['match'] ?? false) === true, + 'confidence' => $output['confidence'] ?? null, + 'svm_confidence' => $output['svm_confidence'] ?? null, 'verification_status' => $output['verification_status'] ?? 'UNKNOWN', - 'blur_score' => $output['blur_score'] ?? null, - 'message' => $output['message'] ?? null, + 'blur_score' => $output['blur_score'] ?? null, + 'message' => $output['message'] ?? null, ]; } } diff --git a/app/Services/PoinService.php b/app/Services/PoinService.php index d8051717..bc122232 100644 --- a/app/Services/PoinService.php +++ b/app/Services/PoinService.php @@ -48,12 +48,6 @@ public function deductPoin($userId, $jumlahPoinDibutuhkan, $idPenggunaan) $remainingNeeded -= $take; } - - $penggunaan = PenggunaanPoin::find($idPenggunaan); - if ($penggunaan) { - $penggunaan->id_status = StatusPengajuan::DISETUJUI; - $penggunaan->save(); - } } public function refundPoin($idPenggunaan) @@ -70,14 +64,9 @@ public function refundPoin($idPenggunaan) } $poinSumber->save(); } - $detail->delete(); } - $penggunaan = PenggunaanPoin::find($idPenggunaan); - if ($penggunaan) { - $penggunaan->id_status = StatusPengajuan::DITOLAK; - $penggunaan->save(); - } + DetailPenggunaanPoin::where('id_penggunaan', $idPenggunaan)->delete(); } public function getActivePoints($userId) diff --git a/database/migrations/2026_03_31_193339_create_log_verifikasi_wajah_table.php b/database/migrations/2026_03_31_193339_create_log_verifikasi_wajah_table.php index a817fc81..a9f877ae 100644 --- a/database/migrations/2026_03_31_193339_create_log_verifikasi_wajah_table.php +++ b/database/migrations/2026_03_31_193339_create_log_verifikasi_wajah_table.php @@ -15,12 +15,12 @@ public function up(): void Schema::create('log_verifikasi_wajah', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('id_user'); - $table->decimal('confidence', 5, 4)->nullable(); - $table->decimal('svm_confidence', 5, 4)->nullable(); - $table->decimal('normalized_distance', 8, 4)->nullable(); - $table->string('verification_status', 30)->nullable(); - $table->boolean('is_match')->default(false); - $table->decimal('blur_score', 6, 1)->nullable(); + $table->decimal('skor_kepercayaan', 5, 4)->nullable(); + $table->decimal('skor_svm', 5, 4)->nullable(); + $table->decimal('jarak_normalisasi', 8, 4)->nullable(); + $table->string('status_verifikasi', 30)->nullable(); + $table->boolean('apakah_cocok')->default(false); + $table->decimal('skor_blur', 6, 1)->nullable(); $table->string('tipe', 10)->default('presensi'); $table->timestamps(); diff --git a/database/seeders/PengajuanIzinSeeder.php b/database/seeders/PengajuanIzinSeeder.php index adf271bd..43889ac5 100644 --- a/database/seeders/PengajuanIzinSeeder.php +++ b/database/seeders/PengajuanIzinSeeder.php @@ -27,7 +27,10 @@ public function run(): void $tglMulai = now()->addDays(rand(-10, 10)); $tglSelesai = (clone $tglMulai)->addDays(rand(1, 3)); + $idIzin = 'IZN-' . date('ym') . '-' . strtoupper(Str::random(5)); + DB::table('pengajuan_izin')->insertOrIgnore([ + 'id_izin' => $idIzin, 'id_user' => $user->id, 'id_jenis_izin' => $idJenis, 'tanggal_mulai' => $tglMulai->format('Y-m-d'), diff --git a/public/js/notifications.js b/public/js/notifications.js index c8854ad6..b14555e7 100644 --- a/public/js/notifications.js +++ b/public/js/notifications.js @@ -76,13 +76,17 @@ // ==== Polling Badge Notifikasi ==== function updateNotifBadge() { const badge = document.getElementById('notif-badge'); - if (!badge) return; + if (!badge) return; // User tidak punya akses notifikasi, hentikan tanpa fetch fetch('/notifikasi/unread-count', { headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } }) - .then(r => r.json()) + .then(r => { + if (!r.ok) return null; // Tangani 403/redirect dengan aman + return r.json(); + }) .then(data => { + if (!data) return; const count = data.count ?? 0; if (count > 0) { badge.textContent = count > 99 ? '99+' : count; @@ -215,7 +219,7 @@ function confirmDelete(id) { } } }); - } +} function confirmAction(event, formId, message, confirmBtnColor = '#3085d6', confirmBtnText = 'Ya, lanjutkan!') { event.preventDefault(); diff --git a/python_scripts/requirements.txt b/python_scripts/requirements.txt index 2fcb076b..6f11d0c5 100644 --- a/python_scripts/requirements.txt +++ b/python_scripts/requirements.txt @@ -1,6 +1,5 @@ opencv-contrib-python-headless numpy scikit-learn -scikit-image joblib Pillow diff --git a/python_scripts/verify_face_svm.py b/python_scripts/verify_face_svm.py index f50dc6d3..4788e546 100644 --- a/python_scripts/verify_face_svm.py +++ b/python_scripts/verify_face_svm.py @@ -199,7 +199,7 @@ def verify_face(model_dir, user_id, image_path): "expected_user": expected_user, "unknown_confidence": round(unknown_confidence, 4), "blur_score": round(blur_score, 1), - "user_id": int(user_id) + "user_id": int(user_id) if str(user_id).isdigit() else user_id } print(json.dumps(result)) diff --git a/resources/views/components/date-input.blade.php b/resources/views/components/date-input.blade.php index 2d1031d3..1626fb75 100644 --- a/resources/views/components/date-input.blade.php +++ b/resources/views/components/date-input.blade.php @@ -1,28 +1,29 @@ -@props(['label' => null, 'name', 'value' => '']) +@props(['label' => null, 'name', 'value' => '']) + +@once + +@endonce
@if($label) @endif - -
- - + merge(['class' => 'w-full pl-4 pr-10 py-2.5 bg-white border border-slate-300 rounded-xl text-sm outline-none focus:ring-2 focus:ring-[#130F26] focus:border-[#130F26] transition-all text-slate-600 cursor-pointer appearance-none']) }} > - +
diff --git a/resources/views/cuti/index.blade.php b/resources/views/cuti/index.blade.php index 90eea139..b1aa2812 100644 --- a/resources/views/cuti/index.blade.php +++ b/resources/views/cuti/index.blade.php @@ -138,10 +138,11 @@ function toggleResetBtn(value) { function openEditModal(id, nama, sisa) { document.getElementById('editNama').value = nama; document.getElementById('editSisa').value = sisa; - + let form = document.getElementById('formEditCuti'); - form.action = `/cuti/${id}`; - + let baseUrl = "{{ route('cuti.update', ':id') }}"; + form.action = baseUrl.replace(':id', id); + window.dispatchEvent(new CustomEvent('open-modal', { detail: 'edit-cuti' })); } diff --git a/resources/views/dashboard/index.blade.php b/resources/views/dashboard/index.blade.php index e368e826..ba21821f 100644 --- a/resources/views/dashboard/index.blade.php +++ b/resources/views/dashboard/index.blade.php @@ -224,7 +224,7 @@ class="text-xs text-primary hover:underline font-medium">Lihat
@endsection -@push('scripts') +@section('script') -@endpush +@endsection diff --git a/resources/views/divisi/index.blade.php b/resources/views/divisi/index.blade.php index a7fd9780..00b7ae6e 100644 --- a/resources/views/divisi/index.blade.php +++ b/resources/views/divisi/index.blade.php @@ -1,7 +1,25 @@ -@extends('layouts.app') +@extends('layouts.app') @section('title', 'Data Divisi') +@section('script') + +@endsection + @section('content')
@@ -84,12 +102,7 @@ class="px-4 py-2 text-sm font-medium text-white bg-primary rounded-xl hover:bg-p -
- - - -
+
@@ -154,6 +293,9 @@ +@endsection + +@section('script') +@endsection + @section('content')
@@ -80,11 +98,7 @@ class="px-4 py-2 text-sm font-medium text-white bg-primary rounded-xl hover:bg-p -
- - -
+
-
Batal - Ya, Hapus Terpilih + Ya, Hapus Terpilih
@@ -469,7 +469,7 @@ function initCalendar() { window.jadwalCalendar = new FullCalendar.Calendar(calendarEl, { initialView: 'dayGridMonth', locale: 'id', - initialDate: '2026-01-01', + initialDate: new Date().toISOString().split('T')[0], headerToolbar: { left: 'prev,next today', center: 'title', @@ -983,40 +983,44 @@ function deleteJadwal() { } } }); - selectAllCheckbox.addEventListener('change', function () { - const visibleCheckboxes = document.querySelectorAll('.user-checkbox'); - visibleCheckboxes.forEach(cb => cb.checked = this.checked); - }); - document.addEventListener('DOMContentLoaded', function() { - const forms = document.querySelectorAll('form'); - - forms.forEach(form => { - const inputMulai = form.querySelector('input[name="tanggal_mulai"]'); - const inputSelesai = form.querySelector('input[name="tanggal_selesai"]'); - - if (inputMulai && inputSelesai) { - inputMulai.addEventListener('change', function() { - if (this.value) { - inputSelesai.min = this.value; - if (inputSelesai.value && inputSelesai.value < this.value) { - inputSelesai.value = this.value; - } - } - }); - inputSelesai.addEventListener('change', function() { - if (this.value) { - inputMulai.max = this.value; - if (inputMulai.value && inputMulai.value > this.value) { - inputMulai.value = this.value; - } - } - }); - if (inputMulai.value) inputSelesai.min = inputMulai.value; - if (inputSelesai.value) inputMulai.max = inputSelesai.value; + function confirmBulkDelete() { + const form = document.getElementById('form-bulk-delete'); + if (!form) return; + + const checkedBoxes = form.querySelectorAll('.user-checkbox:checked'); + const tglMulai = form.querySelector('input[name="tanggal_mulai"]')?.value; + const tglSelesai = form.querySelector('input[name="tanggal_selesai"]')?.value; + + if (checkedBoxes.length === 0) { + Swal.fire('Peringatan', 'Pilih minimal 1 pegawai untuk dihapus jadwalnya.', 'warning'); + return; + } + if (!tglMulai || !tglSelesai) { + Swal.fire('Peringatan', 'Tanggal mulai dan selesai wajib diisi.', 'warning'); + return; + } + + const formatTgl = (d) => { + const obj = new Date(d); + return obj.toLocaleDateString('id-ID', { day: 'numeric', month: 'long', year: 'numeric' }); + }; + + Swal.fire({ + title: 'Konfirmasi Hapus Massal', + html: `Anda akan menghapus jadwal ${checkedBoxes.length} pegawai pada rentang:
${formatTgl(tglMulai)} — ${formatTgl(tglSelesai)}

Tindakan ini tidak dapat dibatalkan!`, + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#ef4444', + cancelButtonColor: '#64748b', + confirmButtonText: 'Ya, Hapus Sekarang', + cancelButtonText: 'Batal' + }).then((result) => { + if (result.isConfirmed) { + form.submit(); } }); - }); + } @endsection diff --git a/resources/views/jadwal/test_isolation.blade.php b/resources/views/jadwal/test_isolation.blade.php deleted file mode 100644 index b6d10e9e..00000000 --- a/resources/views/jadwal/test_isolation.blade.php +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - -

Test Calendar Isolation

-
- - - diff --git a/resources/views/laporan-lembur/index.blade.php b/resources/views/laporan-lembur/index.blade.php index 3b94d55f..394f385a 100644 --- a/resources/views/laporan-lembur/index.blade.php +++ b/resources/views/laporan-lembur/index.blade.php @@ -8,7 +8,7 @@
- + Export Excel diff --git a/resources/views/laporan/index.blade.php b/resources/views/laporan/index.blade.php index 483c680f..d5f612a2 100644 --- a/resources/views/laporan/index.blade.php +++ b/resources/views/laporan/index.blade.php @@ -15,7 +15,7 @@ class="flex flex-wrap gap-4 items-end mb-6 border-b border-slate-100 pb-6"> @for ($i = 1; $i <= 12; $i++) @endfor @@ -130,7 +130,7 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-red-50 text-red-700 hover:bg- @empty - + @endforelse
diff --git a/resources/views/laporan/izin.blade.php b/resources/views/laporan/izin.blade.php index 5223c371..b157133c 100644 --- a/resources/views/laporan/izin.blade.php +++ b/resources/views/laporan/izin.blade.php @@ -2,6 +2,8 @@ @section('title', 'Laporan Izin & Cuti') +@php use App\Enums\StatusPengajuan; @endphp + @section('content')
@@ -10,6 +12,7 @@
Export Excel @@ -90,7 +93,7 @@ class="!mb-0" oninput="if(this.value.length === 0) this.form.submit()" /> @if($izin->tanggal_mulai == $izin->tanggal_selesai) {{ \Carbon\Carbon::parse($izin->tanggal_mulai)->translatedFormat('d M Y') }} @else - {{ \Carbon\Carbon::parse($izin->tanggal_mulai)->format('d M') }} - {{ \Carbon\Carbon::parse($izin->tanggal_selesai)->translatedFormat('d M Y') }} + {{ \Carbon\Carbon::parse($izin->tanggal_mulai)->translatedFormat('d M') }} - {{ \Carbon\Carbon::parse($izin->tanggal_selesai)->translatedFormat('d M Y') }} @endif @@ -104,8 +107,8 @@ class="!mb-0" oninput="if(this.value.length === 0) this.form.submit()" /> @php $color = 'yellow'; $label = 'Menunggu'; - if($izin->id_status == 2) { $color = 'green'; $label = 'Disetujui'; } - elseif($izin->id_status == 3) { $color = 'red'; $label = 'Ditolak'; } + if($izin->id_status == StatusPengajuan::DISETUJUI) { $color = 'green'; $label = 'Disetujui'; } + elseif($izin->id_status == StatusPengajuan::DITOLAK) { $color = 'red'; $label = 'Ditolak'; } @endphp {{ $label }} diff --git a/resources/views/lembur/create.blade.php b/resources/views/lembur/create.blade.php index 901b2281..4cefa92a 100644 --- a/resources/views/lembur/create.blade.php +++ b/resources/views/lembur/create.blade.php @@ -1,4 +1,4 @@ -@extends('layouts.app') +@extends('layouts.app') @section('title', 'Input Lembur Manual') @@ -32,7 +32,7 @@ @foreach($kompensasi as $k) @endforeach diff --git a/resources/views/lembur/index.blade.php b/resources/views/lembur/index.blade.php index 0f304680..a7fd174d 100644 --- a/resources/views/lembur/index.blade.php +++ b/resources/views/lembur/index.blade.php @@ -45,7 +45,7 @@ {{ $item->durasi_menit }} Menit - @if($item->id_kompensasi == 2) + @if($item->id_kompensasi == \App\Enums\JenisKompensasi::TAMBAHAN_POIN) ⭐ Poin @else 💵 Uang @@ -56,12 +56,11 @@ @php - $status = strtolower($item->status->nama_status ?? '-'); $idStatus = $item->id_status; $badgeColor = match ($idStatus) { - 1 => 'yellow', - 2 => 'green', - 3 => 'red', + \App\Enums\StatusPengajuan::PENDING => 'yellow', + \App\Enums\StatusPengajuan::DISETUJUI => 'green', + \App\Enums\StatusPengajuan::DITOLAK => 'red', default => 'gray' }; @endphp @@ -70,9 +69,9 @@ - @if($item->id_status == 1) + @if($item->id_status == \App\Enums\StatusPengajuan::PENDING)
- +
@csrf @method('PUT') diff --git a/resources/views/pegawai/create.blade.php b/resources/views/pegawai/create.blade.php index f293b15c..59ac5a59 100644 --- a/resources/views/pegawai/create.blade.php +++ b/resources/views/pegawai/create.blade.php @@ -33,7 +33,7 @@ class="grid grid-cols-1 lg:grid-cols-3 gap-6"> required />
- +

Penempatan Kerja

- + @foreach ($divisi as $d) @@ -60,7 +60,7 @@ class="grid grid-cols-1 lg:grid-cols-3 gap-6">
- + @foreach ($jabatan as $j) @@ -69,7 +69,7 @@ class="grid grid-cols-1 lg:grid-cols-3 gap-6">
- + @foreach ($kantor as $k) @@ -78,7 +78,7 @@ class="grid grid-cols-1 lg:grid-cols-3 gap-6">
- + @foreach ($roles as $r) @@ -86,7 +86,7 @@ class="grid grid-cols-1 lg:grid-cols-3 gap-6">
-
+
diff --git a/resources/views/pegawai/edit.blade.php b/resources/views/pegawai/edit.blade.php index 79ba8b46..afaae888 100644 --- a/resources/views/pegawai/edit.blade.php +++ b/resources/views/pegawai/edit.blade.php @@ -32,7 +32,7 @@ class="grid grid-cols-1 lg:grid-cols-3 gap-6">
- +

Penempatan & Status

- + @foreach ($divisi as $d) - + @foreach ($jabatan as $j) - + @foreach ($kantor as $k) - + - + @foreach ($roles as $r)