diff --git a/components/features/queue/CurrentPatientCard.vue b/components/features/queue/CurrentPatientCard.vue index 0191bb6..8784b17 100644 --- a/components/features/queue/CurrentPatientCard.vue +++ b/components/features/queue/CurrentPatientCard.vue @@ -85,6 +85,24 @@
{{ patient.klinik }} | {{ patient.pembayaran }}
+ + +
+ + mdi-skip-next + Panggil Berikutnya + +
+ (Gunakan jika lupa klik Selesai pada pasien ini) +
+
diff --git a/pages/CheckInPasien/checkIn.vue b/pages/CheckInPasien/checkIn.vue index bbc0aa2..9f1959e 100644 --- a/pages/CheckInPasien/checkIn.vue +++ b/pages/CheckInPasien/checkIn.vue @@ -3289,331 +3289,348 @@ const checkInManual = async () => { console.log("🔍 Original input:", originalInput); console.log("📊 Total patients in store:", queueStore.allPatients.length); - // Cari pasien dengan dua metode: - // 1. EXACT barcode match (prioritas tertinggi) - format: YYMMDD + 5 digit (contoh: 26011500001) - // 2. EXACT nomor antrean match - format: RA020, F-RA001, EA013, dll - // Menggunakan matchNoAntrian helper untuk memastikan kode + angka match dengan benar - const foundPatient = queueStore.allPatients.find((p) => { - if (!p) return false; + let foundPatient: any = null; + let freshPatient: any = null; - // Normalize barcode untuk comparison (remove whitespace, case-insensitive) - const patientBarcode = String(p.barcode || "").trim(); + try { + // Cari pasien dengan dua metode: + // ... + foundPatient = queueStore.allPatients.find((p) => { + try { + if (!p) return false; - // 1. EXACT barcode match (case-insensitive, whitespace-insensitive) - PRIORITAS TERTINGGI - if ( - patientBarcode === cleanInput || - patientBarcode.toLowerCase() === cleanInput.toLowerCase() - ) { - console.log( - "✅ Found by exact barcode match:", - patientBarcode, - "===", - cleanInput, + // Normalize barcode untuk comparison (remove whitespace, case-insensitive) + const patientBarcode = String(p.barcode || "").trim(); + + // 1. EXACT barcode match (case-insensitive, whitespace-insensitive) - PRIORITAS TERTINGGI + if ( + patientBarcode === cleanInput || + patientBarcode.toLowerCase() === cleanInput.toLowerCase() + ) { + console.log( + "✅ Found by exact barcode match:", + patientBarcode, + "===", + cleanInput, + ); + return true; + } + + // 2. EXACT nomor antrean match (untuk manual input) + // Menggunakan matchNoAntrian helper untuk memastikan kode + angka match dengan benar + if (p.noAntrian && typeof matchNoAntrian === "function") { + if (matchNoAntrian(originalInput, p.noAntrian)) { + console.log( + "✅ Found by exact nomor antrean match:", + p.noAntrian, + "===", + originalInput, + ); + return true; + } + } + } catch (innerErr) { + console.warn("⚠️ Error in patient find loop for patient:", p, innerErr); + } + return false; + }); + + if (!foundPatient) { + // Tiket belum di-generate (tidak ada di queueStore) + console.error("❌ Patient not found. Input:", inputValue); + console.error("🔍 Extracted barcode/patientId:", searchBarcode); + console.error( + "📋 Available patients (first 10):", + queueStore.allPatients.slice(0, 10).map((p) => ({ + no: p.no, + barcode: p.barcode, + noAntrian: p.noAntrian?.split(" |")[0], + })), ); - return true; - } - // 2. EXACT nomor antrean match (untuk manual input) - // Menggunakan matchNoAntrian helper untuk memastikan kode + angka match dengan benar - // Ini mencegah false positive seperti RA020 terdeteksi sebagai RA002 - if (p.noAntrian && matchNoAntrian(originalInput, p.noAntrian)) { - console.log( - "✅ Found by exact nomor antrean match:", - p.noAntrian, - "===", - originalInput, + saveToHistory({ + patientId: searchBarcode || inputValue, + queueNumber: null, + klinikQueueNumber: null, + pembayaran: "N/A", + status: "failed", + checkInTime: new Date().toISOString(), + checkInDate: new Date().toISOString(), + method: "Manual", + klinik: "N/A", + kodeKlinik: null, + }); + + // RESET lastCheckInResult for failure to avoid success color bleed + lastCheckInResult.value = { + success: false, + patientId: searchBarcode || inputValue, + status: "NOT_FOUND", + }; + + const errorMsg = `❌ Tiket Tidak Ditemukan!\n\nInput: ${inputValue}\n\nTiket dengan nomor antrean atau barcode tersebut tidak ditemukan di sistem.\n\nPastikan:\n- Nomor antrean atau barcode sudah benar\n- Tiket sudah di-generate terlebih dahulu\n- Format input: RA020, F-RA001, atau 26011500001`; + infoMessage.value = errorMsg; + infoAction.value = "checkin"; + infoDialog.value = true; + showSnackbar( + "Error", + "Tiket tidak ditemukan. Pastikan nomor antrean atau barcode sudah benar.", + "error", + "mdi-alert-circle", ); - return true; + return; } - return false; - }); + // CATATAN: Validasi processStage dihapus karena terlalu ketat + // Yang penting adalah status pasien (waiting/pending), bukan processStage + // Pasien yang sudah dipanggil (status: waiting) harus bisa check-in + // meskipun processStage-nya bukan 'loket' (bisa 'klinik' atau 'penunjang') + // Validasi processStage akan dilakukan di checkInPatient() di queueStore + // yang akan mengecek status pasien secara real-time - if (!foundPatient) { - // Tiket belum di-generate (tidak ada di queueStore) - console.error("❌ Patient not found. Input:", inputValue); - console.error("🔍 Extracted barcode/patientId:", searchBarcode); - console.error( - "📋 Available patients (first 10):", - queueStore.allPatients.slice(0, 10).map((p) => ({ - no: p.no, - barcode: p.barcode, - noAntrian: p.noAntrian?.split(" |")[0], - })), - ); + // IMPORTANT: Refresh data pasien dari queueStore untuk mendapatkan status REAL-TIME + // Jangan gunakan foundPatient.status karena mungkin sudah stale + // Cari ulang pasien dari queueStore dengan EXACT match (barcode atau nomor antrean) + freshPatient = queueStore.allPatients.find((p) => { + if (!p) return false; - saveToHistory({ - patientId: searchBarcode || inputValue, - queueNumber: null, - klinikQueueNumber: null, - pembayaran: "N/A", - status: "failed", - checkInTime: new Date().toISOString(), - checkInDate: new Date().toISOString(), - method: "Manual", - klinik: "N/A", - kodeKlinik: null, + // Match by barcode + const patientBarcode = String(p.barcode || "").trim(); + if ( + foundPatient && ( + patientBarcode === foundPatient.barcode || + patientBarcode === searchBarcode || + patientBarcode === cleanInput + ) + ) { + return true; + } + + // Match by nomor antrean (jika input adalah nomor antrean) + if (foundPatient && p.noAntrian && p.no === foundPatient.no) { + return true; + } + + return false; }); - // RESET lastCheckInResult for failure to avoid success color bleed - lastCheckInResult.value = { - success: false, - patientId: searchBarcode || inputValue, - status: "NOT_FOUND", - }; + if (!freshPatient) { + console.error("❌ Patient not found in queueStore after refresh"); + saveToHistory({ + patientId: foundPatient.barcode, + queueNumber: foundPatient.noAntrian, + klinikQueueNumber: + foundPatient.noAntrian?.split(" |")[0] || foundPatient.noAntrian, + pembayaran: foundPatient.pembayaran || "N/A", + status: "failed", + checkInTime: new Date().toISOString(), + checkInDate: new Date().toISOString(), + method: "Manual", + klinik: foundPatient.klinik || "N/A", + kodeKlinik: foundPatient.kodeKlinik || null, + }); - const errorMsg = `❌ Tiket Tidak Ditemukan!\n\nInput: ${inputValue}\n\nTiket dengan nomor antrean atau barcode tersebut tidak ditemukan di sistem.\n\nPastikan:\n- Nomor antrean atau barcode sudah benar\n- Tiket sudah di-generate terlebih dahulu\n- Format input: RA020, F-RA001, atau 26011500001`; - infoMessage.value = errorMsg; - infoAction.value = "checkin"; - infoDialog.value = true; + // RESET lastCheckInResult for failure + lastCheckInResult.value = { + success: false, + patientId: foundPatient.barcode, + status: "REFRESH_NOT_FOUND", + }; + + const errorMsg = `❌ Pasien tidak ditemukan di sistem setelah refresh data.`; + infoMessage.value = errorMsg; + infoAction.value = "checkin"; + infoDialog.value = true; + showSnackbar( + "Error", + "Pasien tidak ditemukan di sistem", + "error", + "mdi-alert-circle", + ); + return; + } + + // Gunakan data pasien yang fresh untuk validasi + const patientStatus = freshPatient.status; + const klinikQueueNumber = + freshPatient.noAntrian?.split(" |")[0] || freshPatient.noAntrian || "N/A"; + + console.log("🔍 Fresh patient status:", { + barcode: freshPatient.barcode, + noAntrian: freshPatient.noAntrian, + status: patientStatus, + processStage: freshPatient.processStage, + }); + + // Cek apakah sudah check-in (status === 'di-loket') - gunakan data fresh + if (patientStatus === "di-loket") { + // Sudah check-in sebelumnya + saveToHistory({ + patientId: freshPatient.barcode, + queueNumber: freshPatient.noAntrian, + klinikQueueNumber: klinikQueueNumber, + pembayaran: freshPatient.pembayaran || "N/A", + status: "failed", + checkInTime: new Date().toISOString(), + checkInDate: new Date().toISOString(), + method: "Manual", + klinik: freshPatient.klinik || "N/A", + kodeKlinik: freshPatient.kodeKlinik || null, + }); + + lastCheckInResult.value = { + success: false, + patientId: freshPatient.barcode, + status: "ALREADY_CHECKED_IN", + }; + + const errorMsg = `⚠️ Sudah Check-in!\n\nNomor Antrean ${klinikQueueNumber} sudah melakukan check-in sebelumnya.`; + infoMessage.value = errorMsg; + infoAction.value = "checkin"; + infoDialog.value = true; + showSnackbar( + "Warning", + `Tiket ${klinikQueueNumber} sudah check-in sebelumnya`, + "warning", + "mdi-check-circle", + ); + return; + } + + if (patientStatus === "menunggu") { + // Status "menunggu" = belum dipanggil oleh admin loket, belum muncul di AntrianLoket + // TIDAK BOLEH check-in + saveToHistory({ + patientId: freshPatient.barcode, + queueNumber: freshPatient.noAntrian, + klinikQueueNumber: klinikQueueNumber, + pembayaran: freshPatient.pembayaran || "N/A", + status: "NOT_ALLOWED", + checkInTime: new Date().toISOString(), + checkInDate: new Date().toISOString(), + method: "Manual", + klinik: freshPatient.klinik || "N/A", + kodeKlinik: freshPatient.kodeKlinik || null, + }); + + lastCheckInResult.value = { + success: false, + patientId: freshPatient.barcode, + status: "NOT_ALLOWED", + }; + + const errorMsg = `⏳ Belum Diizinkan Check-in\n\nNomor Antrean ${klinikQueueNumber} belum dipanggil oleh admin loket. Mohon menunggu hingga nomor antrean Anda dipanggil dan muncul di layar Antrian Loket.`; + infoMessage.value = errorMsg; + infoAction.value = "kembali"; + infoDialog.value = true; + showSnackbar( + "Info", + `Tiket ${klinikQueueNumber} belum dipanggil. Mohon menunggu.`, + "info", + "mdi-clock-alert", + ); + return; + } + + // Status "waiting" atau "pending" → BOLEH check-in + // Panggil checkInPatient untuk validasi dan update status ke "di-loket" + // Gunakan barcode pasien yang fresh, bukan inputValue (untuk memastikan format benar) + const patientBarcodeForCheckIn = + freshPatient.barcode || searchBarcode || inputValue; + console.log( + "🔍 Calling checkInPatient with barcode (fresh):", + patientBarcodeForCheckIn, + ); + console.log( + "🔍 Original input:", + inputValue, + "| Extracted barcode:", + searchBarcode, + ); + const checkInResult = await queueStore.checkInPatient( + patientBarcodeForCheckIn, + ); + + if (checkInResult.success && checkInResult.patient) { + // Check-in berhasil + const successKlinikQueueNumber = + checkInResult.patient.noAntrian?.split(" |")[0] || + checkInResult.patient.noAntrian || + "N/A"; + + lastCheckInResult.value = { + success: true, + patientId: checkInResult.patient.barcode, + status: "ALLOWED", + }; + + saveToHistory({ + patientId: checkInResult.patient.barcode, + queueNumber: checkInResult.patient.noAntrian, + klinikQueueNumber: successKlinikQueueNumber, + pembayaran: checkInResult.patient.pembayaran || "N/A", + status: "success", + checkInTime: new Date().toISOString(), + checkInDate: new Date().toISOString(), + method: "Manual", + klinik: checkInResult.patient.klinik || "N/A", + kodeKlinik: checkInResult.patient.kodeKlinik || null, + }); + + const successMsg = `✅ Check-in Berhasil!\n\nPasien ${successKlinikQueueNumber} berhasil melakukan check-in dan status berubah menjadi di loket.`; + infoMessage.value = successMsg; + infoAction.value = "checkin"; + infoDialog.value = true; + showSnackbar( + "Berhasil", + `Check-in manual berhasil. Tiket ${successKlinikQueueNumber} siap diproses.`, + "success", + "mdi-check-circle", + ); + + manualInput.value = ""; + if (manualForm.value) { + (manualForm.value as any).reset(); + } + } else { + // Check-in gagal (misalnya validasi di checkInPatient gagal) + saveToHistory({ + patientId: foundPatient.barcode, + queueNumber: foundPatient.noAntrian, + klinikQueueNumber: klinikQueueNumber, + pembayaran: foundPatient.pembayaran || "N/A", + status: "failed", + checkInTime: new Date().toISOString(), + checkInDate: new Date().toISOString(), + method: "Manual", + klinik: foundPatient.klinik || "N/A", + kodeKlinik: foundPatient.kodeKlinik || null, + }); + + lastCheckInResult.value = { + success: false, + patientId: foundPatient.barcode, + status: "FAILED", + }; + + const errorMsg = `❌ Check-in Gagal!\n\n${checkInResult?.message || "Status pasien tidak memungkinkan untuk check-in."}`; + infoMessage.value = errorMsg; + infoAction.value = "checkin"; + infoDialog.value = true; + showSnackbar( + "Gagal", + checkInResult?.message || "Check-in manual gagal dilakukan", + "error", + "mdi-close-circle", + ); + } + } catch (error) { + console.error("❌ Fatal Error in checkInManual:", error); showSnackbar( - "Error", - "Tiket tidak ditemukan. Pastikan nomor antrean atau barcode sudah benar.", + "Fatal Error", + "Terjadi kesalahan sistem saat check-in manual", "error", - "mdi-alert-circle", - ); - return; - } - - // CATATAN: Validasi processStage dihapus karena terlalu ketat - // Yang penting adalah status pasien (waiting/pending), bukan processStage - // Pasien yang sudah dipanggil (status: waiting) harus bisa check-in - // meskipun processStage-nya bukan 'loket' (bisa 'klinik' atau 'penunjang') - // Validasi processStage akan dilakukan di checkInPatient() di queueStore - // yang akan mengecek status pasien secara real-time - - // IMPORTANT: Refresh data pasien dari queueStore untuk mendapatkan status REAL-TIME - // Jangan gunakan foundPatient.status karena mungkin sudah stale - // Cari ulang pasien dari queueStore dengan EXACT match (barcode atau nomor antrean) - const freshPatient = queueStore.allPatients.find((p) => { - if (!p) return false; - - // Match by barcode - const patientBarcode = String(p.barcode || "").trim(); - if ( - patientBarcode === foundPatient.barcode || - patientBarcode === searchBarcode || - patientBarcode === cleanInput - ) { - return true; - } - - // Match by nomor antrean (jika input adalah nomor antrean) - if (p.noAntrian && p.no === foundPatient.no) { - return true; - } - - return false; - }); - - if (!freshPatient) { - console.error("❌ Patient not found in queueStore after refresh"); - saveToHistory({ - patientId: foundPatient.barcode, - queueNumber: foundPatient.noAntrian, - klinikQueueNumber: - foundPatient.noAntrian?.split(" |")[0] || foundPatient.noAntrian, - pembayaran: foundPatient.pembayaran || "N/A", - status: "failed", - checkInTime: new Date().toISOString(), - checkInDate: new Date().toISOString(), - method: "Manual", - klinik: foundPatient.klinik || "N/A", - kodeKlinik: foundPatient.kodeKlinik || null, - }); - - // RESET lastCheckInResult for failure - lastCheckInResult.value = { - success: false, - patientId: foundPatient.barcode, - status: "REFRESH_NOT_FOUND", - }; - - const errorMsg = `❌ Pasien tidak ditemukan di sistem setelah refresh data.`; - infoMessage.value = errorMsg; - infoAction.value = "checkin"; - infoDialog.value = true; - showSnackbar( - "Error", - "Pasien tidak ditemukan di sistem", - "error", - "mdi-alert-circle", - ); - return; - } - - // Gunakan data pasien yang fresh untuk validasi - const patientStatus = freshPatient.status; - const klinikQueueNumber = - freshPatient.noAntrian?.split(" |")[0] || freshPatient.noAntrian || "N/A"; - - console.log("🔍 Fresh patient status:", { - barcode: freshPatient.barcode, - noAntrian: freshPatient.noAntrian, - status: patientStatus, - processStage: freshPatient.processStage, - }); - - // Cek apakah sudah check-in (status === 'di-loket') - gunakan data fresh - if (patientStatus === "di-loket") { - // Sudah check-in sebelumnya - saveToHistory({ - patientId: freshPatient.barcode, - queueNumber: freshPatient.noAntrian, - klinikQueueNumber: klinikQueueNumber, - pembayaran: freshPatient.pembayaran || "N/A", - status: "failed", - checkInTime: new Date().toISOString(), - checkInDate: new Date().toISOString(), - method: "Manual", - klinik: freshPatient.klinik || "N/A", - kodeKlinik: freshPatient.kodeKlinik || null, - }); - - lastCheckInResult.value = { - success: false, - patientId: freshPatient.barcode, - status: "ALREADY_CHECKED_IN", - }; - - const errorMsg = `⚠️ Sudah Check-in!\n\nNomor Antrean ${klinikQueueNumber} sudah melakukan check-in sebelumnya.`; - infoMessage.value = errorMsg; - infoAction.value = "checkin"; - infoDialog.value = true; - showSnackbar( - "Warning", - `Tiket ${klinikQueueNumber} sudah check-in sebelumnya`, - "warning", - "mdi-check-circle", - ); - return; - } - - if (patientStatus === "menunggu") { - // Status "menunggu" = belum dipanggil oleh admin loket, belum muncul di AntrianLoket - // TIDAK BOLEH check-in - saveToHistory({ - patientId: freshPatient.barcode, - queueNumber: freshPatient.noAntrian, - klinikQueueNumber: klinikQueueNumber, - pembayaran: freshPatient.pembayaran || "N/A", - status: "NOT_ALLOWED", - checkInTime: new Date().toISOString(), - checkInDate: new Date().toISOString(), - method: "Manual", - klinik: freshPatient.klinik || "N/A", - kodeKlinik: freshPatient.kodeKlinik || null, - }); - - lastCheckInResult.value = { - success: false, - patientId: freshPatient.barcode, - status: "NOT_ALLOWED", - }; - - const errorMsg = `⏳ Belum Diizinkan Check-in\n\nNomor Antrean ${klinikQueueNumber} belum dipanggil oleh admin loket. Mohon menunggu hingga nomor antrean Anda dipanggil dan muncul di layar Antrian Loket.`; - infoMessage.value = errorMsg; - infoAction.value = "kembali"; - infoDialog.value = true; - showSnackbar( - "Info", - `Tiket ${klinikQueueNumber} belum dipanggil. Mohon menunggu.`, - "info", - "mdi-clock-alert", - ); - return; - } - - // Status "waiting" atau "pending" → BOLEH check-in - // Panggil checkInPatient untuk validasi dan update status ke "di-loket" - // Gunakan barcode pasien yang fresh, bukan inputValue (untuk memastikan format benar) - const patientBarcodeForCheckIn = - freshPatient.barcode || searchBarcode || inputValue; - console.log( - "🔍 Calling checkInPatient with barcode (fresh):", - patientBarcodeForCheckIn, - ); - console.log( - "🔍 Original input:", - inputValue, - "| Extracted barcode:", - searchBarcode, - ); - const checkInResult = await queueStore.checkInPatient( - patientBarcodeForCheckIn, - ); - - if (checkInResult.success && checkInResult.patient) { - // Check-in berhasil - const successKlinikQueueNumber = - checkInResult.patient.noAntrian?.split(" |")[0] || - checkInResult.patient.noAntrian || - "N/A"; - - lastCheckInResult.value = { - success: true, - patientId: checkInResult.patient.barcode, - status: "ALLOWED", - }; - - saveToHistory({ - patientId: checkInResult.patient.barcode, - queueNumber: checkInResult.patient.noAntrian, - klinikQueueNumber: successKlinikQueueNumber, - pembayaran: checkInResult.patient.pembayaran || "N/A", - status: "success", - checkInTime: new Date().toISOString(), - checkInDate: new Date().toISOString(), - method: "Manual", - klinik: checkInResult.patient.klinik || "N/A", - kodeKlinik: checkInResult.patient.kodeKlinik || null, - }); - - const successMsg = `✅ Check-in Berhasil!\n\nPasien ${successKlinikQueueNumber} berhasil melakukan check-in dan status berubah menjadi di loket.`; - infoMessage.value = successMsg; - infoAction.value = "checkin"; - infoDialog.value = true; - showSnackbar( - "Berhasil", - `Check-in manual berhasil. Tiket ${successKlinikQueueNumber} siap diproses.`, - "success", - "mdi-check-circle", - ); - - manualInput.value = ""; - if (manualForm.value) { - (manualForm.value as any).reset(); - } - } else { - // Check-in gagal (misalnya validasi di checkInPatient gagal) - saveToHistory({ - patientId: foundPatient.barcode, - queueNumber: foundPatient.noAntrian, - klinikQueueNumber: klinikQueueNumber, - pembayaran: foundPatient.pembayaran || "N/A", - status: "failed", - checkInTime: new Date().toISOString(), - checkInDate: new Date().toISOString(), - method: "Manual", - klinik: foundPatient.klinik || "N/A", - kodeKlinik: foundPatient.kodeKlinik || null, - }); - - lastCheckInResult.value = { - success: false, - patientId: foundPatient.barcode, - status: "FAILED", - }; - - const errorMsg = `❌ Check-in Gagal!\n\n${checkInResult.message || "Status pasien tidak memungkinkan untuk check-in."}`; - infoMessage.value = errorMsg; - infoAction.value = "checkin"; - infoDialog.value = true; - showSnackbar( - "Gagal", - checkInResult.message || "Check-in manual gagal dilakukan", - "error", - "mdi-close-circle", + "mdi-alert-octagon", ); } };