From 89c7fc14b8a145c8ce3699effd90c66094abf8e7 Mon Sep 17 00:00:00 2001 From: Endyfadlullah Date: Fri, 29 Aug 2025 13:16:14 +0700 Subject: [PATCH] final --- app/Http/Controllers/AdminController.php | 261 +++++++-- app/Http/Controllers/DashboardController.php | 83 ++- app/Http/Controllers/DisplayController.php | 102 ++-- app/Models/Antrian.php | 10 +- app/Models/Loket.php | 19 - app/Providers/AppServiceProvider.php | 5 +- composer.json | 1 - composer.lock | 70 +-- config/auth.php | 27 +- config/sanctum.php | 84 --- .../2024_01_01_000003_create_lokets_table.php | 27 - ...03_create_personal_access_tokens_table.php | 32 -- .../2025_08_10_160027_create_jobs_table.php | 32 -- ...ang_diperiksa_status_to_antrians_table.php | 42 ++ ..._014038_cleanup_unused_database_tables.php | 92 ++++ ...15356_drop_password_reset_tokens_table.php | 30 ++ database/seeders/AntrianPuskesmasSeeder.php | 102 +++- database/seeders/LoketSeeder.php | 22 - .../views/admin/antrian/tambah.blade.php | 498 ++++++++++-------- resources/views/admin/dashboard.blade.php | 357 +++++++++---- resources/views/admin/laporan/index.blade.php | 167 ++---- resources/views/admin/laporan/pdf.blade.php | 11 +- .../admin/partials/sidebar-script.blade.php | 140 +++-- .../views/admin/partials/sidebar.blade.php | 26 +- .../views/admin/partials/top-nav.blade.php | 34 +- resources/views/admin/poli/index.blade.php | 365 +++++++------ resources/views/admin/users/create.blade.php | 120 +---- resources/views/admin/users/show.blade.php | 144 ++++- resources/views/auth/login.blade.php | 38 +- resources/views/auth/register.blade.php | 3 +- .../views/custom/pagination-simple.blade.php | 54 ++ resources/views/custom/pagination.blade.php | 147 ++++++ resources/views/dashboard/index.blade.php | 52 +- resources/views/display/index.blade.php | 49 +- resources/views/landing.blade.php | 198 ++++--- routes/web.php | 5 + 36 files changed, 2171 insertions(+), 1278 deletions(-) delete mode 100644 app/Models/Loket.php delete mode 100644 config/sanctum.php delete mode 100644 database/migrations/2024_01_01_000003_create_lokets_table.php delete mode 100644 database/migrations/2025_08_08_092603_create_personal_access_tokens_table.php delete mode 100644 database/migrations/2025_08_10_160027_create_jobs_table.php create mode 100644 database/migrations/2025_08_14_004750_add_sedang_diperiksa_status_to_antrians_table.php create mode 100644 database/migrations/2025_08_14_014038_cleanup_unused_database_tables.php create mode 100644 database/migrations/2025_08_14_015356_drop_password_reset_tokens_table.php delete mode 100644 database/seeders/LoketSeeder.php create mode 100644 resources/views/custom/pagination-simple.blade.php create mode 100644 resources/views/custom/pagination.blade.php diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 9c88150..186e171 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -49,7 +49,7 @@ public function dashboard() // Get recent antrian $antrianTerbaru = Antrian::with(['user', 'poli']) ->orderBy('created_at', 'desc') - ->limit(10) + ->limit(3) ->get(); return view('admin.dashboard', compact( @@ -65,6 +65,56 @@ public function dashboard() )); } + public function dashboardApi() + { + // Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit + $this->autoBatalkanAntrianLama(); + + // Get counts for each poli + $poliUmumCount = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'umum'); + })->where('status', 'menunggu')->count(); + + $poliGigiCount = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'gigi'); + })->where('status', 'menunggu')->count(); + + $poliJiwaCount = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'kesehatan jiwa'); + })->where('status', 'menunggu')->count(); + + $poliTradisionalCount = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'kesehatan tradisional'); + })->where('status', 'menunggu')->count(); + + // Get recent antrian + $antrianTerbaru = Antrian::with(['user', 'poli']) + ->orderBy('created_at', 'desc') + ->limit(3) + ->get(); + + return response()->json([ + 'success' => true, + 'data' => [ + 'poliUmumCount' => $poliUmumCount, + 'poliGigiCount' => $poliGigiCount, + 'poliJiwaCount' => $poliJiwaCount, + 'poliTradisionalCount' => $poliTradisionalCount, + 'antrianTerbaru' => $antrianTerbaru->map(function ($antrian) { + return [ + 'id' => $antrian->id, + 'no_antrian' => $antrian->no_antrian, + 'status' => $antrian->status, + 'poli_name' => $antrian->poli->nama_poli, + 'user_name' => $antrian->user->nama, + 'created_at' => $antrian->created_at->diffForHumans(), + 'updated_at' => $antrian->updated_at->diffForHumans(), + ]; + }) + ] + ]); + } + public function manageUsers(Request $request) { // Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit @@ -91,10 +141,39 @@ public function manageUsers(Request $request) public function showUser(User $user) { - $user->load(['antrians.poli']); + // Load user with antrians ordered by created_at desc to get the latest first + $user->load([ + 'antrians' => function ($query) { + $query->with('poli')->orderBy('created_at', 'desc'); + } + ]); + return view('admin.users.show', compact('user')); } + public function getUserAntrianTerbaru(User $user) + { + // Get latest 5 antrians for the user + $antrians = $user->antrians() + ->with('poli') + ->orderBy('created_at', 'desc') + ->take(5) + ->get(); + + return response()->json([ + 'success' => true, + 'antrians' => $antrians->map(function ($antrian) { + return [ + 'id' => $antrian->id, + 'no_antrian' => $antrian->no_antrian, + 'poli_name' => $antrian->poli->nama_poli ?? 'N/A', + 'status' => $antrian->status, + 'created_at' => $antrian->created_at ? $antrian->created_at->format('d/m/Y H:i') : 'N/A' + ]; + }) + ]); + } + public function createUser() { return view('admin.users.create'); @@ -213,7 +292,12 @@ public function laporan(Request $request) // Filter berdasarkan status if ($request->filled('status')) { - $query->where('status', $request->status); + $status = $request->status; + // Convert 'sedang' to 'sedang_diperiksa' for database compatibility + if ($status === 'sedang') { + $status = 'sedang_diperiksa'; + } + $query->where('status', $status); } // Filter berdasarkan jenis kelamin @@ -223,17 +307,23 @@ public function laporan(Request $request) }); } - $antrian = $query->orderBy('created_at', 'desc')->get(); + // Clone query for statistics before pagination + $statisticQuery = clone $query; + $allData = $statisticQuery->get(); + + // Paginate the results + $antrian = $query->orderBy('created_at', 'desc')->paginate(10); $polis = Poli::all(); - // Statistik - $totalAntrian = $antrian->count(); - $antrianSelesai = $antrian->where('status', 'selesai')->count(); - $antrianMenunggu = $antrian->where('status', 'menunggu')->count(); - $antrianDipanggil = $antrian->where('status', 'dipanggil')->count(); - $antrianSedang = $antrian->where('status', 'sedang')->count(); + // Statistik dari semua data (tidak hanya yang dipaginate) + $totalAntrian = $allData->count(); + $antrianSelesai = $allData->where('status', 'selesai')->count(); + $antrianMenunggu = $allData->where('status', 'menunggu')->count(); + $antrianDipanggil = $allData->where('status', 'dipanggil')->count(); + $antrianSedang = $allData->where('status', 'sedang_diperiksa')->count(); + $antrianBatal = $allData->where('status', 'batal')->count(); - return view('admin.laporan.index', compact('antrian', 'polis', 'totalAntrian', 'antrianSelesai', 'antrianMenunggu', 'antrianDipanggil', 'antrianSedang')); + return view('admin.laporan.index', compact('antrian', 'polis', 'totalAntrian', 'antrianSelesai', 'antrianMenunggu', 'antrianDipanggil', 'antrianSedang', 'antrianBatal')); } public function exportPDF(Request $request) @@ -256,7 +346,11 @@ public function exportPDF(Request $request) // Filter berdasarkan status if ($request->filled('status')) { - $query->where('status', $request->status); + $status = $request->status; + if ($status === 'sedang') { + $status = 'sedang_diperiksa'; + } + $query->where('status', $status); } // Filter berdasarkan jenis kelamin @@ -273,7 +367,7 @@ public function exportPDF(Request $request) $antrianSelesai = $antrian->where('status', 'selesai')->count(); $antrianMenunggu = $antrian->where('status', 'menunggu')->count(); $antrianDipanggil = $antrian->where('status', 'dipanggil')->count(); - $antrianSedang = $antrian->where('status', 'sedang')->count(); + $antrianSedang = $antrian->where('status', 'sedang_diperiksa')->count(); $pdf = Pdf::loadView('admin.laporan.pdf', compact('antrian', 'totalAntrian', 'antrianSelesai', 'antrianMenunggu', 'antrianDipanggil', 'antrianSedang')); return $pdf->download('laporan-antrian-' . date('Y-m-d') . '.pdf'); @@ -299,7 +393,11 @@ public function exportExcel(Request $request) // Filter berdasarkan status if ($request->filled('status')) { - $query->where('status', $request->status); + $status = $request->status; + if ($status === 'sedang') { + $status = 'sedang_diperiksa'; + } + $query->where('status', $status); } // Filter berdasarkan jenis kelamin @@ -316,7 +414,7 @@ public function exportExcel(Request $request) $antrianSelesai = $antrian->where('status', 'selesai')->count(); $antrianMenunggu = $antrian->where('status', 'menunggu')->count(); $antrianDipanggil = $antrian->where('status', 'dipanggil')->count(); - $antrianSedang = $antrian->where('status', 'sedang')->count(); + $antrianSedang = $antrian->where('status', 'sedang_diperiksa')->count(); $filename = 'laporan-antrian-' . date('Y-m-d') . '.csv'; @@ -339,21 +437,21 @@ public function exportExcel(Request $request) 'Tanggal Daftar', 'Waktu Daftar', 'Waktu Panggil' - ]); + ], ';'); // Data CSV foreach ($antrian as $item) { fputcsv($file, [ $item->no_antrian, $item->user->nama, - $item->user->no_ktp, + "'" . $item->user->no_ktp, $item->user->jenis_kelamin, $item->poli->nama_poli, ucfirst($item->status), $item->created_at ? $item->created_at->format('d/m/Y') : '-', $item->created_at ? $item->created_at->format('H:i') : '-', $item->waktu_panggil ? $item->waktu_panggil->format('H:i') : '-' - ]); + ], ';'); } fclose($file); @@ -362,7 +460,7 @@ public function exportExcel(Request $request) return response()->stream($callback, 200, $headers); } - public function poliUmum() + public function poliUmum(Request $request) { // Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit $this->autoBatalkanAntrianLama(); @@ -372,13 +470,13 @@ public function poliUmum() $query->where('nama_poli', 'umum'); }) ->orderBy('created_at', 'asc') - ->get(); + ->paginate(10); $title = 'Poli Umum'; return view('admin.poli.index', compact('antrians', 'title')); } - public function poliGigi() + public function poliGigi(Request $request) { // Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit $this->autoBatalkanAntrianLama(); @@ -388,13 +486,13 @@ public function poliGigi() $query->where('nama_poli', 'gigi'); }) ->orderBy('created_at', 'asc') - ->get(); + ->paginate(10); $title = 'Poli Gigi'; return view('admin.poli.index', compact('antrians', 'title')); } - public function poliJiwa() + public function poliJiwa(Request $request) { // Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit $this->autoBatalkanAntrianLama(); @@ -404,13 +502,13 @@ public function poliJiwa() $query->where('nama_poli', 'kesehatan jiwa'); }) ->orderBy('created_at', 'asc') - ->get(); + ->paginate(10); $title = 'Poli Jiwa'; return view('admin.poli.index', compact('antrians', 'title')); } - public function poliTradisional() + public function poliTradisional(Request $request) { // Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit $this->autoBatalkanAntrianLama(); @@ -420,7 +518,7 @@ public function poliTradisional() $query->where('nama_poli', 'kesehatan tradisional'); }) ->orderBy('created_at', 'asc') - ->get(); + ->paginate(10); $title = 'Poli Tradisional'; return view('admin.poli.index', compact('antrians', 'title')); @@ -513,10 +611,10 @@ public function selesaiAntrian(Request $request) $antrian = Antrian::findOrFail($request->antrian_id); - if ($antrian->status !== 'dipanggil') { + if (!in_array($antrian->status, ['dipanggil', 'sedang_diperiksa'])) { return response()->json([ 'success' => false, - 'message' => 'Antrian ini tidak dalam status dipanggil' + 'message' => 'Antrian ini tidak dalam status dipanggil atau sedang diperiksa' ]); } @@ -535,6 +633,71 @@ public function selesaiAntrian(Request $request) } } + public function konfirmasiKehadiran(Request $request) + { + try { + $request->validate([ + 'antrian_id' => 'required|exists:antrians,id' + ]); + + $antrian = Antrian::findOrFail($request->antrian_id); + + if ($antrian->status !== 'dipanggil') { + return response()->json([ + 'success' => false, + 'message' => 'Antrian ini tidak dalam status dipanggil' + ]); + } + + // Update waktu hadir + $antrian->update(['waktu_hadir' => now()]); + + return response()->json([ + 'success' => true, + 'message' => 'Kehadiran pasien ' . $antrian->no_antrian . ' telah dikonfirmasi' + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan: ' . $e->getMessage() + ], 500); + } + } + + public function mulaiPemeriksaan(Request $request) + { + try { + $request->validate([ + 'antrian_id' => 'required|exists:antrians,id' + ]); + + $antrian = Antrian::findOrFail($request->antrian_id); + + if ($antrian->status !== 'dipanggil' || !$antrian->waktu_hadir) { + return response()->json([ + 'success' => false, + 'message' => 'Pasien harus dikonfirmasi hadir terlebih dahulu' + ]); + } + + // Update status dan waktu mulai pemeriksaan + $antrian->update([ + 'status' => 'sedang_diperiksa', + 'waktu_mulai_periksa' => now() + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Pemeriksaan pasien ' . $antrian->no_antrian . ' dimulai' + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan: ' . $e->getMessage() + ], 500); + } + } + public function batalAntrian(Request $request) { try { @@ -607,13 +770,30 @@ public function storeAntrianAdmin(Request $request) $existingQueue = Antrian::where('user_id', $request->user_id) ->where('poli_id', $request->poli_id) ->whereDate('created_at', today()) - ->whereIn('status', ['menunggu', 'dipanggil']) + ->whereIn('status', ['menunggu', 'dipanggil', 'sedang_diperiksa']) ->first(); if ($existingQueue) { + $statusMessage = ''; + switch ($existingQueue->status) { + case 'menunggu': + $statusMessage = 'User ini masih memiliki antrian yang menunggu di ' . $poli->nama_poli . '.'; + break; + case 'dipanggil': + $statusMessage = 'User ini memiliki antrian yang sudah dipanggil di ' . $poli->nama_poli . '.'; + break; + case 'sedang_diperiksa': + $statusMessage = 'User ini sedang dalam proses pemeriksaan di ' . $poli->nama_poli . '. Tidak dapat membuat antrian baru.'; + break; + } + return response()->json([ 'success' => false, - 'message' => 'User ini sudah memiliki antrian aktif di ' . $poli->nama_poli . ' hari ini.' + 'message' => $statusMessage, + 'existing_queue' => [ + 'no_antrian' => $existingQueue->no_antrian, + 'status' => $existingQueue->status + ] ]); } @@ -681,14 +861,15 @@ private function getPoliPrefix($namaPoli) /** * Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit - * tanpa dikonfirmasi selesai oleh admin + * tetapi belum dikonfirmasi kehadirannya (belum ada waktu_hadir) */ private function autoBatalkanAntrianLama() { try { - // Cari antrian yang sudah dipanggil lebih dari 5 menit + // Cari antrian yang sudah dipanggil lebih dari 5 menit tapi belum konfirmasi hadir $antrianLama = Antrian::where('status', 'dipanggil') ->where('waktu_panggil', '<=', now()->subMinutes(5)) + ->whereNull('waktu_hadir') // Hanya yang belum konfirmasi hadir ->get(); $count = 0; @@ -698,16 +879,16 @@ private function autoBatalkanAntrianLama() $count++; // Log untuk tracking - \Log::info("Antrian {$antrian->no_antrian} otomatis dibatalkan karena lewat 5 menit sejak dipanggil"); + Log::info("Antrian {$antrian->no_antrian} otomatis dibatalkan karena lewat 5 menit sejak dipanggil tanpa konfirmasi kehadiran"); } // Jika ada antrian yang dibatalkan, log jumlahnya if ($count > 0) { - \Log::info("Total {$count} antrian otomatis dibatalkan karena timeout"); + Log::info("Total {$count} antrian otomatis dibatalkan karena timeout tanpa konfirmasi kehadiran"); } } catch (\Exception $e) { // Log error jika terjadi masalah - \Log::error("Error saat auto-batalkan antrian: " . $e->getMessage()); + Log::error("Error saat auto-batalkan antrian: " . $e->getMessage()); } } @@ -737,7 +918,7 @@ public function playQueueCallAudio(Request $request) ]); $poliName = $request->input('poli_name'); - + // Get audio sequence from AudioService $audioService = app(AudioService::class); $result = $audioService->getQueueCallAudio($poliName); @@ -765,9 +946,9 @@ public function panggilSelanjutnya(Request $request) $poliName = $request->input('poli_name'); // Cari antrian berikutnya yang status 'menunggu' - $antrianSelanjutnya = Antrian::whereHas('poli', function($query) use ($poliName) { - $query->where('nama_poli', $poliName); - }) + $antrianSelanjutnya = Antrian::whereHas('poli', function ($query) use ($poliName) { + $query->where('nama_poli', $poliName); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') @@ -790,7 +971,7 @@ public function panggilSelanjutnya(Request $request) \App\Models\RiwayatPanggilan::create([ 'antrian_id' => $antrianSelanjutnya->id, 'waktu_panggilan' => now(), - 'admin_id' => auth()->id() + 'admin_id' => Auth::id() ]); // Get audio sequence diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index a2725af..83049d8 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -43,20 +43,34 @@ public function addQueue(Request $request) $poli = Poli::find($request->poli_id); $poliName = $poli->nama_poli; - // Check if user already has a queue today for the same poli + // Check if user already has an active queue today for the same poli $existingQueue = Antrian::where('user_id', $user->id) ->where('poli_id', $request->poli_id) ->whereDate('created_at', today()) - ->whereIn('status', ['menunggu', 'dipanggil']) + ->whereIn('status', ['menunggu', 'dipanggil', 'sedang_diperiksa']) ->first(); if ($existingQueue) { + // Determine the appropriate message based on status + $statusMessage = ''; + switch ($existingQueue->status) { + case 'menunggu': + $statusMessage = 'Anda masih memiliki antrian yang menunggu di ' . $poliName . '.'; + break; + case 'dipanggil': + $statusMessage = 'Anda memiliki antrian yang sudah dipanggil di ' . $poliName . '. Silakan datang ke poli.'; + break; + case 'sedang_diperiksa': + $statusMessage = 'Anda sedang dalam proses pemeriksaan di ' . $poliName . '. Tidak dapat mengambil antrian baru di poli yang sama.'; + break; + } + // Check if request wants JSON response if ($request->expectsJson()) { return response()->json([ 'success' => false, 'type' => 'existing_queue', - 'message' => 'Anda sudah memiliki antrian di ' . $poliName . ' hari ini.', + 'message' => $statusMessage, 'existing_queue' => [ 'id' => $existingQueue->id, 'no_antrian' => $existingQueue->no_antrian, @@ -68,7 +82,7 @@ public function addQueue(Request $request) } // Fallback to redirect for non-AJAX requests - return redirect()->back()->with('error', 'Anda sudah memiliki antrian di ' . $poliName . ' hari ini.'); + return redirect()->back()->with('error', $statusMessage); } // Note: User CAN have multiple queues in different polis @@ -91,14 +105,14 @@ public function addQueue(Request $request) $lastNumber = (int) preg_replace('/^[A-Z]+/', '', $lastQueue->no_antrian); // Debug log - \Log::info("Last queue: {$lastQueue->no_antrian}, Extracted number: {$lastNumber}"); + Log::info("Last queue: {$lastQueue->no_antrian}, Extracted number: {$lastNumber}"); } $nextNumber = $lastNumber + 1; $nextQueueNumber = $prefix . $nextNumber; // Debug log - \Log::info("Generated next queue number: {$nextQueueNumber} for poli: {$poliName}"); + Log::info("Generated next queue number: {$nextQueueNumber} for poli: {$poliName}"); // Create new queue using current user's data $antrian = Antrian::create([ @@ -152,6 +166,7 @@ public function updateProfile(Request $request) ]); try { + /** @var \App\Models\User $user */ $user = Auth::user(); // Check if KTP number is already used by another user @@ -205,14 +220,15 @@ private function getPoliPrefix($namaPoli) /** * Auto-batalkan antrian yang sudah dipanggil lebih dari 5 menit - * tanpa dikonfirmasi selesai oleh admin + * tetapi belum dikonfirmasi kehadirannya (belum ada waktu_hadir) */ private function autoBatalkanAntrianLama() { try { - // Cari antrian yang sudah dipanggil lebih dari 5 menit + // Cari antrian yang sudah dipanggil lebih dari 5 menit tapi belum konfirmasi hadir $antrianLama = Antrian::where('status', 'dipanggil') ->where('waktu_panggil', '<=', now()->subMinutes(5)) + ->whereNull('waktu_hadir') // Hanya yang belum konfirmasi hadir ->get(); foreach ($antrianLama as $antrian) { @@ -220,16 +236,61 @@ private function autoBatalkanAntrianLama() $antrian->update(['status' => 'batal']); // Log untuk tracking (optional) - \Log::info("Antrian {$antrian->no_antrian} otomatis dibatalkan karena lewat 5 menit sejak dipanggil"); + Log::info("Antrian {$antrian->no_antrian} otomatis dibatalkan karena lewat 5 menit sejak dipanggil tanpa konfirmasi kehadiran"); } // Jika ada antrian yang dibatalkan, log jumlahnya if ($antrianLama->count() > 0) { - \Log::info("Total {$antrianLama->count()} antrian otomatis dibatalkan karena timeout"); + Log::info("Total {$antrianLama->count()} antrian otomatis dibatalkan karena timeout tanpa konfirmasi kehadiran"); } } catch (\Exception $e) { // Log error jika terjadi masalah - \Log::error("Error saat auto-batalkan antrian: " . $e->getMessage()); + Log::error("Error saat auto-batalkan antrian: " . $e->getMessage()); + } + } + + /** + * Check if user has active queue in specific poli today + * @param int $userId + * @param int $poliId + * @return array|null + */ + private function checkActiveQueue($userId, $poliId) + { + $activeQueue = Antrian::where('user_id', $userId) + ->where('poli_id', $poliId) + ->whereDate('created_at', today()) + ->whereIn('status', ['menunggu', 'dipanggil', 'sedang_diperiksa']) + ->first(); + + if ($activeQueue) { + return [ + 'exists' => true, + 'queue' => $activeQueue, + 'message' => $this->getStatusMessage($activeQueue->status, $activeQueue->poli->nama_poli) + ]; + } + + return ['exists' => false]; + } + + /** + * Get appropriate message based on queue status + * @param string $status + * @param string $poliName + * @return string + */ + private function getStatusMessage($status, $poliName) + { + switch ($status) { + case 'menunggu': + return "Anda masih memiliki antrian yang menunggu di {$poliName}."; + case 'dipanggil': + return "Anda memiliki antrian yang sudah dipanggil di {$poliName}. Silakan datang ke poli."; + case 'sedang_diperiksa': + return "Anda sedang dalam proses pemeriksaan di {$poliName}. Tidak dapat mengambil antrian baru di poli yang sama."; + default: + return "Anda sudah memiliki antrian aktif di {$poliName}."; } } diff --git a/app/Http/Controllers/DisplayController.php b/app/Http/Controllers/DisplayController.php index 170ed08..61a3452 100644 --- a/app/Http/Controllers/DisplayController.php +++ b/app/Http/Controllers/DisplayController.php @@ -12,71 +12,71 @@ public function index() { // Get all available polis $polis = Poli::all(); - + // Current: sedang dipanggil per poli - $poliUmumCurrent = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'umum'); - }) - ->where('status', 'dipanggil') + $poliUmumCurrent = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'umum'); + }) + ->whereIn('status', ['dipanggil', 'sedang_diperiksa']) ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliGigiCurrent = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'gigi'); - }) - ->where('status', 'dipanggil') + $poliGigiCurrent = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'gigi'); + }) + ->whereIn('status', ['dipanggil', 'sedang_diperiksa']) ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliJiwaCurrent = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'kesehatan jiwa'); - }) - ->where('status', 'dipanggil') + $poliJiwaCurrent = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'kesehatan jiwa'); + }) + ->whereIn('status', ['dipanggil', 'sedang_diperiksa']) ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliTradisionalCurrent = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'kesehatan tradisional'); - }) - ->where('status', 'dipanggil') + $poliTradisionalCurrent = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'kesehatan tradisional'); + }) + ->whereIn('status', ['dipanggil', 'sedang_diperiksa']) ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); // Next: menunggu per poli (maks 3) - $poliUmumNext = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'umum'); - }) + $poliUmumNext = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'umum'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') ->take(3) ->get(); - $poliGigiNext = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'gigi'); - }) + $poliGigiNext = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'gigi'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') ->take(3) ->get(); - $poliJiwaNext = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'kesehatan jiwa'); - }) + $poliJiwaNext = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'kesehatan jiwa'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') ->take(3) ->get(); - $poliTradisionalNext = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'kesehatan tradisional'); - }) + $poliTradisionalNext = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'kesehatan tradisional'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') @@ -126,57 +126,57 @@ public function checkNewCalls(Request $request) public function getDisplayData() { // Current: sedang dipanggil per poli - $poliUmumCurrent = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'umum'); - }) - ->where('status', 'dipanggil') + $poliUmumCurrent = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'umum'); + }) + ->whereIn('status', ['dipanggil', 'sedang_diperiksa']) ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliGigiCurrent = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'gigi'); - }) - ->where('status', 'dipanggil') + $poliGigiCurrent = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'gigi'); + }) + ->whereIn('status', ['dipanggil', 'sedang_diperiksa']) ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); - $poliJiwaCurrent = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'kesehatan jiwa'); - }) - ->where('status', 'dipanggil') + $poliJiwaCurrent = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'kesehatan jiwa'); + }) + ->whereIn('status', ['dipanggil', 'sedang_diperiksa']) ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); $poliTradisionalCurrent = Antrian::where('poli_id', 4) - ->where('status', 'dipanggil') + ->whereIn('status', ['dipanggil', 'sedang_diperiksa']) ->whereDate('created_at', today()) ->orderByDesc('updated_at') ->first(); // Next: menunggu per poli (maks 3) - $poliUmumNext = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'umum'); - }) + $poliUmumNext = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'umum'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') ->take(3) ->get(); - $poliGigiNext = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'gigi'); - }) + $poliGigiNext = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'gigi'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') ->get(); - $poliJiwaNext = Antrian::whereHas('poli', function($query) { - $query->where('nama_poli', 'kesehatan jiwa'); - }) + $poliJiwaNext = Antrian::whereHas('poli', function ($query) { + $query->where('nama_poli', 'kesehatan jiwa'); + }) ->where('status', 'menunggu') ->whereDate('created_at', today()) ->orderBy('created_at', 'asc') diff --git a/app/Models/Antrian.php b/app/Models/Antrian.php index 3861e7f..d8da537 100644 --- a/app/Models/Antrian.php +++ b/app/Models/Antrian.php @@ -18,12 +18,15 @@ class Antrian extends Model 'is_call', 'status', 'waktu_panggil', - 'loket_id' + 'waktu_hadir', + 'waktu_mulai_periksa' ]; protected $casts = [ 'tanggal_antrian' => 'date', 'waktu_panggil' => 'datetime', + 'waktu_hadir' => 'datetime', + 'waktu_mulai_periksa' => 'datetime', 'is_call' => 'boolean', ]; @@ -37,11 +40,6 @@ public function poli() return $this->belongsTo(Poli::class); } - public function loket() - { - return $this->belongsTo(Loket::class); - } - public function riwayatPanggilan() { return $this->hasMany(RiwayatPanggilan::class); diff --git a/app/Models/Loket.php b/app/Models/Loket.php deleted file mode 100644 index 4a5733c..0000000 --- a/app/Models/Loket.php +++ /dev/null @@ -1,19 +0,0 @@ -hasMany(Antrian::class); - } -} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index cdf5020..246b608 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -19,7 +19,8 @@ public function register(): void */ public function boot(): void { - // Set pagination to use Tailwind CSS styling - \Illuminate\Pagination\Paginator::useTailwind(); + // Set pagination to use custom view + \Illuminate\Pagination\Paginator::defaultView('custom.pagination'); + \Illuminate\Pagination\Paginator::defaultSimpleView('custom.pagination-simple'); } } diff --git a/composer.json b/composer.json index 281564c..0c1c1b0 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,6 @@ "php": "^8.2", "barryvdh/laravel-dompdf": "^3.1", "laravel/framework": "^12.0", - "laravel/sanctum": "^4.2", "laravel/tinker": "^2.10.1", "pusher/pusher-php-server": "^7.2" }, diff --git a/composer.lock b/composer.lock index 6a7614e..5a8dce3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6a7319662ab12a24a54850a3e8a7b468", + "content-hash": "74ecb2b28b89ed06697fc02c059657d8", "packages": [ { "name": "barryvdh/laravel-dompdf", @@ -1559,70 +1559,6 @@ }, "time": "2025-07-07T14:17:42+00:00" }, - { - "name": "laravel/sanctum", - "version": "v4.2.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/sanctum.git", - "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/fd6df4f79f48a72992e8d29a9c0ee25422a0d677", - "reference": "fd6df4f79f48a72992e8d29a9c0ee25422a0d677", - "shasum": "" - }, - "require": { - "ext-json": "*", - "illuminate/console": "^11.0|^12.0", - "illuminate/contracts": "^11.0|^12.0", - "illuminate/database": "^11.0|^12.0", - "illuminate/support": "^11.0|^12.0", - "php": "^8.2", - "symfony/console": "^7.0" - }, - "require-dev": { - "mockery/mockery": "^1.6", - "orchestra/testbench": "^9.0|^10.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^11.3" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Laravel\\Sanctum\\SanctumServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Laravel\\Sanctum\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", - "keywords": [ - "auth", - "laravel", - "sanctum" - ], - "support": { - "issues": "https://github.com/laravel/sanctum/issues", - "source": "https://github.com/laravel/sanctum" - }, - "time": "2025-07-09T19:45:24+00:00" - }, { "name": "laravel/serializable-closure", "version": "v2.0.4", @@ -8747,12 +8683,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/config/auth.php b/config/auth.php index fc8b76c..c9138a1 100644 --- a/config/auth.php +++ b/config/auth.php @@ -93,14 +93,25 @@ | */ - 'passwords' => [ - 'users' => [ - 'provider' => 'users', - 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), - 'expire' => 60, - 'throttle' => 60, - ], - ], + /* + |-------------------------------------------------------------------------- + | Password Reset Settings (DISABLED) + |-------------------------------------------------------------------------- + | + | Password reset settings are disabled because this application uses + | session-based password reset instead of Laravel's built-in token-based + | password reset functionality. + | + */ + + // 'passwords' => [ + // 'users' => [ + // 'provider' => 'users', + // 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + // 'expire' => 60, + // 'throttle' => 60, + // ], + // ], /* |-------------------------------------------------------------------------- diff --git a/config/sanctum.php b/config/sanctum.php deleted file mode 100644 index 44527d6..0000000 --- a/config/sanctum.php +++ /dev/null @@ -1,84 +0,0 @@ - explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( - '%s%s', - 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', - Sanctum::currentApplicationUrlWithPort(), - // Sanctum::currentRequestHost(), - ))), - - /* - |-------------------------------------------------------------------------- - | Sanctum Guards - |-------------------------------------------------------------------------- - | - | This array contains the authentication guards that will be checked when - | Sanctum is trying to authenticate a request. If none of these guards - | are able to authenticate the request, Sanctum will use the bearer - | token that's present on an incoming request for authentication. - | - */ - - 'guard' => ['web'], - - /* - |-------------------------------------------------------------------------- - | Expiration Minutes - |-------------------------------------------------------------------------- - | - | This value controls the number of minutes until an issued token will be - | considered expired. This will override any values set in the token's - | "expires_at" attribute, but first-party sessions are not affected. - | - */ - - 'expiration' => null, - - /* - |-------------------------------------------------------------------------- - | Token Prefix - |-------------------------------------------------------------------------- - | - | Sanctum can prefix new tokens in order to take advantage of numerous - | security scanning initiatives maintained by open source platforms - | that notify developers if they commit tokens into repositories. - | - | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning - | - */ - - 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), - - /* - |-------------------------------------------------------------------------- - | Sanctum Middleware - |-------------------------------------------------------------------------- - | - | When authenticating your first-party SPA with Sanctum you may need to - | customize some of the middleware Sanctum uses while processing the - | request. You may change the middleware listed below as required. - | - */ - - 'middleware' => [ - 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, - 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, - 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, - ], - -]; diff --git a/database/migrations/2024_01_01_000003_create_lokets_table.php b/database/migrations/2024_01_01_000003_create_lokets_table.php deleted file mode 100644 index 29da2f8..0000000 --- a/database/migrations/2024_01_01_000003_create_lokets_table.php +++ /dev/null @@ -1,27 +0,0 @@ -id(); - $table->string('nama_loket', 100); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('lokets'); - } -}; diff --git a/database/migrations/2025_08_08_092603_create_personal_access_tokens_table.php b/database/migrations/2025_08_08_092603_create_personal_access_tokens_table.php deleted file mode 100644 index 4279a2e..0000000 --- a/database/migrations/2025_08_08_092603_create_personal_access_tokens_table.php +++ /dev/null @@ -1,32 +0,0 @@ -id(); - $table->morphs('tokenable'); - $table->text('name'); - $table->string('token', 64)->unique(); - $table->text('abilities')->nullable(); - $table->timestamp('last_used_at')->nullable(); - $table->timestamp('expires_at')->nullable()->index(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('personal_access_tokens'); - } -}; diff --git a/database/migrations/2025_08_10_160027_create_jobs_table.php b/database/migrations/2025_08_10_160027_create_jobs_table.php deleted file mode 100644 index 6098d9b..0000000 --- a/database/migrations/2025_08_10_160027_create_jobs_table.php +++ /dev/null @@ -1,32 +0,0 @@ -bigIncrements('id'); - $table->string('queue')->index(); - $table->longText('payload'); - $table->unsignedTinyInteger('attempts'); - $table->unsignedInteger('reserved_at')->nullable(); - $table->unsignedInteger('available_at'); - $table->unsignedInteger('created_at'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('jobs'); - } -}; diff --git a/database/migrations/2025_08_14_004750_add_sedang_diperiksa_status_to_antrians_table.php b/database/migrations/2025_08_14_004750_add_sedang_diperiksa_status_to_antrians_table.php new file mode 100644 index 0000000..30b4039 --- /dev/null +++ b/database/migrations/2025_08_14_004750_add_sedang_diperiksa_status_to_antrians_table.php @@ -0,0 +1,42 @@ +enum('status', ['menunggu', 'dipanggil', 'sedang_diperiksa', 'selesai', 'batal']) + ->default('menunggu') + ->change(); + + // Tambah field waktu konfirmasi kehadiran + $table->timestamp('waktu_hadir')->nullable()->after('waktu_panggil'); + + // Tambah field waktu mulai pemeriksaan + $table->timestamp('waktu_mulai_periksa')->nullable()->after('waktu_hadir'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('antrians', function (Blueprint $table) { + // Kembalikan enum status ke yang lama + $table->enum('status', ['menunggu', 'dipanggil', 'selesai', 'batal']) + ->default('menunggu') + ->change(); + + // Hapus field yang ditambahkan + $table->dropColumn(['waktu_hadir', 'waktu_mulai_periksa']); + }); + } +}; diff --git a/database/migrations/2025_08_14_014038_cleanup_unused_database_tables.php b/database/migrations/2025_08_14_014038_cleanup_unused_database_tables.php new file mode 100644 index 0000000..2c9cd8c --- /dev/null +++ b/database/migrations/2025_08_14_014038_cleanup_unused_database_tables.php @@ -0,0 +1,92 @@ +dropForeign(['loket_id']); + $table->dropColumn('loket_id'); + }); + + // 2. Drop unused tables + Schema::dropIfExists('lokets'); + Schema::dropIfExists('personal_access_tokens'); + Schema::dropIfExists('jobs'); + + // 3. Drop failed_jobs table if exists (related to jobs) + Schema::dropIfExists('failed_jobs'); + Schema::dropIfExists('job_batches'); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Recreate jobs related tables + Schema::create('jobs', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('failed_jobs', function (Blueprint $table) { + $table->id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + + // Recreate personal_access_tokens table + Schema::create('personal_access_tokens', function (Blueprint $table) { + $table->id(); + $table->morphs('tokenable'); + $table->text('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable()->index(); + $table->timestamps(); + }); + + // Recreate lokets table + Schema::create('lokets', function (Blueprint $table) { + $table->id(); + $table->string('nama_loket', 100); + $table->timestamps(); + }); + + // Add back loket_id to antrians table + Schema::table('antrians', function (Blueprint $table) { + $table->foreignId('loket_id')->nullable()->constrained('lokets')->onDelete('set null'); + }); + } +}; diff --git a/database/migrations/2025_08_14_015356_drop_password_reset_tokens_table.php b/database/migrations/2025_08_14_015356_drop_password_reset_tokens_table.php new file mode 100644 index 0000000..d7ab0cb --- /dev/null +++ b/database/migrations/2025_08_14_015356_drop_password_reset_tokens_table.php @@ -0,0 +1,30 @@ +string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } +}; diff --git a/database/seeders/AntrianPuskesmasSeeder.php b/database/seeders/AntrianPuskesmasSeeder.php index 42407ab..6c60d9a 100644 --- a/database/seeders/AntrianPuskesmasSeeder.php +++ b/database/seeders/AntrianPuskesmasSeeder.php @@ -30,18 +30,6 @@ public function run(): void ); } - // Seed lokets table with upsert - $lokets = [ - ['id' => 1, 'nama_loket' => 'Loket 1'], - ['id' => 2, 'nama_loket' => 'Loket 2'], - ]; - foreach ($lokets as $loket) { - DB::table('lokets')->updateOrInsert( - ['id' => $loket['id']], - ['nama_loket' => $loket['nama_loket'], 'updated_at' => now()] - ); - } - // Seed users table with upsert DB::table('users')->updateOrInsert( ['id' => 1], @@ -57,7 +45,7 @@ public function run(): void ] ); - // Seed antrians table with upsert + // Seed antrians table with upsert (removed loket_id reference) DB::table('antrians')->updateOrInsert( ['id' => 1], [ @@ -68,12 +56,13 @@ public function run(): void 'is_call' => 0, 'status' => 'menunggu', 'waktu_panggil' => null, - 'loket_id' => 1, + 'waktu_hadir' => null, + 'waktu_mulai_periksa' => null, 'updated_at' => now(), ] ); - // Add more antrian data for testing + // Add more antrian data for testing - Poli Gigi DB::table('antrians')->updateOrInsert( ['id' => 2], [ @@ -84,11 +73,13 @@ public function run(): void 'is_call' => 0, 'status' => 'menunggu', 'waktu_panggil' => null, - 'loket_id' => 1, + 'waktu_hadir' => null, + 'waktu_mulai_periksa' => null, 'updated_at' => now(), ] ); + // Poli Jiwa - Status dipanggil untuk testing DB::table('antrians')->updateOrInsert( ['id' => 3], [ @@ -96,14 +87,16 @@ public function run(): void 'poli_id' => 3, 'no_antrian' => 'J1', 'tanggal_antrian' => now()->toDateString(), - 'is_call' => 0, + 'is_call' => 1, 'status' => 'dipanggil', 'waktu_panggil' => now(), - 'loket_id' => 1, + 'waktu_hadir' => null, + 'waktu_mulai_periksa' => null, 'updated_at' => now(), ] ); + // Poli Tradisional - Status sedang diperiksa untuk testing DB::table('antrians')->updateOrInsert( ['id' => 4], [ @@ -111,10 +104,79 @@ public function run(): void 'poli_id' => 4, 'no_antrian' => 'T1', 'tanggal_antrian' => now()->toDateString(), + 'is_call' => 1, + 'status' => 'sedang_diperiksa', + 'waktu_panggil' => now()->subMinutes(10), + 'waktu_hadir' => now()->subMinutes(8), + 'waktu_mulai_periksa' => now()->subMinutes(5), + 'updated_at' => now(), + ] + ); + + // Tambah satu lagi data sedang diperiksa untuk testing filter + DB::table('antrians')->updateOrInsert( + ['id' => 5], + [ + 'user_id' => 1, + 'poli_id' => 1, + 'no_antrian' => 'U2', + 'tanggal_antrian' => now()->toDateString(), + 'is_call' => 1, + 'status' => 'sedang_diperiksa', + 'waktu_panggil' => now()->subMinutes(15), + 'waktu_hadir' => now()->subMinutes(12), + 'waktu_mulai_periksa' => now()->subMinutes(8), + 'updated_at' => now(), + ] + ); + + // Tambah data dengan status batal untuk testing filter + DB::table('antrians')->updateOrInsert( + ['id' => 6], + [ + 'user_id' => 1, + 'poli_id' => 2, + 'no_antrian' => 'G2', + 'tanggal_antrian' => now()->toDateString(), 'is_call' => 0, - 'status' => 'menunggu', + 'status' => 'batal', 'waktu_panggil' => null, - 'loket_id' => 1, + 'waktu_hadir' => null, + 'waktu_mulai_periksa' => null, + 'updated_at' => now(), + ] + ); + + // Tambah satu lagi data batal untuk testing filter + DB::table('antrians')->updateOrInsert( + ['id' => 7], + [ + 'user_id' => 1, + 'poli_id' => 3, + 'no_antrian' => 'J2', + 'tanggal_antrian' => now()->toDateString(), + 'is_call' => 0, + 'status' => 'batal', + 'waktu_panggil' => null, + 'waktu_hadir' => null, + 'waktu_mulai_periksa' => null, + 'updated_at' => now(), + ] + ); + + // Tambah data dengan status selesai untuk testing filter + DB::table('antrians')->updateOrInsert( + ['id' => 8], + [ + 'user_id' => 1, + 'poli_id' => 4, + 'no_antrian' => 'T2', + 'tanggal_antrian' => now()->toDateString(), + 'is_call' => 1, + 'status' => 'selesai', + 'waktu_panggil' => now()->subMinutes(30), + 'waktu_hadir' => now()->subMinutes(25), + 'waktu_mulai_periksa' => now()->subMinutes(20), 'updated_at' => now(), ] ); diff --git a/database/seeders/LoketSeeder.php b/database/seeders/LoketSeeder.php deleted file mode 100644 index defc0fc..0000000 --- a/database/seeders/LoketSeeder.php +++ /dev/null @@ -1,22 +0,0 @@ - 'Loket 1'], - ['nama_loket' => 'Loket 2'], - ['nama_loket' => 'Loket 3'], - ]; - - foreach ($lokets as $loket) { - Loket::create($loket); - } - } -} diff --git a/resources/views/admin/antrian/tambah.blade.php b/resources/views/admin/antrian/tambah.blade.php index 28fad1c..d746191 100644 --- a/resources/views/admin/antrian/tambah.blade.php +++ b/resources/views/admin/antrian/tambah.blade.php @@ -1,143 +1,151 @@ @extends('layouts.app') @section('content') -
- @include('admin.partials.top-nav') +
+ @include('admin.partials.top-nav') -
- @include('admin.partials.sidebar') +
+ @include('admin.partials.sidebar') - -
-
- -
-

Tambah Antrian Manual

-

Bantu pasien yang tidak bisa antri online dengan membuat antrian manual

-
+ +
+
+ +
+

Tambah Antrian Manual

+

Bantu pasien yang tidak bisa antri online dengan membuat antrian manual

+
- -
-

Cari Pasien

- -
-
- + +
+

Cari Pasien

+ +
+
+ +
+
-
+ + + - - - - - - -
- - -
-
- - + + - + // Create queue + document.getElementById('createQueueBtn').addEventListener('click', function() { + if (!selectedUser || !document.getElementById('poliSelect').value) { + Swal.fire({ + icon: 'warning', + title: 'Peringatan', + text: 'Pilih user dan poli terlebih dahulu', + confirmButtonColor: '#3B82F6' + }); + return; + } -@include('admin.partials.sidebar-script') + createQueue(); + }); + + function createQueue() { + const poliId = document.getElementById('poliSelect').value; + + fetch('{{ route('admin.antrian.store') }}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + }, + body: JSON.stringify({ + user_id: selectedUser.id, + poli_id: poliId + }) + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + Swal.fire({ + icon: 'success', + title: 'Berhasil!', + text: data.message, + confirmButtonColor: '#10B981' + }).then(() => { + resetForm(); + }); + } else { + // Check if it's an existing queue error with additional details + if (data.existing_queue) { + let statusText = ''; + let alertIcon = 'warning'; + let titleText = 'Antrian Sudah Ada!'; + + switch (data.existing_queue.status) { + case 'menunggu': + statusText = 'Menunggu'; + break; + case 'dipanggil': + statusText = 'Dipanggil'; + alertIcon = 'info'; + titleText = 'Antrian Sedang Dipanggil!'; + break; + case 'sedang_diperiksa': + statusText = 'Sedang Diperiksa'; + alertIcon = 'error'; + titleText = 'Sedang Dalam Pemeriksaan!'; + break; + } + + Swal.fire({ + icon: alertIcon, + title: titleText, + html: ` +
+

${data.message}

+
+
+ + + + Detail Antrian Aktif: +
+
+

No. Antrian: ${data.existing_queue.no_antrian}

+

Status: ${statusText}

+
+
+ ${data.existing_queue.status === 'sedang_diperiksa' + ? '

⚠️ User tidak dapat mengambil antrian baru di poli yang sama selama masih dalam proses pemeriksaan.

' + : '

Silakan tunggu hingga antrian selesai atau dibatalkan terlebih dahulu.

' + } +
+ `, + confirmButtonColor: data.existing_queue.status === 'sedang_diperiksa' ? + '#EF4444' : '#F59E0B' + }); + } else { + // Generic error + Swal.fire({ + icon: 'error', + title: 'Error', + text: data.message, + confirmButtonColor: '#EF4444' + }); + } + } + }) + .catch(error => { + console.error('Error:', error); + Swal.fire({ + icon: 'error', + title: 'Error', + text: 'Terjadi kesalahan saat membuat antrian', + confirmButtonColor: '#EF4444' + }); + }); + } + + function resetForm() { + selectedUser = null; + document.getElementById('userSelection').classList.add('hidden'); + document.getElementById('searchResults').classList.add('hidden'); + document.getElementById('noResults').classList.add('hidden'); + document.getElementById('searchInput').value = ''; + document.getElementById('poliSelect').value = ''; + document.getElementById('createQueueBtn').disabled = true; + } + + + @include('admin.partials.sidebar-script') @endsection diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 2a50a45..aa4666e 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -19,68 +19,64 @@
-
-
-
-
- - - -
-
-

Poli Umum

-

{{ $poliUmumCount ?? 0 }}

-

Antrian menunggu

-
+
+
+
+ + + +
+
+

Poli Umum

+

{{ $poliUmumCount ?? 0 }}

+

Antrian menunggu

-
-
-
- - - -
-
-

Poli Gigi

-

{{ $poliGigiCount ?? 0 }}

-

Antrian menunggu

-
+
+
+ + + +
+
+

Poli Gigi

+

{{ $poliGigiCount ?? 0 }}

+

Antrian menunggu

-
-
-
- - - -
-
-

Poli Jiwa

-

{{ $poliJiwaCount ?? 0 }}

-

Antrian menunggu

-
+
+
+ + + +
+
+

Poli Jiwa

+

{{ $poliJiwaCount ?? 0 }}

+

Antrian menunggu

-
-
-
- - - -
-
-

Poli Tradisional

-

{{ $poliTradisionalCount ?? 0 }}

-

Antrian menunggu

-
+
+
+ + + +
+
+

Poli Tradisional

+

{{ $poliTradisionalCount ?? 0 }}

+

Antrian menunggu

@@ -102,8 +98,8 @@ class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition duration-200"> Tambah Antrian - + +
@@ -156,43 +152,105 @@ class="inline-flex items-center px-4 py-2 bg-purple-600 text-white rounded-lg ho

Aktivitas Terbaru

-
-
-
- - - +
+ @if (isset($antrianTerbaru) && $antrianTerbaru->count() > 0) + @foreach ($antrianTerbaru as $antrian) +
+ @if ($antrian->status == 'menunggu') +
+ + + +
+
+

Antrian baru ditambahkan

+

{{ $antrian->poli->nama_poli }} - + {{ $antrian->created_at->diffForHumans() }}

+
+ @elseif($antrian->status == 'dipanggil') +
+ + + +
+
+

Antrian dipanggil

+

{{ $antrian->poli->nama_poli }} - + {{ $antrian->updated_at->diffForHumans() }}

+
+ @elseif($antrian->status == 'selesai') +
+ + + +
+
+

Antrian selesai

+

{{ $antrian->poli->nama_poli }} - + {{ $antrian->updated_at->diffForHumans() }}

+
+ @elseif($antrian->status == 'sedang_diperiksa') +
+ + + +
+
+

Sedang diperiksa

+

{{ $antrian->poli->nama_poli }} - + {{ $antrian->updated_at->diffForHumans() }}

+
+ @elseif($antrian->status == 'batal') +
+ + + +
+
+

Antrian dibatalkan

+

{{ $antrian->poli->nama_poli }} - + {{ $antrian->updated_at->diffForHumans() }}

+
+ @else +
+ + + + +
+
+

Status tidak dikenal

+

{{ $antrian->poli->nama_poli }} - + {{ $antrian->updated_at->diffForHumans() }}

+
+ @endif +
+ @endforeach + @else +
+
+ + + + +

Belum ada aktivitas hari ini

+
-
-

Antrian baru ditambahkan

-

Poli Umum - 2 menit yang lalu

-
-
-
-
- - - -
-
-

Antrian selesai

-

Poli Gigi - 5 menit yang lalu

-
-
-
-
- - - -
-
-

Antrian dipanggil

-

Poli Jiwa - 8 menit yang lalu

-
-
+ @endif
@@ -206,4 +264,121 @@ class="inline-flex items-center px-4 py-2 bg-purple-600 text-white rounded-lg ho @include('admin.partials.sidebar-script') + + @push('scripts') + + @endpush @endsection diff --git a/resources/views/admin/laporan/index.blade.php b/resources/views/admin/laporan/index.blade.php index c5a63dc..64dac6b 100644 --- a/resources/views/admin/laporan/index.blade.php +++ b/resources/views/admin/laporan/index.blade.php @@ -4,142 +4,10 @@ @section('content')
- - + @include('admin.partials.top-nav')
- - - - - + @include('admin.partials.sidebar')
@@ -266,7 +134,7 @@ class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shad
-
+
@@ -358,6 +226,24 @@ class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shad
+ +
+
+
+
+ + + +
+
+
+

Batal

+

{{ $antrianBatal }}

+
+
+
@@ -427,7 +313,7 @@ class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium b class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"> Dipanggil - @elseif($item->status == 'sedang') + @elseif($item->status == 'sedang_diperiksa') Sedang @@ -504,7 +390,7 @@ class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium b class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"> Dipanggil - @elseif($item->status == 'sedang') + @elseif($item->status == 'sedang_diperiksa') Sedang @@ -568,6 +454,11 @@ class="text-gray-600">{{ $item->waktu_panggil ? $item->waktu_panggil->format('H:
@endforelse
+ + +
+ {{ $antrian->withQueryString()->links() }} +
@@ -661,4 +552,6 @@ function toggleLaporanDetails(id) { } @endpush + + @include('admin.partials.sidebar-script') @endsection diff --git a/resources/views/admin/laporan/pdf.blade.php b/resources/views/admin/laporan/pdf.blade.php index ad959f5..3d313a0 100644 --- a/resources/views/admin/laporan/pdf.blade.php +++ b/resources/views/admin/laporan/pdf.blade.php @@ -116,7 +116,16 @@ {{ $item->user->no_ktp }} {{ $item->poli->nama_poli }} - {{ ucfirst($item->status) }} + @php + $statusMap = [ + 'menunggu' => 'Menunggu', + 'dipanggil' => 'Dipanggil', + 'sedang_diperiksa' => 'Sedang Diperiksa', + 'selesai' => 'Selesai', + 'batal' => 'Batal', + ]; + @endphp + {{ $statusMap[$item->status] ?? ucfirst(str_replace('_', ' ', $item->status)) }} {{ $item->created_at ? $item->created_at->format('d/m/Y') : '-' }} {{ $item->created_at ? $item->created_at->format('H:i') : '-' }} diff --git a/resources/views/admin/partials/sidebar-script.blade.php b/resources/views/admin/partials/sidebar-script.blade.php index 5a3bb19..f1c9ece 100644 --- a/resources/views/admin/partials/sidebar-script.blade.php +++ b/resources/views/admin/partials/sidebar-script.blade.php @@ -1,49 +1,113 @@ diff --git a/resources/views/admin/partials/sidebar.blade.php b/resources/views/admin/partials/sidebar.blade.php index 047e302..fe8e24a 100644 --- a/resources/views/admin/partials/sidebar.blade.php +++ b/resources/views/admin/partials/sidebar.blade.php @@ -1,6 +1,6 @@
+ + +
+ +
- + diff --git a/resources/views/admin/partials/top-nav.blade.php b/resources/views/admin/partials/top-nav.blade.php index 07e6092..70e085e 100644 --- a/resources/views/admin/partials/top-nav.blade.php +++ b/resources/views/admin/partials/top-nav.blade.php @@ -1,5 +1,5 @@ -
diff --git a/resources/views/admin/poli/index.blade.php b/resources/views/admin/poli/index.blade.php index 588c14c..c6e8a51 100644 --- a/resources/views/admin/poli/index.blade.php +++ b/resources/views/admin/poli/index.blade.php @@ -2,144 +2,51 @@ @section('title', $title) +@push('styles') + +@endpush + @section('content')
- - + @include('admin.partials.top-nav')
- - - - - + @include('admin.partials.sidebar')
@@ -158,28 +65,28 @@ class="flex items-center px-4 py-3 rounded-xl text-gray-700 hover:bg-gray-50 tra + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-20"> No Antrian Nama + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-32"> Alamat - Jenis Kelamin + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-24"> + JK - Nomor HP + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-28"> + No HP - Nomor KTP + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-32"> + No KTP + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-24"> Status + class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-32"> Aksi @@ -190,9 +97,15 @@ class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking- {{ $antrian->no_antrian }} {{ $antrian->user?->nama }} - {{ $antrian->user?->alamat }} - - {{ $antrian->user?->jenis_kelamin }} + +
+ {{ Str::limit($antrian->user?->alamat, 30) }} +
+ + + {{ $antrian->user?->jenis_kelamin == 'laki-laki' ? 'L' : 'P' }} {{ $antrian->user?->no_hp }} @@ -208,6 +121,11 @@ class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 te class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800"> Dipanggil + @elseif($antrian->status == 'sedang_diperiksa') + + Sedang Diperiksa + @elseif($antrian->status == 'selesai') @@ -228,17 +146,38 @@ class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded-md text-xs fon Panggil @elseif($antrian->status == 'dipanggil') +
+ @if (!$antrian->waktu_hadir) + + @else + + @endif +
+ + +
+
+ @elseif($antrian->status == 'sedang_diperiksa')
-
@else - @@ -291,6 +230,11 @@ class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 te class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800"> Dipanggil
+ @elseif($antrian->status == 'sedang_diperiksa') + + Sedang Diperiksa + @elseif($antrian->status == 'selesai') @@ -312,17 +256,38 @@ class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded-md text-xs fon Panggil @elseif($antrian->status == 'dipanggil') +
+ @if (!$antrian->waktu_hadir) + + @else + + @endif +
+ + +
+
+ @elseif($antrian->status == 'sedang_diperiksa')
-
@else - @@ -348,7 +313,10 @@ class="w-4 h-4 ml-1 transform transition-transform" fill="none"
Alamat: - {{ $antrian->user?->alamat }} + + {{ Str::limit($antrian->user?->alamat, 50) }} +
Jenis Kelamin: @@ -380,6 +348,11 @@ class="text-gray-600">{{ $antrian->user?->jenis_kelamin }}
@endforelse
+ + +
+ {{ $antrians->links() }} +
@@ -679,6 +652,80 @@ function batal(url, antrianId) { }); } + function konfirmasiKehadiran(antrianId) { + Swal.fire({ + title: 'Konfirmasi Kehadiran Pasien?', + text: 'Pasien sudah datang dan siap untuk diperiksa?', + icon: 'question', + showCancelButton: true, + confirmButtonText: 'Ya, Pasien Hadir', + cancelButtonText: 'Batal' + }).then((res) => { + if (!res.isConfirmed) return; + fetch('{{ route('admin.konfirmasi-kehadiran') }}', { + method: 'POST', + headers: { + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + antrian_id: antrianId + }) + }) + .then(r => r.json()) + .then(d => { + Swal.fire({ + icon: d.success ? 'success' : 'warning', + title: d.success ? 'Berhasil' : 'Gagal', + text: d.message + }) + .then(() => location.reload()); + }) + .catch(() => Swal.fire({ + icon: 'error', + title: 'Error', + text: 'Terjadi kesalahan' + })); + }); + } + + function mulaiPemeriksaan(antrianId) { + Swal.fire({ + title: 'Mulai Pemeriksaan?', + text: 'Pasien akan dipindah ke status sedang diperiksa', + icon: 'question', + showCancelButton: true, + confirmButtonText: 'Ya, Mulai Periksa', + cancelButtonText: 'Batal' + }).then((res) => { + if (!res.isConfirmed) return; + fetch('{{ route('admin.mulai-pemeriksaan') }}', { + method: 'POST', + headers: { + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + antrian_id: antrianId + }) + }) + .then(r => r.json()) + .then(d => { + Swal.fire({ + icon: d.success ? 'success' : 'warning', + title: d.success ? 'Berhasil' : 'Gagal', + text: d.message + }) + .then(() => location.reload()); + }) + .catch(() => Swal.fire({ + icon: 'error', + title: 'Error', + text: 'Terjadi kesalahan' + })); + }); + } + function confirmLogout() { Swal.fire({ title: 'Konfirmasi Logout', @@ -724,4 +771,6 @@ function toggleDetails(antrianId) { } @endpush + + @include('admin.partials.sidebar-script') @endsection diff --git a/resources/views/admin/users/create.blade.php b/resources/views/admin/users/create.blade.php index 9bd52ba..81168ec 100644 --- a/resources/views/admin/users/create.blade.php +++ b/resources/views/admin/users/create.blade.php @@ -5,7 +5,7 @@ @section('content')
-
@@ -232,24 +236,38 @@ class="px-6 py-3 bg-red-600 hover:bg-red-700 text-white rounded-lg font-medium t

Statistik Antrian User

-
+
{{ $user->antrians->where('status', 'menunggu')->count() }}
-
Antrian Menunggu
+
Menunggu
+
+
+
+ {{ $user->antrians->where('status', 'dipanggil')->count() }} +
+
Dipanggil
+
+
+
+ {{ $user->antrians->where('status', 'sedang_diperiksa')->count() }} +
+
Sedang Periksa
{{ $user->antrians->where('status', 'selesai')->count() }}
-
Antrian Selesai
+
Selesai
{{ $user->antrians->where('status', 'batal')->count() }}
-
Antrian Batal
+
Batal
@@ -287,7 +305,7 @@ class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking- - @foreach ($user->antrians->take(5) as $antrian) + @forelse ($user->antrians->take(5) as $antrian) Dipanggil + @elseif($antrian->status == 'sedang_diperiksa') + + Sedang Diperiksa + @elseif($antrian->status == 'selesai') @@ -326,7 +349,21 @@ class="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text- {{ $antrian->created_at ? $antrian->created_at->format('d/m/Y H:i') : 'N/A' }} - @endforeach + @empty + + + + + + +

Belum ada antrian

+

User ini belum pernah mengambil antrian +

+ + + @endforelse
@@ -466,5 +503,100 @@ function togglePassword(inputId) { }); }); }); + + // Auto refresh table riwayat antrian setiap 10 detik + setInterval(function() { + refreshRiwayatAntrian(); + }, 10000); + + function refreshRiwayatAntrian() { + fetch(`{{ route('admin.users.antrian-terbaru', $user->id) }}`, { + method: 'GET', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + updateRiwayatAntrianTable(data.antrians); + } + }) + .catch(error => { + console.log('Error refreshing riwayat antrian:', error); + }); + } + + function updateRiwayatAntrianTable(antrians) { + const tableBody = document.querySelector('tbody'); + if (!tableBody) return; + + if (antrians.length === 0) { + tableBody.innerHTML = ` + + + + + +

Belum ada antrian

+

User ini belum pernah mengambil antrian

+ + + `; + return; + } + + tableBody.innerHTML = antrians.map(antrian => { + let statusBadge = ''; + switch (antrian.status) { + case 'menunggu': + statusBadge = + 'Menunggu'; + break; + case 'dipanggil': + statusBadge = + 'Dipanggil'; + break; + case 'sedang_diperiksa': + statusBadge = + 'Sedang Diperiksa'; + break; + case 'selesai': + statusBadge = + 'Selesai'; + break; + default: + statusBadge = + 'Batal'; + } + + return ` + + + ${antrian.no_antrian} + + + + ${antrian.poli_name} + + + + ${statusBadge} + + + ${antrian.created_at} + + + `; + }).join(''); + + // Add subtle animation to indicate update + tableBody.style.transition = 'opacity 0.3s ease'; + tableBody.style.opacity = '0.7'; + setTimeout(() => { + tableBody.style.opacity = '1'; + }, 300); + } @endsection diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index c5cec9b..f1a6fb7 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -52,8 +52,27 @@ class="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-xl placeholde
+
@@ -122,6 +141,23 @@ class="text-blue-100 hover:text-white text-sm transition duration-200 flex items @push('scripts')