diff --git a/pages/AdminLoket/[id].vue b/pages/AdminLoket/[id].vue index 22282cb..0cf009b 100644 --- a/pages/AdminLoket/[id].vue +++ b/pages/AdminLoket/[id].vue @@ -649,6 +649,17 @@ const allPatientsForStage = computed(() => { })); } + // Sorting: Fast Track first, then by sequence number (no) + combined.sort((a, b) => { + 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; + + return (a.no || 0) - (b.no || 0); + }); + return combined; }); @@ -717,16 +728,24 @@ const nextQueueInfo = computed(() => { const currentPatientNo = currentProcessingPatient.value?.no; const targetLoketId = parseInt(loketId.value); - // Filter diLoketPatients to only show patients for THIS loket - const loketFilteredPatients = (diLoketPatients.value || []).filter((p) => { - // Only show patients assigned to this specific loket + // Filter ONLY di-loket patients assigned to this loket + const eligibleDiLoket = (diLoketPatients.value || []).filter((p) => { return p.loketId && String(p.loketId) === String(targetLoketId); }); + // Sort them as per table (Fast Track, then No) + const sorted = [...eligibleDiLoket].sort((a, b) => { + 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; + + return (a.no || 0) - (b.no || 0); + }); + // Find next patient (excluding current processing patient) - const nextPatient = - loketFilteredPatients.find((p) => p.no !== currentPatientNo) || - loketFilteredPatients[0]; + const nextPatient = sorted.find((p) => p.no !== currentPatientNo); if (nextPatient) { return `Antrian berikutnya: ${nextPatient.noAntrian.split(" |")[0]}`; diff --git a/stores/queueStore.js b/stores/queueStore.js index 43955cd..7ee2f49 100644 --- a/stores/queueStore.js +++ b/stores/queueStore.js @@ -1015,6 +1015,25 @@ export const useQueueStore = defineStore('queue', () => { syncCountersWithState(); // Re-initialize counters after reset }; + /** + * Helper to sort patients for calling/process selection + * Priority: Fast Track ("YA") first, then by sequence number (no) + */ + const sortPatientsForCalling = (patients) => { + if (!patients || !Array.isArray(patients)) return []; + return [...patients].sort((a, b) => { + 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; + + const numA = parseInt(a.no) || 0; + const numB = parseInt(b.no) || 0; + return numA - numB; + }); + }; + /** * Check if patient's payment type is compatible with loket's accepted payments * Maps various payment names to standardized categories @@ -1105,7 +1124,10 @@ export const useQueueStore = defineStore('queue', () => { // PRIORITAS: Hanya ambil pasien dengan status 'menunggu' (Antrean Baru dari Anjungan) // "bukan untuk memanggil pasien tapi tiket baru dari anjungan yang statusnya menunggu" - const nextPatient = eligiblePatients.find(p => p.status === 'menunggu'); + // SEQUENTIAL: Sort by Fast Track and Queue No before picking the first matching patient + const waitingPatients = eligiblePatients.filter(p => p.status === 'menunggu'); + const sortedWaiting = sortPatientsForCalling(waitingPatients); + const nextPatient = sortedWaiting[0]; if (!nextPatient) { return { success: false, message: `Tidak ada antrean baru yang sesuai untuk ${adminType} ${targetId || ''}` }; @@ -1208,7 +1230,8 @@ export const useQueueStore = defineStore('queue', () => { }); // Hanya ambil pasien status 'menunggu' - const menungguList = eligiblePatients.filter(p => p.status === 'menunggu'); + // SEQUENTIAL: Sort by Fast Track and Queue No + const menungguList = sortPatientsForCalling(eligiblePatients.filter(p => p.status === 'menunggu')); if (menungguList.length === 0) { return { success: false, message: `Tidak ada antrean baru yang sesuai untuk ${adminType} ${targetId || ''}` }; @@ -2448,7 +2471,7 @@ export const useQueueStore = defineStore('queue', () => { }; }; - // FIX: Explicit implementation of processNextQueue to prevent cross-loket leakage + // FIX: Explicit implementation of processNextQueue to target ONLY 'di-loket' patients const processNextQueueCorrected = (adminType = 'loket', specificId = null) => { // 1. Determine target stage and key const stageMap = { 'loket': 'loket', 'klinik': 'klinik', 'penunjang': 'penunjang' }; @@ -2457,55 +2480,37 @@ export const useQueueStore = defineStore('queue', () => { console.log(`🚀 [processNextQueue] Processing for ${key} (Stage: ${targetStage})`); - // 2. Find next patient (Status: 'menunggu' -> 'anjungan') - // Prioritaskan yang assigned ke loket ini (loketId) jika ada match - let nextPatient = null; - - if (adminType === 'loket' && specificId) { - // Cari yang spesifik untuk loket ini dulu - nextPatient = allPatients.value.find(p => - p.status === 'menunggu' && - p.processStage === targetStage && - String(p.loketId) === String(specificId) - ); - } - - // Jika tidak ada yang spesifik, cari yang umum (menunggu & loketId null/match) - if (!nextPatient) { - nextPatient = allPatients.value.find(p => - p.status === 'menunggu' && - p.processStage === targetStage && - (adminType !== 'loket' || !p.loketId || String(p.loketId) === String(specificId)) - ); - } - - // Fallback: Check 'anjungan' status if needed (though usually 'menunggu' is for calling) - if (!nextPatient) { - nextPatient = allPatients.value.find(p => - p.status === 'anjungan' && - p.processStage === targetStage && - (adminType !== 'loket' || String(p.loketId) === String(specificId)) - ); - } + // 2. Find next patient ONLY from 'di-loket' status + // These are patients who have already checked in or been moved to 'di-loket' + const eligiblePatients = allPatients.value.filter(p => + p.status === 'di-loket' && + p.processStage === targetStage && + (adminType !== 'loket' || String(p.loketId) === String(specificId)) + ); + + // Exclude the current processing patient to find the TRULY next one + const currentNum = currentProcessingPatient.value[key]?.no; + const waitingDiLoket = eligiblePatients.filter(p => p.no !== currentNum); + + // 3. Sort by priority (Fast Track, then No) + const sorted = sortPatientsForCalling(waitingDiLoket); + const nextPatient = sorted[0]; if (!nextPatient) { - return { success: false, message: "Tidak ada antrean yang menunggu untuk diproses." }; + return { success: false, message: "Tidak ada antrean 'Di Loket' yang menunggu untuk diproses." }; } - // 3. Update Patient Status (di-loket) + // 4. Update Current Processing ISOLATED by key + // We don't change status because it's already 'di-loket' + // But we update the lastCalledAt and set it as current const index = allPatients.value.findIndex(p => p.no === nextPatient.no); if (index !== -1) { const updatedPatient = { ...allPatients.value[index], - status: 'di-loket', - loketId: specificId || allPatients.value[index].loketId, // Ensure loketId is set lastCalledAt: new Date().toISOString() }; allPatients.value[index] = updatedPatient; - syncApiPatientStatus(allPatients.value[index], 'di-loket'); - - // 4. Set Current Processing ISOLATED by key currentProcessingPatient.value[key] = updatedPatient; return {