diff --git a/composables/useWebSocket.ts b/composables/useWebSocket.ts index 7a37e72..317800a 100644 --- a/composables/useWebSocket.ts +++ b/composables/useWebSocket.ts @@ -72,8 +72,6 @@ export const useWebSocket = (config: WebSocketConfig) => { } const connectionUrl = wsUrl.value - console.log('🔌 Connecting to WebSocket:', connectionUrl) - ws.value = new WebSocket(connectionUrl) ws.value.onopen = () => { @@ -166,9 +164,6 @@ export const useWebSocket = (config: WebSocketConfig) => { postUrl = `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}` } - console.log('📡 POST URL:', postUrl) - console.log('đŸ“Ļ POST Body:', JSON.stringify(message, null, 2)) - const response = await fetch(postUrl, { method: 'POST', headers: { @@ -177,20 +172,13 @@ export const useWebSocket = (config: WebSocketConfig) => { body: JSON.stringify(message), }) - console.log('đŸ“Ĩ POST Response status:', response.status, response.statusText) - if (!response.ok) { const errorText = await response.text().catch(() => 'Unknown error') console.error('❌ POST Error response:', errorText) throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`) } - const result = await response.json().catch(() => { - console.log('â„šī¸ Response is not JSON, assuming success') - return { success: true } - }) - - console.log('✅ POST Response data:', result) + const result = await response.json().catch(() => ({ success: true })) return result } catch (error) { console.error('❌ Error sending message via POST:', error) diff --git a/pages/CheckInPasien/checkIn.vue b/pages/CheckInPasien/checkIn.vue index a6dbf26..86b0396 100644 --- a/pages/CheckInPasien/checkIn.vue +++ b/pages/CheckInPasien/checkIn.vue @@ -2439,11 +2439,8 @@ const stopScanning = async () => { }; const handleQRScanSuccess = (decodedText: string) => { - console.log("đŸŽ¯ QR Scan Success! Data:", decodedText); - // Guard: Jika sedang proses atau dialog sedang terbuka, abaikan scan if (isProcessingScan.value || infoDialog.value) { - console.log("â­ī¸ Scan diabaikan: Sedang memproses atau dialog terbuka"); return; } @@ -3015,20 +3012,14 @@ const onDetect = async (decodedText: string) => { // 2. Format testing: "patientId|status" (contoh: P-00001|ALLOWED) // Status akan dicek real-time dari queueStore saat scan QR - console.log("🔍 onDetect called with QR data:", decodedText); - // Extract barcode/patientId dari QR data (handle format testing dengan pipe) const qrData = extractQRData(decodedText); - const searchBarcode = qrData.barcode; // Barcode/patientId untuk dicari di queueStore + const searchBarcode = qrData.barcode; // Clean input - remove whitespace and normalize const cleanInput = String(searchBarcode).trim(); const cleanInputUpper = cleanInput.toUpperCase(); - console.log("🔍 Extracted barcode/patientId:", searchBarcode); - console.log("🔍 Cleaned input:", cleanInput); - console.log("📊 Total patients in store:", queueStore.allPatients.length); - // IMPORTANT: Hanya cari dengan EXACT barcode match untuk menghindari false positive // Format barcode: YYMMDD + 5 digit (contoh: 26011500001) // Jangan gunakan fallback ke noAntrian atau no karena bisa menyebabkan false positive @@ -3045,12 +3036,6 @@ const onDetect = async (decodedText: string) => { patientBarcode === cleanInput || patientBarcode.toLowerCase() === cleanInput.toLowerCase() ) { - console.log( - "✅ Found by exact barcode match:", - patientBarcode, - "===", - cleanInput, - ); return true; } @@ -3148,13 +3133,6 @@ const onDetect = async (decodedText: string) => { 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 @@ -3230,16 +3208,6 @@ const onDetect = async (decodedText: string) => { // Gunakan barcode pasien yang fresh, bukan decodedText (untuk memastikan format benar) const patientBarcodeForCheckIn = freshPatient.barcode || searchBarcode || decodedText; - console.log( - "🔍 Calling checkInPatient with barcode (fresh):", - patientBarcodeForCheckIn, - ); - console.log( - "🔍 Original QR data:", - decodedText, - "| Extracted barcode:", - searchBarcode, - ); const checkInResult = await queueStore.checkInPatient( patientBarcodeForCheckIn, ); @@ -3381,12 +3349,6 @@ const checkInManual = async () => { const cleanInputUpper = cleanInput.toUpperCase(); const originalInput = inputValue.toUpperCase().trim(); - console.log("🔍 checkInManual called with:", inputValue); - console.log("🔍 Extracted barcode/patientId:", searchBarcode); - console.log("🔍 Cleaned input:", cleanInput); - console.log("🔍 Original input:", originalInput); - console.log("📊 Total patients in store:", queueStore.allPatients.length); - let foundPatient: any = null; let freshPatient: any = null; @@ -3405,12 +3367,6 @@ const checkInManual = async () => { patientBarcode === cleanInput || patientBarcode.toLowerCase() === cleanInput.toLowerCase() ) { - console.log( - "✅ Found by exact barcode match:", - patientBarcode, - "===", - cleanInput, - ); return true; } diff --git a/stores/clinicStore.js b/stores/clinicStore.js index f34d1e0..ef18c84 100644 --- a/stores/clinicStore.js +++ b/stores/clinicStore.js @@ -3,7 +3,6 @@ import { defineStore } from 'pinia'; import { ref, computed } from 'vue'; export const useClinicStore = defineStore('clinic', () => { - console.log('đŸĒ Initializing clinicStore...'); // Data clinics - Single source of truth untuk semua data klinik // Includes basic info (name, kode, icon, doctors, shifts) + master config (totalQuota, jamShiftPerHari, jadwalKlinik, tanggalTutup) @@ -588,8 +587,6 @@ export const useClinicStore = defineStore('clinic', () => { }, ]); - console.log('📊 Initial clinics count:', clinics.value.length); - console.log('📋 Initial clinics:', clinics.value.map(c => ({ id: c.id, name: c.name, jenisLayanan: c.jenisLayanan }))); const isLoadingAPI = ref(false); const apiError = ref(null); @@ -638,14 +635,11 @@ export const useClinicStore = defineStore('clinic', () => { // Helper: Parse jam_operasional to create jamShiftPerHari structure const parseJamOperasional = (jamOperasional) => { - console.log('🔄 Parsing jam operasional:', jamOperasional); - const jamShiftPerHari = {}; // { 'Senin': [{ dari, sampai, kuota }], 'Selasa': [...] } const jadwalKlinikSet = new Set(); let totalQuota = 0; if (!jamOperasional) { - console.log('âš ī¸ No jamOperasional provided, using defaults'); return { jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 100 }] @@ -657,7 +651,6 @@ export const useClinicStore = defineStore('clinic', () => { } if (!Array.isArray(jamOperasional)) { - console.log('âš ī¸ jamOperasional is not an array:', typeof jamOperasional); return { jamShiftPerHari: { 'Senin': [{ dari: '07:00', sampai: '11:00', kuota: 100 }] @@ -668,21 +661,10 @@ export const useClinicStore = defineStore('clinic', () => { }; } - console.log(`📋 Processing ${jamOperasional.length} schedule items`); - // Group by day and create shift per day structure - jamOperasional.forEach((item, index) => { - console.log(` 📍 Schedule ${index + 1}:`, { - hari: item.hari, - jam_operasional: item.jam_operasional, - kuota: item.kuota - }); - + jamOperasional.forEach((item) => { const dayIndo = mapDayToIndonesian(item.hari); - if (!dayIndo) { - console.log(` âš ī¸ Could not map day: ${item.hari}`); - return; - } + if (!dayIndo) return; jadwalKlinikSet.add(dayIndo); @@ -707,28 +689,18 @@ export const useClinicStore = defineStore('clinic', () => { } } - console.log(` ✅ Shift added for ${dayIndo}: ${dari} - ${sampai}, kuota: ${kuota}`); - // Initialize array if not exists if (!jamShiftPerHari[dayIndo]) { jamShiftPerHari[dayIndo] = []; } - jamShiftPerHari[dayIndo].push({ - dari, - sampai, - kuota - }); - + jamShiftPerHari[dayIndo].push({ dari, sampai, kuota }); totalQuota += kuota; - } else { - console.log(` âš ī¸ Invalid jam_operasional format: "${jamOp}"`); } }); // If no valid shift found, add default if (Object.keys(jamShiftPerHari).length === 0) { - console.log('âš ī¸ No valid shifts found, using default'); jamShiftPerHari['Senin'] = [{ dari: '07:00', sampai: '11:00', kuota: 100 }]; jadwalKlinikSet.add('Senin'); totalQuota = 100; @@ -737,16 +709,12 @@ export const useClinicStore = defineStore('clinic', () => { // Count total unique shifts across all days const totalShifts = Object.values(jamShiftPerHari).reduce((sum, shifts) => sum + shifts.length, 0); - const result = { + return { jamShiftPerHari, jadwalKlinik: Array.from(jadwalKlinikSet), totalQuota, shift: totalShifts }; - - console.log('✅ Parse result:', result); - - return result; }; // State for singleton fetch promise @@ -772,7 +740,7 @@ export const useClinicStore = defineStore('clinic', () => { apiError.value = null; try { - console.log(`🔄 Fetching clinics from API (Try ${retryCount + 1})...`); + console.log(`🔄 [clinicStore] Fetching clinics from API (Try ${retryCount + 1})...`); let rawData; try { @@ -879,7 +847,6 @@ export const useClinicStore = defineStore('clinic', () => { // Finally sort by ID if both are same return a.id - b.id; }); - console.log('📊 getAllClinics computed:', sorted.length, 'items'); return sorted; }); diff --git a/stores/doctorStore.js b/stores/doctorStore.js index 59ab35f..e2f5e23 100644 --- a/stores/doctorStore.js +++ b/stores/doctorStore.js @@ -6,6 +6,12 @@ export const useDoctorStore = defineStore('doctor', () => { const doctorsByKlinikId = ref({}); const lastSyncTimestamp = ref(null); const loadingDoctors = ref({}); + + // Track clinics that consistently return 500 errors to avoid hammering the server + // Format: { [idklinik]: { failCount: number, lastFailAt: number } } + const failedClinics = ref({}); + const MAX_FAIL_COUNT = 3; // Blacklist after 3 consecutive failures + const BLACKLIST_DURATION_MS = 30 * 60 * 1000; // 30 minutes /** * Check if sync is needed based on 2 AM schedule @@ -38,11 +44,24 @@ export const useDoctorStore = defineStore('doctor', () => { /** * Fetch doctors for a clinic from API with retry logic */ - const fetchDoctorsForClinic = async (clinic, force = false, retries = 2) => { + const fetchDoctorsForClinic = async (clinic, force = false, retries = 1) => { if (!clinic || !clinic.id) return; const idklinik = clinic.id; + // Check blacklist: skip if this clinic has failed too many times recently + const failInfo = failedClinics.value[idklinik]; + if (failInfo && failInfo.failCount >= MAX_FAIL_COUNT) { + const timeSinceLastFail = Date.now() - failInfo.lastFailAt; + if (timeSinceLastFail < BLACKLIST_DURATION_MS) { + // Still in blacklist window — skip silently + return doctorsByKlinikId.value[idklinik] || []; + } else { + // Blacklist expired — reset and try again + delete failedClinics.value[idklinik]; + } + } + // Skip if already in cache and not forced, and sync not needed if (!force && doctorsByKlinikId.value[idklinik] && !isSyncNeeded()) { return doctorsByKlinikId.value[idklinik]; @@ -50,8 +69,6 @@ export const useDoctorStore = defineStore('doctor', () => { // Skip if already loading if (loadingDoctors.value[idklinik]) { - // Wait a bit if already loading? No, just return existing promise if we had one, - // but for simplicity we just return. return; } @@ -111,6 +128,17 @@ export const useDoctorStore = defineStore('doctor', () => { } catch (error) { const status = error.response ? error.response.status : (error.status || 'unknown'); console.error(`❌ [doctorStore] Gagal mengambil dokter untuk klinik ID ${idklinik} (${clinic.name}) - Status: ${status}`); + + // Track failure for blacklisting + if (!failedClinics.value[idklinik]) { + failedClinics.value[idklinik] = { failCount: 0, lastFailAt: 0 }; + } + failedClinics.value[idklinik].failCount++; + failedClinics.value[idklinik].lastFailAt = Date.now(); + + if (failedClinics.value[idklinik].failCount >= MAX_FAIL_COUNT) { + console.warn(`âš ī¸ [doctorStore] Klinik ${clinic.name} (ID: ${idklinik}) diblacklist selama 30 menit karena ${MAX_FAIL_COUNT}x gagal berturut-turut.`); + } // Don't overwrite existing data on failure unless it's empty if (!doctorsByKlinikId.value[idklinik]) { @@ -145,6 +173,7 @@ export const useDoctorStore = defineStore('doctor', () => { doctorsByKlinikId, lastSyncTimestamp, loadingDoctors, + failedClinics, fetchDoctorsForClinic, syncAllDoctors, isSyncNeeded diff --git a/stores/loketStore.js b/stores/loketStore.js index 24b0b6b..7ff81a1 100644 --- a/stores/loketStore.js +++ b/stores/loketStore.js @@ -110,22 +110,12 @@ export const useLoketStore = defineStore('loket', () => { // Merged data (computed) - Gabungan API + Local const loketData = computed(() => { - // Debug log - if (apiLoketData.value.length > 0) { - console.log('📊 [loketData computed] API data found:', apiLoketData.value.length, 'items'); - } - if (localLoketData.value.length > 0) { - console.log('📊 [loketData computed] Local data found:', localLoketData.value.length, 'items'); - } - // Create new objects to prevent state mutation in computed const merged = [ ...apiLoketData.value.map(i => ({...i})), ...localLoketData.value.map(i => ({...i})) ] - console.log('📊 [loketData computed] Merged total:', merged.length, 'items'); - // Sort by ID merged.sort((a, b) => a.id - b.id) @@ -544,9 +534,9 @@ export const useLoketStore = defineStore('loket', () => { key: 'loket-store-state', storage: typeof window !== 'undefined' ? localStorage : undefined, paths: [ - 'localLoketData', // Persist EKSEKUTIF data (ID 1000+) - 'apiLoketData', // Persist API data (REGULER) to minimize data loss - 'lastSyncTimestamp' // Track when API data was last fetched + 'localLoketData', // Persist EKSEKUTIF data (ID 1000+) only + // NOTE: apiLoketData & lastSyncTimestamp are intentionally NOT persisted + // API data is always fetched fresh on load to save localStorage writes ], serializer: { deserialize: JSON.parse, diff --git a/stores/queueStore.js b/stores/queueStore.js index d017aa7..d49f884 100644 --- a/stores/queueStore.js +++ b/stores/queueStore.js @@ -36,7 +36,6 @@ export const useQueueStore = defineStore('queue', () => { if (!loketId) return; const id = String(loketId); activeLoketInterest.value[id] = (activeLoketInterest.value[id] || 0) + 1; - console.log(`🔌 [queueStore] Registered interest in Loket ${id}. Active:`, activeLoketInterest.value); }; const unregisterInterest = (loketId) => { @@ -48,14 +47,12 @@ export const useQueueStore = defineStore('queue', () => { delete activeLoketInterest.value[id]; } } - console.log(`🔌 [queueStore] Unregistered interest in Loket ${id}. Active lokets:`, activeLoketInterest.value); }; const registerClinicInterest = (kodeKlinik) => { if (!kodeKlinik) return; const code = String(kodeKlinik); activeClinicInterest.value[code] = (activeClinicInterest.value[code] || 0) + 1; - console.log(`🔌 [queueStore] Registered interest in Clinic ${code}. Active clinics:`, activeClinicInterest.value); }; const unregisterClinicInterest = (kodeKlinik) => { @@ -67,17 +64,14 @@ export const useQueueStore = defineStore('queue', () => { delete activeClinicInterest.value[code]; } } - console.log(`🔌 [queueStore] Unregistered interest in Clinic ${code}. Active clinics:`, activeClinicInterest.value); }; const registerGlobalInterest = () => { globalInterestCount.value++; - console.log(`🌐 [queueStore] Global interest registered. Total: ${globalInterestCount.value}`); }; const unregisterGlobalInterest = () => { globalInterestCount.value = Math.max(0, globalInterestCount.value - 1); - console.log(`🌐 [queueStore] Global interest unregistered. Total: ${globalInterestCount.value}`); }; const fetchPatientsForClinic = async (kodeKlinik, force = false) => { @@ -304,10 +298,7 @@ export const useQueueStore = defineStore('queue', () => { const targetLoketId = messageData?.loketId || messageData?.idloket; const targetKlinikId = messageData?.klinikId || messageData?.idklinik; - console.log('📨 [queueStore] Global WS Message received Type:', typeof messageData); - console.log('📨 [queueStore] Global WS Message content:', JSON.stringify(messageData)); - - // Handle Call Events (cross-device sync for "Sedang Dipanggil" popup + // Handle Call Events and WS messages if (messageData?.triggerRefresh) { if (messageData.klinikId) { console.log(`🔄 [queueStore] Received refresh trigger for clinic ${messageData.klinikId}`); @@ -335,7 +326,6 @@ export const useQueueStore = defineStore('queue', () => { } } if (messageData?.callEvent) { - console.log('📞 [queueStore] Call event received:', messageData.callEvent); lastGlobalCall.value = messageData.callEvent; } @@ -381,53 +371,39 @@ export const useQueueStore = defineStore('queue', () => { ); } } - // TRIGGER STRATEGIC REFRESHES let refreshedSomething = false; if (targetLoketId) { - // 1. If message specifies a loket, refresh that one specifically (Force bypass throttle) - console.log(`đŸŽ¯ [queueStore] WS targeting Loket ${targetLoketId}: Refreshing (FORCED)...`); fetchPatientsForLoket(targetLoketId, true); refreshedSomething = true; } if (targetKlinikId) { - // 2. If message specifies a klinik, refresh those clinics (Force bypass throttle) const interestingClinics = Object.keys(activeClinicInterest.value); if (interestingClinics.includes(String(targetKlinikId)) || targetKlinikId === 'broadcast') { const clinicToFetch = targetKlinikId === 'broadcast' ? interestingClinics[0] : targetKlinikId; - console.log(`đŸŽ¯ [queueStore] WS targeting Clinic ${targetKlinikId}: Refreshing ${clinicToFetch} (FORCED)...`); fetchPatientsForClinic(clinicToFetch, true); refreshedSomething = true; } } - // 3. Global interest refresh (Force bypass throttle) if (globalInterestCount.value > 0) { - console.log(`📡 [queueStore] WS trigger: Global Interest active, triggering bulk refresh (FORCED)...`); - fetchAllPatients(); // Note: fetchAllPatients might need force internal too, but typically calls fetchPatientsForClinic + fetchAllPatients(); refreshedSomething = true; } - // 4. Fallback: If nothing specific was refreshed but we have generic interest, refresh all active things (FORCED) if (!refreshedSomething) { const interestingLokets = Object.keys(activeLoketInterest.value); const interestingClinics = Object.keys(activeClinicInterest.value); if (interestingLokets.length > 0) { - console.log(`🌐 [queueStore] WS trigger fallback: Refreshing ${interestingLokets.length} active lokets (FORCED):`, interestingLokets); - interestingLokets.forEach(loketId => { - fetchPatientsForLoket(loketId, true); - }); + interestingLokets.forEach(loketId => { fetchPatientsForLoket(loketId, true); }); refreshedSomething = true; } if (interestingClinics.length > 0) { - console.log(`🌐 [queueStore] WS trigger fallback: Refreshing ${interestingClinics.length} active clinics (FORCED):`, interestingClinics); - interestingClinics.forEach(kodeKlinik => { - fetchPatientsForClinic(kodeKlinik, true); - }); + interestingClinics.forEach(kodeKlinik => { fetchPatientsForClinic(kodeKlinik, true); }); refreshedSomething = true; } } @@ -1009,7 +985,34 @@ const fetchPatientsForLoket = async (loketId, force = false) => { } } - console.log(`✅ [queueStore] Staggered bulk fetch completed. Total: ${allPatients.value.length} patients in memory.`); + // Trim allPatients if it grows too large (keep processed patients manageable) + trimPatients(); + }; + + /** + * PHASE 2: Cap allPatients to prevent unbounded memory growth. + * Removes 'processed' patients first, then oldest non-critical ones if still over limit. + */ + const MAX_PATIENTS_IN_MEMORY = 3000; + const trimPatients = () => { + if (allPatients.value.length <= MAX_PATIENTS_IN_MEMORY) return; + + // 1. Remove 'processed' patients first (they are done) + const activePatients = allPatients.value.filter(p => p.status !== 'processed'); + + if (activePatients.length <= MAX_PATIENTS_IN_MEMORY) { + allPatients.value = activePatients; + return; + } + + // 2. If still over limit, keep the MAX most recent by createdAt + const sorted = [...activePatients].sort((a, b) => { + const aTime = a.createdAt ? new Date(a.createdAt).getTime() : 0; + const bTime = b.createdAt ? new Date(b.createdAt).getTime() : 0; + return bTime - aTime; // Newest first + }); + allPatients.value = sorted.slice(0, MAX_PATIENTS_IN_MEMORY); + console.warn(`[queueStore] allPatients trimmed to ${MAX_PATIENTS_IN_MEMORY} entries to prevent memory leak.`); }; /** @@ -1585,30 +1588,24 @@ const fetchPatientsForLoket = async (loketId, force = false) => { * Handles complex names like "UMUM / JKMM / SPM / DLL" */ const isPaymentCompatible = (patientPayment, loketPayments) => { - if (!loketPayments || loketPayments.length === 0) return true; // No restriction + if (!loketPayments || loketPayments.length === 0) return true; if (!patientPayment) return false; const normalizedPatient = String(patientPayment).toUpperCase().trim(); - // Map patient payment to category const patientCategory = (normalizedPatient.includes('JKN') || normalizedPatient.includes('BPJS')) ? 'JKN' : normalizedPatient.includes('EKSEKUTIF') || normalizedPatient.includes('VIP') ? 'EKSEKUTIF' : - 'UMUM'; // Default to UMUM for non-JKN, non-EKSEKUTIF + 'UMUM'; - // Check if loket accepts this category return loketPayments.some(lp => { const normalized = String(lp).toUpperCase().trim(); - - const result = (normalized === patientCategory) || + return (normalized === patientCategory) || (patientCategory === 'UMUM' && (normalized.includes('UMUM') || normalized.includes('MANDIRI'))) || (patientCategory === 'JKN' && (normalized.includes('JKN') || normalized.includes('BPJS'))) || (patientCategory === 'EKSEKUTIF' && (normalized.includes('EKSEKUTIF') || normalized.includes('VIP'))) || (normalized === 'BPJS' && patientCategory === 'JKN') || (normalized === 'JKN' && patientCategory === 'JKN'); - - console.log(` - Checking loket accepts "${lp}" (normalized: "${normalized}") vs patient category "${patientCategory}": ${result}`); - return result; }); }; @@ -1943,66 +1940,36 @@ const fetchPatientsForLoket = async (loketId, force = false) => { idklinikstatus2: "2" }; - console.log('📤 [TERLAMBAT] Sending API request:', payload); - // POST to external API and WAIT for response try { const response = await fetch('http://10.10.123.140:8089/api/v1/tiket/update', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); - console.log('đŸ“Ĩ [TERLAMBAT] API response status:', response.status); - - // Read response body const responseData = await response.json().catch(() => ({})); - console.log('đŸ“Ĩ [TERLAMBAT] API response data:', responseData); if (response.ok) { - console.log(`✅ [TERLAMBAT] API accepted request`); - // Add delay to ensure database is updated - console.log('âŗ [TERLAMBAT] Waiting 500ms for database update...'); await new Promise(resolve => setTimeout(resolve, 500)); // REFRESH data from database to get updated status - console.log(`🔄 [TERLAMBAT] Refreshing data from database...`); await fetchPatientsForLoket(loketId); - // Verify status after refresh const updatedPatient = allPatients.value.find(p => p.barcode === patient.barcode); - console.log('🔍 [TERLAMBAT] Patient after refresh:', updatedPatient); if (updatedPatient?.status === 'terlambat') { - // API successfully saved status - console.log('✅ [TERLAMBAT] Status persisted in database!'); message = `Pasien ${patientCode} ditandai terlambat`; } else { - // API didn't save status - use LocalStorage fallback console.warn('âš ī¸ [TERLAMBAT] API did not persist status. Using LocalStorage fallback.'); - - // Save to LocalStorage const storageKey = `patient-status-${patient.barcode}`; - localStorage.setItem(storageKey, JSON.stringify({ - status: 'terlambat', - timestamp: Date.now(), - barcode: patient.barcode - })); - - // Manually update local state since API didn't save + localStorage.setItem(storageKey, JSON.stringify({ status: 'terlambat', timestamp: Date.now(), barcode: patient.barcode })); const patientIndex = allPatients.value.findIndex(p => p.barcode === patient.barcode); if (patientIndex !== -1) { - allPatients.value[patientIndex] = { - ...allPatients.value[patientIndex], - status: 'terlambat', - calledByAdmin: false - }; + allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: 'terlambat', calledByAdmin: false }; syncApiPatientStatus(allPatients.value[patientIndex], 'terlambat'); } - message = `Pasien ${patientCode} ditandai terlambat (local)`; } } else { @@ -2035,66 +2002,33 @@ const fetchPatientsForLoket = async (loketId, force = false) => { idklinikstatus2: "2" }; - console.log('📤 [PENDING] Sending API request:', payload); - // POST to external API and WAIT for response try { const response = await fetch('http://10.10.123.140:8089/api/v1/tiket/update', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); - console.log('đŸ“Ĩ [PENDING] API response status:', response.status); - - // Read response body const responseData = await response.json().catch(() => ({})); - console.log('đŸ“Ĩ [PENDING] API response data:', responseData); if (response.ok) { - console.log(`✅ [PENDING] API accepted request`); - - // Add delay to ensure database is updated - console.log('âŗ [PENDING] Waiting 500ms for database update...'); await new Promise(resolve => setTimeout(resolve, 500)); - - // REFRESH data from database to get updated status - console.log(`🔄 [PENDING] Refreshing data from database...`); await fetchPatientsForLoket(loketId); - // Verify status after refresh const updatedPatient = allPatients.value.find(p => p.barcode === patient.barcode); - console.log('🔍 [PENDING] Patient after refresh:', updatedPatient); if (updatedPatient?.status === 'pending') { - // API successfully saved status - console.log('✅ [PENDING] Status persisted in database!'); message = `Pasien ${patientCode} di-pending`; } else { - // API didn't save status - use LocalStorage fallback console.warn('âš ī¸ [PENDING] API did not persist status. Using LocalStorage fallback.'); - - // Save to LocalStorage const storageKey = `patient-status-${patient.barcode}`; - localStorage.setItem(storageKey, JSON.stringify({ - status: 'pending', - timestamp: Date.now(), - barcode: patient.barcode - })); - - // Manually update local state since API didn't save + localStorage.setItem(storageKey, JSON.stringify({ status: 'pending', timestamp: Date.now(), barcode: patient.barcode })); const patientIndex = allPatients.value.findIndex(p => p.barcode === patient.barcode); if (patientIndex !== -1) { - allPatients.value[patientIndex] = { - ...allPatients.value[patientIndex], - status: 'pending', - calledByAdmin: false - }; + allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: 'pending', calledByAdmin: false }; syncApiPatientStatus(allPatients.value[patientIndex], 'pending'); } - message = `Pasien ${patientCode} di-pending (local)`; } } else { @@ -2291,13 +2225,9 @@ const fetchPatientsForLoket = async (loketId, force = false) => { const targetLoket = allLokets.find(l => l.pelayanan && Array.isArray(l.pelayanan) && l.pelayanan.includes(newPatient.kodeKlinik) ); - if (targetLoket) { newPatient.loketId = targetLoket.id; newPatient.loket = targetLoket.namaLoket; - console.log(`✅ Auto-assigned Ticket ${newPatient.noAntrian} to Loket ${targetLoket.id} (${targetLoket.namaLoket})`); - } else { - console.log(`âš ī¸ No specific Loket found for Clinic ${newPatient.kodeKlinik}, defaulting to general pool.`); } } @@ -2364,8 +2294,7 @@ const fetchPatientsForLoket = async (loketId, force = false) => { // Use API ticket number if available, otherwise generate locally if (apiTicketNumber) { - newNoAntrian = apiTicketNumber; // Use ticket from API (e.g., "PK001") - console.log('đŸŽĢ Using API ticket number:', newNoAntrian); + newNoAntrian = apiTicketNumber; } else { // 1. Ambil huruf pertama dari nama klinik/poli const firstLetter = klinikRuang.namaKlinik.charAt(0).toUpperCase(); @@ -3001,51 +2930,23 @@ const fetchPatientsForLoket = async (loketId, force = false) => { */ const registerRegulerPatientViaApi = async (clinic, paymentType, visitType = 'SEKARANG', isFastTrack = false, fastTrackData = null) => { try { - console.log('🔄 [queueStore] Generating ticket via API for REGULER patient...'); - console.log('📋 [queueStore] Payment Type:', paymentType, '| Clinic:', clinic.name, '| Clinic Code:', clinic.kode); - // 1. Find appropriate idloket based on BOTH clinic code AND payment type - // IMPROVED: Check specifically for API-source lokets (id < 1000) const apiLoketsExist = loketStore.lokets?.some(l => l.source === 'api' || l.id < 1000); if (!apiLoketsExist) { - console.log('🔄 [queueStore] API loket data empty, fetching from API...'); await loketStore.fetchLoketFromAPI(); } const allLokets = loketStore.lokets || []; - - // Determine payment type for matching (normalize BPJS to JKN for API compatibility) const paymentTypeForMatching = paymentType === "BPJS" ? "JKN" : paymentType; - console.log('🔍 [queueStore] Searching lokets...'); - console.log(' Total lokets:', allLokets.length); - console.log(' API lokets (id < 1000):', allLokets.filter(l => l.source === 'api' && l.id < 1000).length); - console.log(' Looking for clinic:', clinic.kode, 'payment:', paymentTypeForMatching); - // Find loket that handles BOTH the clinic AND the payment type const targetLoket = allLokets.find(l => { - // Check source: only use API lokets (id < 1000), not local eksekutif if (l.source !== 'api' || l.id >= 1000) return false; - - // Check if loket handles the clinic const handlesClinic = l.pelayanan && Array.isArray(l.pelayanan) && l.pelayanan.includes(clinic.kode); - if (handlesClinic) { - // Debug: This loket handles the clinic, now check payment - console.log(` ✓ Loket ${l.id} (${l.namaLoket}) handles clinic ${clinic.kode}`); - console.log(` Pembayaran array:`, l.pembayaran); - - // NEW: Use isPaymentCompatible helper for robust payment matching - const acceptsPayment = isPaymentCompatible(paymentTypeForMatching, l.pembayaran); - console.log(` Accepts payment ${paymentTypeForMatching}:`, acceptsPayment); - - if (acceptsPayment) { - console.log(` ✅ MATCH FOUND: Loket ${l.id}`); - return true; - } + return isPaymentCompatible(paymentTypeForMatching, l.pembayaran); } - return false; }); @@ -3065,8 +2966,7 @@ const fetchPatientsForLoket = async (loketId, force = false) => { }; } - console.log('đŸŽ¯ [queueStore] Target Loket:', `${targetLoket.namaLoket} (ID: ${targetLoket.id})`, '| Accepts Payment:', targetLoket.pembayaran); - + // 2. Determine idpembayaran based on paymentType const idloket = String(targetLoket.id); // BPJS (JKN) = "2", UMUM and others = "1" @@ -3100,7 +3000,6 @@ const fetchPatientsForLoket = async (loketId, force = false) => { } const result = await response.json(); - console.log('đŸ“Ĩ [queueStore] API Response:', result); if (result.metadata && result.metadata.code !== 200) { return { success: false, message: result.message || 'Gagal generate barcode via API' }; @@ -3158,7 +3057,6 @@ const fetchPatientsForLoket = async (loketId, force = false) => { allPatients.value.push(newPatient); // NOTE: Kita tidak perlu incrementBarcodeCounter() di sini karena barcode berasal dari API - console.log(`✅ [queueStore] Successfully registered REGULER patient via API: ${noAntrian}`); return { success: true, @@ -3182,8 +3080,6 @@ const fetchPatientsForLoket = async (loketId, force = false) => { */ const checkInPatientViaApi = async (barcode) => { try { - console.log(`🔄 [queueStore] Syncing check-in via API for barcode: ${barcode}...`); - const body = { barcode: barcode, statuspasien: "5", @@ -3192,13 +3088,9 @@ const fetchPatientsForLoket = async (loketId, force = false) => { idklinikstatus2: "2" }; - console.log('📤 [queueStore] Check-in API Body:', body); - const response = await fetch('http://10.10.123.140:8089/api/v1/tiket/checkin', { method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); @@ -3207,7 +3099,6 @@ const fetchPatientsForLoket = async (loketId, force = false) => { } const result = await response.json(); - console.log('đŸ“Ĩ [queueStore] Check-in API Response:', result); return { success: true, @@ -3229,53 +3120,21 @@ const fetchPatientsForLoket = async (loketId, force = false) => { // Format barcode: YYMMDD + 5 digit (contoh: 26011500001) // Jangan gunakan fallback ke noAntrian atau no karena bisa menyebabkan false positive const checkInPatient = async (patientIdOrBarcode) => { - console.log('🔍 checkInPatient called with:', patientIdOrBarcode); - console.log('📊 Total patients in store:', allPatients.value.length); - // Clean input - remove whitespace and normalize const cleanInput = String(patientIdOrBarcode).trim(); - // PRIORITAS: Hanya cari dengan EXACT barcode match (case-insensitive, whitespace-insensitive) - // Format barcode: YYMMDD + 5 digit (contoh: 26011500001) - // Ini adalah satu-satunya cara yang aman untuk match pasien const patientIndex = allPatients.value.findIndex(p => { - // Normalize barcode untuk comparison const patientBarcode = String(p.barcode || '').trim(); - - // EXACT barcode match (case-insensitive, whitespace-insensitive) - // Ini adalah satu-satunya cara yang aman untuk match pasien - if (patientBarcode === cleanInput || - patientBarcode.toLowerCase() === cleanInput.toLowerCase()) { - console.log('✅ Found by exact barcode match:', patientBarcode, '===', cleanInput); - return true; - } - - return false; + return patientBarcode === cleanInput || patientBarcode.toLowerCase() === cleanInput.toLowerCase(); }); if (patientIndex === -1) { - console.log('❌ Patient not found. Searched for barcode:', cleanInput); - console.log('📋 Available barcodes (first 10):', allPatients.value.slice(0, 10).map(p => ({ - no: p.no, - barcode: p.barcode, - noAntrian: p.noAntrian?.split(' |')[0] - }))); return { success: false, message: `Pasien dengan barcode ${cleanInput} tidak ditemukan. Pastikan barcode benar (format: YYMMDD + 5 digit, contoh: 26011500001).` }; } - // IMPORTANT: Get fresh patient data from array to avoid stale data - // Use the patientIndex we found earlier, but get fresh data from array const patient = allPatients.value[patientIndex]; - console.log('✅ Patient found (fresh):', { - no: patient.no, - barcode: patient.barcode, - noAntrian: patient.noAntrian, - status: patient.status, - processStage: patient.processStage - }); // Only allow check-in if status is anjungan (sudah dipanggil) or pending - // Pasien dengan status "menunggu" belum bisa check-in (belum dipanggil) if (patient.status === 'menunggu') { console.log('âš ī¸ Patient status is menunggu (belum dipanggil):', patient.status); return {