From 48e4aabd4ba76afe6121dd09711bc5a617bf7bd1 Mon Sep 17 00:00:00 2001 From: Fanrouver Date: Mon, 12 Jan 2026 15:35:28 +0700 Subject: [PATCH] push loket perbaikan sedang dilayani --- pages/Anjungan/AntrianLoket/[id].vue | 163 +++++++++++++++++---------- stores/queueStore.js | 132 +++++++++++++++++----- 2 files changed, 205 insertions(+), 90 deletions(-) diff --git a/pages/Anjungan/AntrianLoket/[id].vue b/pages/Anjungan/AntrianLoket/[id].vue index 60a21fe..8e2bb3f 100644 --- a/pages/Anjungan/AntrianLoket/[id].vue +++ b/pages/Anjungan/AntrianLoket/[id].vue @@ -66,6 +66,7 @@ >
{{ ticket.noAntrian.split(' |')[0] }}
{{ ticket.klinik || 'Klinik' }}
+
{{ ticket.loket }}
mdi-timer {{ getTimerText(ticket) }} @@ -373,10 +374,20 @@ const displayedClinics = computed(() => { // Flatten multiple call groups untuk mendapatkan semua multiple calls const allMultipleCalls = multipleCallGroups.flat() - // Current queue adalah yang sedang dilayani (prioritas: di-loket, lalu waiting yang paling lama) - const currentQueue = sortedQueues.find(q => q.status === 'di-loket') || - sortedQueues.find(q => q.status === 'waiting') || - null + // Current queue adalah yang sedang dilayani untuk klinik ini + // Hanya ambil yang status 'di-loket' (yang sedang dilayani) + let currentQueue = null + + // Cari yang sedang di-loket (sedang dilayani) + const diLoketQueues = sortedQueues.filter(q => q.status === 'di-loket') + if (diLoketQueues.length > 0) { + // Ambil yang paling lama (berdasarkan waktu check-in atau createdAt) + currentQueue = diLoketQueues.sort((a, b) => { + const checkInA = getCheckInTime(a) + const checkInB = getCheckInTime(b) + return checkInA - checkInB // Yang lebih lama lebih dulu + })[0] + } // Multiple calls untuk display - semua yang dipanggil bersamaan (termasuk yang sudah di-loket) const multipleCalls = allMultipleCalls.filter(q => @@ -508,9 +519,22 @@ const getTimerText = (queue) => { const _ = currentTime.value // Force reactivity - const callTime = queue.lastCalledAt ? new Date(queue.lastCalledAt) : (queue.createdAt ? new Date(queue.createdAt) : null) + // Gunakan lastCalledAt atau pendingCallAt untuk waktu panggilan + const callTime = queue.lastCalledAt ? new Date(queue.lastCalledAt) : + (queue.pendingCallAt ? new Date(queue.pendingCallAt) : + (queue.createdAt ? new Date(queue.createdAt) : null)) if (!callTime) return '' + // Jika status pending-call, hitung waktu sampai dipanggil (5 detik) + if (queue.status === 'pending-call') { + const elapsed = Date.now() - callTime.getTime() + const remaining = Math.max(0, 5000 - elapsed) + if (remaining <= 0) return '' + const seconds = Math.ceil(remaining / 1000) + return `Dipanggil dalam ${seconds}s` + } + + // Jika sudah dipanggil, hitung waktu text-to-speech (15 detik) const ttsDuration = 15000 // 15 detik const elapsed = Date.now() - callTime.getTime() const remaining = Math.max(0, ttsDuration - elapsed) @@ -521,14 +545,72 @@ const getTimerText = (queue) => { return `${seconds}s` } -// Next 5 tickets to be called (tercepat) +// Next 5 tickets to be called (untuk urutan text-to-speech ketika ada multiple loket memanggil bersamaan) const nextTicketsToCall = computed(() => { - // Ambil semua antrian dengan status 'menunggu' (belum dipanggil) atau 'waiting' (sudah dipanggil tapi belum check-in) - // Prioritaskan yang belum dipanggil (menunggu) dulu + // Ambil semua antrian dari SEMUA loket yang memiliki status 'waiting' atau 'pending-call' (sudah dipanggil atau akan dipanggil) + // Ini untuk menampilkan urutan text-to-speech ketika ada 2+ loket memanggil bersamaan + const allWaitingFromAllLokets = queueStore.allPatients.filter(p => + (p.status === 'waiting' || p.status === 'pending-call') && (p.lastCalledAt || p.pendingCallAt) + ) + + // Group berdasarkan waktu panggilan (dalam 5 detik = multiple calls) + const callGroups = [] + const processed = new Set() + + allWaitingFromAllLokets.forEach((queue) => { + if (processed.has(queue.no)) return + + // Gunakan lastCalledAt atau pendingCallAt untuk waktu panggilan + const callTime = new Date(queue.lastCalledAt || queue.pendingCallAt) + const group = [queue] + processed.add(queue.no) + + // Cari semua tiket yang dipanggil dalam 5 detik + allWaitingFromAllLokets.forEach((otherQueue) => { + if (!processed.has(otherQueue.no) && otherQueue.no !== queue.no) { + const otherCallTime = new Date(otherQueue.lastCalledAt || otherQueue.pendingCallAt) + const timeDiff = Math.abs(callTime - otherCallTime) + if (timeDiff <= 5000) { + group.push(otherQueue) + processed.add(otherQueue.no) + } + } + }) + + if (group.length > 1) { + // Sort dalam group berdasarkan waktu panggilan (siapa yang dipanggil duluan) + group.sort((a, b) => { + const timeA = new Date(a.lastCalledAt || a.pendingCallAt) + const timeB = new Date(b.lastCalledAt || b.pendingCallAt) + return timeA - timeB + }) + callGroups.push(...group) + } + }) + + // Jika ada multiple calls, ambil 5 teratas dari yang dipanggil bersamaan + // Urutkan berdasarkan waktu panggilan untuk text-to-speech + if (callGroups.length > 0) { + // Ambil 5 teratas dari multiple calls, urutkan berdasarkan waktu panggilan + const sortedMultipleCalls = callGroups + .sort((a, b) => { + const timeA = new Date(a.lastCalledAt || a.pendingCallAt) + const timeB = new Date(b.lastCalledAt || b.pendingCallAt) + return timeA - timeB + }) + .slice(0, 5) + + return sortedMultipleCalls.map(queue => ({ + ...queue, + isMultipleCall: true, + klinik: queue.klinik || 'Klinik', + loket: queue.loket || 'Loket' + })) + } + + // Jika tidak ada multiple calls, tampilkan 5 tiket tercepat yang akan dipanggil const menungguQueues = loketPatients.value.filter(p => p.status === 'menunggu') const waitingQueues = loketPatients.value.filter(p => p.status === 'waiting') - - // Gabungkan: menunggu dulu, baru waiting const allWaitingQueues = [...menungguQueues, ...waitingQueues] // Filter berdasarkan pelayanan loket jika ada @@ -555,81 +637,37 @@ const nextTicketsToCall = computed(() => { } } - // Sort berdasarkan prioritas: - // 1. Status (menunggu lebih dulu dari waiting) - // 2. Waktu generate tiket (createdAt) - yang lebih lama lebih dulu - // 3. Urutan tiket (no) - yang lebih kecil lebih dulu - // 4. Waktu check-in - yang lebih lama lebih dulu + // Sort berdasarkan prioritas const sortedQueues = filteredQueues.sort((a, b) => { - // Prioritas 1: Status (menunggu > waiting) const statusPriority = { 'menunggu': 1, 'waiting': 2 } const statusDiff = (statusPriority[a.status] || 99) - (statusPriority[b.status] || 99) if (statusDiff !== 0) return statusDiff - // Prioritas 2: Waktu generate tiket const createdAtA = new Date(a.createdAt || 0) const createdAtB = new Date(b.createdAt || 0) if (createdAtA.getTime() !== createdAtB.getTime()) { return createdAtA - createdAtB } - // Prioritas 3: Urutan tiket if (a.no !== b.no) { return a.no - b.no } - // Prioritas 4: Waktu check-in const checkInA = getCheckInTime(a) const checkInB = getCheckInTime(b) return checkInA - checkInB }) - // Deteksi multiple calls (antrian yang dipanggil bersamaan dalam 5 detik) - const multipleCallGroups = [] - const processed = new Set() - - sortedQueues.forEach((queue, index) => { - if (processed.has(queue.no)) return - - const callTime = queue.lastCalledAt ? new Date(queue.lastCalledAt) : null - if (callTime) { - const group = [queue] - processed.add(queue.no) - - sortedQueues.forEach((otherQueue, otherIndex) => { - if (otherIndex !== index && !processed.has(otherQueue.no)) { - const otherCallTime = otherQueue.lastCalledAt ? new Date(otherQueue.lastCalledAt) : null - if (otherCallTime) { - const timeDiff = Math.abs(callTime - otherCallTime) - if (timeDiff <= 5000) { - group.push(otherQueue) - processed.add(otherQueue.no) - } - } - } - }) - - if (group.length > 1) { - multipleCallGroups.push(group) - } - } - }) - - // Flatten multiple call groups - const allMultipleCalls = multipleCallGroups.flat() - - // Exclude current called queue dari next tickets + // Exclude current called queue const currentCalledNo = currentCalledQueue.value?.no const filteredForNext = sortedQueues.filter(q => q.no !== currentCalledNo) - // Ambil 5 teratas dan mark yang multiple call - const next5 = filteredForNext.slice(0, 5).map(queue => ({ + // Ambil 5 teratas + return filteredForNext.slice(0, 5).map(queue => ({ ...queue, - isMultipleCall: allMultipleCalls.some(mc => mc.no === queue.no), + isMultipleCall: false, klinik: queue.klinik || 'Klinik' })) - - return next5 }) // Get grid style based on queue count - semakin sedikit antrian, semakin besar ukurannya @@ -941,6 +979,13 @@ onUnmounted(() => { font-size: 14px; font-weight: 600; color: var(--color-neutral-600); + margin-bottom: 4px; +} + +.next-ticket-loket { + font-size: 12px; + font-weight: 500; + color: var(--color-primary-600); margin-bottom: 6px; } diff --git a/stores/queueStore.js b/stores/queueStore.js index 2b14dd6..791a08b 100644 --- a/stores/queueStore.js +++ b/stores/queueStore.js @@ -365,22 +365,55 @@ export const useQueueStore = defineStore('queue', () => { return { success: false, message: "Tidak ada pasien yang menunggu untuk dipanggil" }; } - // Update status dari 'menunggu' menjadi 'waiting' (sudah dipanggil, bisa check-in) - // Track waktu panggilan untuk multiple calls detection - const callTimestamp = new Date().toISOString(); - const index = allPatients.value.findIndex(p => p.no === nextPatient.no); - if (index !== -1) { - allPatients.value[index] = { - ...allPatients.value[index], - status: "waiting", - lastCalledAt: callTimestamp // Track waktu panggilan untuk multiple calls + // Untuk adminType 'loket', tambahkan delay 5 detik sebelum status berubah + // Ini untuk simulasi multiple loket memanggil bersamaan + if (adminType === 'loket') { + // Set status pending call dengan timestamp + const pendingCallTimestamp = new Date().toISOString(); + const index = allPatients.value.findIndex(p => p.no === nextPatient.no); + if (index !== -1) { + allPatients.value[index] = { + ...allPatients.value[index], + status: "pending-call", // Status sementara sebelum delay + pendingCallAt: pendingCallTimestamp + }; + } + + // Set timeout 5 detik untuk mengubah status menjadi 'waiting' + setTimeout(() => { + const patientIndex = allPatients.value.findIndex(p => p.no === nextPatient.no); + if (patientIndex !== -1 && allPatients.value[patientIndex].status === 'pending-call') { + const callTimestamp = new Date().toISOString(); + allPatients.value[patientIndex] = { + ...allPatients.value[patientIndex], + status: "waiting", + lastCalledAt: callTimestamp, // Track waktu panggilan untuk multiple calls + pendingCallAt: undefined // Clear pending call timestamp + }; + } + }, 5000); // Delay 5 detik + + return { + success: true, + message: `Memanggil pasien ${nextPatient.noAntrian.split(" |")[0]} (akan dipanggil dalam 5 detik)`, + }; + } else { + // Untuk adminType selain loket, langsung update status + const callTimestamp = new Date().toISOString(); + const index = allPatients.value.findIndex(p => p.no === nextPatient.no); + if (index !== -1) { + allPatients.value[index] = { + ...allPatients.value[index], + status: "waiting", + lastCalledAt: callTimestamp // Track waktu panggilan untuk multiple calls + }; + } + + return { + success: true, + message: `Memanggil pasien ${nextPatient.noAntrian.split(" |")[0]}`, }; } - - return { - success: true, - message: `Memanggil pasien ${nextPatient.noAntrian.split(" |")[0]}`, - }; }; const callMultiplePatients = (count, adminType = 'loket') => { @@ -426,24 +459,61 @@ export const useQueueStore = defineStore('queue', () => { const patientsToCall = combinedList.slice(0, maxCallable); - // Update status dari 'menunggu' menjadi 'waiting' (sudah dipanggil, bisa check-in) - // Track waktu panggilan untuk multiple calls detection - semua dipanggil dengan timestamp yang sama - const callTimestamp = new Date().toISOString(); - patientsToCall.forEach((patient) => { - const index = allPatients.value.findIndex(p => p.no === patient.no); - if (index !== -1) { - allPatients.value[index] = { - ...allPatients.value[index], - status: "waiting", - lastCalledAt: callTimestamp // Track waktu panggilan untuk multiple calls - }; - } - }); + // Untuk adminType 'loket', tambahkan delay 5 detik sebelum status berubah + // Ini untuk simulasi multiple loket memanggil bersamaan + if (adminType === 'loket') { + // Set status pending call dengan timestamp + const pendingCallTimestamp = new Date().toISOString(); + patientsToCall.forEach((patient) => { + const index = allPatients.value.findIndex(p => p.no === patient.no); + if (index !== -1) { + allPatients.value[index] = { + ...allPatients.value[index], + status: "pending-call", // Status sementara sebelum delay + pendingCallAt: pendingCallTimestamp + }; + } + }); + + // Set timeout 5 detik untuk mengubah status menjadi 'waiting' + setTimeout(() => { + const callTimestamp = new Date().toISOString(); + patientsToCall.forEach((patient) => { + const patientIndex = allPatients.value.findIndex(p => p.no === patient.no); + if (patientIndex !== -1 && allPatients.value[patientIndex].status === 'pending-call') { + allPatients.value[patientIndex] = { + ...allPatients.value[patientIndex], + status: "waiting", + lastCalledAt: callTimestamp, // Track waktu panggilan untuk multiple calls + pendingCallAt: undefined // Clear pending call timestamp + }; + } + }); + }, 5000); // Delay 5 detik + + return { + success: true, + message: `Memanggil ${patientsToCall.length} pasien ke loket (akan dipanggil dalam 5 detik)`, + }; + } else { + // Untuk adminType selain loket, langsung update status + const callTimestamp = new Date().toISOString(); + patientsToCall.forEach((patient) => { + const index = allPatients.value.findIndex(p => p.no === patient.no); + if (index !== -1) { + allPatients.value[index] = { + ...allPatients.value[index], + status: "waiting", + lastCalledAt: callTimestamp // Track waktu panggilan untuk multiple calls + }; + } + }); - return { - success: true, - message: `Memanggil ${patientsToCall.length} pasien ke loket`, - }; + return { + success: true, + message: `Memanggil ${patientsToCall.length} pasien ke loket`, + }; + } }; const processPatient = (patient, action, adminType = 'loket') => {