diff --git a/pages/AdminKlinikRuang/[kodeKlinik].vue b/pages/AdminKlinikRuang/[kodeKlinik].vue index 5ffcdf4..844db15 100644 --- a/pages/AdminKlinikRuang/[kodeKlinik].vue +++ b/pages/AdminKlinikRuang/[kodeKlinik].vue @@ -1062,53 +1062,55 @@ const getFilteredAndSortedPatientsForRoom = (ruang) => { } } - // Default sorting - berdasarkan createdAt dari nomor tiket awal + // Default sorting return patients.sort((a, b) => { - // Prioritaskan yang sudah digenerate/diproses & pending di atas - const aHasTiket = !!a.tipeLayanan; - const bHasTiket = !!b.tipeLayanan; + // 1. Prioritaskan Fast Track (Selalu paling atas di status yang sama) + const aFT = (a.fastTrack ?? "").toString().trim().toUpperCase() === "YA"; + const bFT = (b.fastTrack ?? "").toString().trim().toUpperCase() === "YA"; + if (aFT && !bFT) return -1; + if (!aFT && bFT) return 1; + + // 2. Prioritaskan yang sudah diproses (punya tipeLayanan) + const aHasTiket = !!a.tipeLayanan || a.status === 'pemeriksaan'; + const bHasTiket = !!b.tipeLayanan || b.status === 'pemeriksaan'; // Jika satu sudah punya tiket dan yang lain belum, yang sudah tiket di atas if (aHasTiket && !bHasTiket) return -1; if (!aHasTiket && bHasTiket) return 1; - // Jika keduanya sudah punya tiket atau keduanya belum, sort by status + // 3. Sort berdasarkan status priority const statusPriority = { - 'di-loket': 1, - 'waiting': 2, - 'terlambat': 3, - 'pending': 4 + 'pemeriksaan': 1, + 'di-loket': 2, + 'menunggu': 3, + 'waiting': 3, // Fallback for local/legacy data + 'terlambat': 4, + 'pending': 5 }; const priorityDiff = (statusPriority[a.status] || 99) - (statusPriority[b.status] || 99); if (priorityDiff !== 0) return priorityDiff; - // Sort by createdAt dari nomor tiket awal (referencePatient) - // Jika ada referencePatient, cari createdAt dari pasien awal + // 4. Sort by createdAt dari nomor tiket awal (referencePatient) let createdAtA = a.createdAt; let createdAtB = b.createdAt; if (a.referencePatient) { const sourceA = queueStore.allPatients.find(p => p.noAntrian === a.referencePatient); - if (sourceA?.createdAt) { - createdAtA = sourceA.createdAt; - } + if (sourceA?.createdAt) createdAtA = sourceA.createdAt; } if (b.referencePatient) { const sourceB = queueStore.allPatients.find(p => p.noAntrian === b.referencePatient); - if (sourceB?.createdAt) { - createdAtB = sourceB.createdAt; - } + if (sourceB?.createdAt) createdAtB = sourceB.createdAt; } - // Sort by createdAt (yang lebih awal di atas) if (createdAtA && createdAtB) { const dateA = new Date(createdAtA).getTime(); const dateB = new Date(createdAtB).getTime(); if (dateA !== dateB) return dateA - dateB; } - // Fallback: Sort by time + // 5. Fallback: Sort by time const timeA = a.jamPanggil?.split(':').map(Number) || [0, 0]; const timeB = b.jamPanggil?.split(':').map(Number) || [0, 0]; return timeA[0] * 60 + timeA[1] - (timeB[0] * 60 + timeB[1]); @@ -1636,9 +1638,11 @@ const broadcastUpdate = async () => { const getStatusColor = (status) => { const colors = { - 'di-loket': 'success-600', + 'pemeriksaan': 'success-600', + 'di-loket': 'warning-600', + 'menunggu': 'primary-600', 'waiting': 'primary-600', - 'terlambat': 'primary-600', + 'terlambat': 'secondary-600', 'pending': 'danger-600' }; return colors[status] || 'grey'; @@ -1647,9 +1651,11 @@ const getStatusColor = (status) => { const getStatusLabel = (status) => { const labels = { 'di-loket': 'Di Loket', + 'menunggu': 'Menunggu', 'waiting': 'Menunggu', 'terlambat': 'Terlambat', - 'pending': 'Pending' + 'pending': 'Pending', + 'pemeriksaan': 'Pemeriksaan' }; return labels[status] || status; }; @@ -1671,7 +1677,7 @@ const handleCallPatientByTipe = async (ruang, tipeLayanan) => { const updateData = { ...queueStore.allPatients[patientIndex], - status: 'di-loket', + status: 'pemeriksaan', tipeLayanan: tipeLayanan, lastCalledAt: new Date().toISOString(), lastCalledTipeLayanan: tipeLayanan diff --git a/stores/queueStore.js b/stores/queueStore.js index d3ba86e..99988c9 100644 --- a/stores/queueStore.js +++ b/stores/queueStore.js @@ -131,8 +131,18 @@ export const useQueueStore = defineStore('queue', () => { const latestStatus = visitStatuses[visitStatuses.length - 1]; let patientStatus = 'di-loket'; - if (latestStatus?.desc) { - const desc = latestStatus.desc.toLowerCase(); + + // Map status from database ID if available + if (latestStatus?.fk_ref_visit_status_id) { + const statusId = String(latestStatus.fk_ref_visit_status_id); + if (PATIENT_STATUS_MAP[statusId]) { + patientStatus = PATIENT_STATUS_MAP[statusId]; + } + } + + // Fallback to description search + if (patientStatus === 'di-loket' && (latestStatus?.visit_status_description || latestStatus?.desc)) { + const desc = (latestStatus.visit_status_description || latestStatus.desc).toLowerCase(); if (desc.includes('pemeriksaan') || desc.includes('sedang diproses')) { patientStatus = 'pemeriksaan'; } @@ -155,7 +165,7 @@ export const useQueueStore = defineStore('queue', () => { createdAt: visit.registration_datetime || new Date().toISOString(), visitType: visit.visit_type_name || 'ONSITE', noRM: visit.norm || '', - fastTrack: 'TIDAK', + fastTrack: (service.ticket && String(service.ticket).startsWith('F-')) ? "YA" : "TIDAK", registrationType: 'api', visitId: visit.visit_id || visit.id, visitCode: visit.visit_code, @@ -202,32 +212,44 @@ export const useQueueStore = defineStore('queue', () => { const key = p.visitId ? `vid-${p.visitId}` : (p.barcode ? `bc-${p.barcode}` : `no-${p.no}`); const apiP = newPatientMap.get(key); - - if (apiP) { - // If we have local updates, preserve them - const hasLocalUpdates = ( - p.status !== 'di-loket' || - p.calledPemeriksaanAwal || - p.calledTindakan || - p.lastCalledAt - ); - - if (hasLocalUpdates) { + if (apiP) { newPatientMap.delete(key); // Mark as handled - return { + + // Preserve fastTrack and F- prefix if existing patient has it + const isFastTrack = (p.fastTrack === "YA" || (p.noAntrian && p.noAntrian.startsWith('F-'))); + let finalNoAntrian = apiP.noAntrian; + if (isFastTrack && !finalNoAntrian.startsWith('F-')) { + finalNoAntrian = `F-${finalNoAntrian}`; + } + + const mergedP = { ...apiP, - status: p.status, - calledPemeriksaanAwal: p.calledPemeriksaanAwal, - calledTindakan: p.calledTindakan, - tipeLayanan: p.tipeLayanan, - lastCalledAt: p.lastCalledAt, - lastCalledTipeLayanan: p.lastCalledTipeLayanan + fastTrack: isFastTrack ? "YA" : apiP.fastTrack, + noAntrian: finalNoAntrian }; - } else { - newPatientMap.delete(key); // Handled - return apiP; + + // If we have local updates, preserve them + const hasLocalUpdates = ( + p.status !== 'di-loket' || + p.calledPemeriksaanAwal || + p.calledTindakan || + p.lastCalledAt + ); + + if (hasLocalUpdates) { + return { + ...mergedP, + status: p.status, + calledPemeriksaanAwal: p.calledPemeriksaanAwal, + calledTindakan: p.calledTindakan, + tipeLayanan: p.tipeLayanan, + lastCalledAt: p.lastCalledAt, + lastCalledTipeLayanan: p.lastCalledTipeLayanan + }; + } else { + return mergedP; + } } - } // If it was an API patient that disappeared from the list, it's either stale or from another room if (p.registrationType === 'api') return null; @@ -413,22 +435,45 @@ export const useQueueStore = defineStore('queue', () => { * idvisit 5, 6 = di-loket (PS CHECK-IN, TP LOKET) */ const PATIENT_STATUS_MAP = { - 1: 'menunggu', - 2: 'menunggu', - 3: 'anjungan', - 4: 'anjungan', - 5: 'di-loket', - 6: 'di-loket', - 28: 'pending', // PE PENDAFTARAN - 29: 'terlambat', // TR PENDAFTARAN - "1": 'menunggu', - "2": 'menunggu', - "3": 'anjungan', - "4": 'anjungan', - "5": 'di-loket', - "6": 'di-loket', - "28": 'pending', // PE PENDAFTARAN - "29": 'terlambat' // TR PENDAFTARAN + // Stage: ANJUNGAN (1-5, 25-27) + 1: 'menunggu', // CT ANJUNGAN + 2: 'menunggu', // RT ANJUNGAN + 3: 'anjungan', // PG ANJUNGAN + 4: 'anjungan', // RT CHECK-IN + 5: 'di-loket', // PS CHECK-IN + 25: 'menunggu', // BT ANJUNGAN + 26: 'pending', // PE ANJUNGAN + 27: 'terlambat', // TE ANJUNGAN + + // Stage: LOKET (6-9, 28-29) + 6: 'di-loket', // RT PENDAFTARAN + 7: 'anjungan', // PG PENDAFTARAN + 8: 'di-loket', // PO PENDAFTARAN + 9: 'di-loket', // PS PENDAFTARAN + 28: 'pending', // PE PENDAFTARAN + 29: 'terlambat', // TR PENDAFTARAN + + // Stage: VERIFIKASI (10-13, 30-31) + 10: 'di-loket', + 11: 'anjungan', + 12: 'di-loket', + 13: 'di-loket', + 30: 'pending', + 31: 'terlambat', + + // Stage: PELAYANAN (14-19, 32-33) + 14: 'pemeriksaan', // CT PEMERIKSAAN + 15: 'pemeriksaan', // RT PEMERIKSAAN + 16: 'pemeriksaan', // PG PEMERIKSAAN AWAL + 17: 'pemeriksaan', // PG PEMERIKSAAN + 18: 'pemeriksaan', // PO PEMERIKSAAN + 19: 'pemeriksaan', // PS PEMERIKSAAN + 32: 'pending', // PE PEMERIKSAAN + 33: 'terlambat', // TR PEMERIKSAAN + + // String versions for robustness + "1": 'menunggu', "2": 'menunggu', "3": 'anjungan', "4": 'anjungan', "5": 'di-loket', + "6": 'di-loket', "14": 'pemeriksaan', "15": 'pemeriksaan', "28": 'pending', "29": 'terlambat' }; /** @@ -517,7 +562,7 @@ export const useQueueStore = defineStore('queue', () => { shift: apiPatient.shift ? `Shift ${apiPatient.shift}` : '', klinik: apiPatient.klinik || '', kodeKlinik: apiPatient.klinik || '', // Will be mapped later - fastTrack: "TIDAK", + fastTrack: (apiPatient.ticket && String(apiPatient.ticket).startsWith('F-')) ? "YA" : "TIDAK", pembayaran: apiPatient.pembayaran || '', status: status, processStage: 'loket', @@ -678,7 +723,7 @@ export const useQueueStore = defineStore('queue', () => { } }); - // 3. Update new patients with preserved status + // 3. Update new patients with preserved status and fasttrack patientsWithLoketId.forEach(p => { const key = p.idtiket ? `id-${p.idtiket}` : `bc-${p.barcode}`; if (preservedStatuses.has(key)) { @@ -725,25 +770,11 @@ export const useQueueStore = defineStore('queue', () => { return true; } - // CRITICAL FIX: Protect terlambat and pending status from being overwritten - // API might have lag in updating these statuses, so keep local version - if ((p.status === 'terlambat' || p.status === 'pending') && newPatientMap.has(key)) { - console.log(`🛡️ [queueStore] Protecting ${p.status} patient ${p.noAntrian} from API overwrite`); - newPatientMap.delete(key); - return true; - } - - // Remove if it's being replaced by new API batch - if (newPatientMap.has(key)) return false; - - // Also check if barcode matches any new API patient - if (p.barcode) { - const barcodeMatches = patientsWithLoketId.some(newP => newP.barcode === p.barcode); - if (barcodeMatches) return false; - } - // Remove if it's a stale 'api' patient for this loket - if (p.registrationType === 'api' && String(p.loketId) === String(loketId)) return false; + if (p.registrationType === 'api' && String(p.loketId) === String(loketId)) { + // But only if it's NOT in the new batch (stale) + if (!newPatientMap.has(key)) return false; + } return true; }); @@ -767,15 +798,25 @@ export const useQueueStore = defineStore('queue', () => { const existingPrio = statusPriority[existing.status] || 0; const newPrio = statusPriority[newPatient.status] || 0; - // Only update if new status has higher priority (e.g., di-loket > terlambat) + // Only update if new status has higher or equal priority // This prevents API data from overwriting LocalStorage terlambat/pending status - if (newPrio > existingPrio) { + if (newPrio >= existingPrio) { console.log(`🔄 [queueStore] Updating patient ${newPatient.barcode} from ${existing.status} to ${newPatient.status}`); + + // Preserve fastTrack and F- prefix if existing patient has it + const isFastTrack = (existing.fastTrack === "YA" || (existing.noAntrian && existing.noAntrian.startsWith('F-'))); + let finalNoAntrian = newPatient.noAntrian; + if (isFastTrack && !finalNoAntrian.startsWith('F-')) { + finalNoAntrian = `F-${finalNoAntrian}`; + } + allPatients.value[existingIndex] = { ...newPatient, // Preserve some local state calledByAdmin: existing.calledByAdmin, - lastCalledAt: existing.lastCalledAt + lastCalledAt: existing.lastCalledAt, + fastTrack: isFastTrack ? "YA" : newPatient.fastTrack, + noAntrian: finalNoAntrian }; } else { console.log(`🛡️ [queueStore] Protecting patient ${existing.barcode} with status ${existing.status} from API overwrite (${newPatient.status})`);