diff --git a/pages/Anjungan/AntrianKlinik/[id].vue b/pages/Anjungan/AntrianKlinik/[id].vue index f8cc453..677cf71 100644 --- a/pages/Anjungan/AntrianKlinik/[id].vue +++ b/pages/Anjungan/AntrianKlinik/[id].vue @@ -20,11 +20,11 @@ -
+
mdi-bell-ring - SEDANG DIPANGGIL + {{ currentCalledQueue.status === 'pending' ? 'PENDING' : 'SEDANG DIPANGGIL' }}
{{ currentCalledQueue.noAntrian.split(" |")[0] }} @@ -56,8 +56,10 @@
-
SEKARANG
-
+
+ {{ clinic.currentQueue.status === 'pending' ? 'PENDING' : 'SEKARANG' }} +
+
{{ clinic.currentQueue.noAntrian.split(" |")[0] }}
@@ -70,6 +72,7 @@ v-for="queue in clinic.nextQueues.slice(0, 3)" :key="queue.no" class="next-number-badge" + :class="{ 'badge-pending': queue.status === 'pending' }" > {{ queue.noAntrian.split(" |")[0] }}
@@ -84,6 +87,20 @@ mdi-minus-circle-outline
Tidak Ada Antrian
+ + +
+
PENDING
+
+
+ {{ queue.noAntrian.split(" |")[0] }} +
+
+
@@ -176,7 +193,24 @@ let timeInterval = null; let refreshInterval = null; const klinikPatients = computed(() => { - return queueStore.getPatientsByStage("klinik").value.all; + // Ambil pasien dengan processStage "klinik" (sudah check-in di loket) + const klinikStagePatients = queueStore.getPatientsByStage("klinik").value.all; + + // Juga ambil pasien dengan status "pending" yang processStage-nya masih "loket" + // Ini adalah pasien yang di-pending di AdminLoket dan harus kembali ke kolom klinik + const pendingFromLoket = queueStore.getPatientsByStage("loket").value.all + .filter(p => p.status === "pending"); + + // Gabungkan dan pastikan tidak ada duplikat + const allPatients = [...klinikStagePatients]; + pendingFromLoket.forEach(pendingPatient => { + const exists = allPatients.find(p => p.no === pendingPatient.no); + if (!exists) { + allPatients.push(pendingPatient); + } + }); + + return allPatients; }); // Get kliniks based on screen configuration @@ -217,21 +251,33 @@ const displayedClinics = computed(() => { return timeA[0] * 60 + timeA[1] - (timeB[0] * 60 + timeB[1]); }); + // Current queue: prioritaskan di-loket, lalu waiting, lalu pending (dengan card merah) const currentQueue = queues.find((q) => q.status === "di-loket") || queues.find((q) => q.status === "waiting") || + queues.find((q) => q.status === "pending") || null; + // Next queues: tampilkan semua yang bukan current queue, termasuk pending const nextQueues = queues.filter( (q) => q.no !== currentQueue?.no && - (q.status === "waiting" || q.status === "di-loket") + (q.status === "waiting" || q.status === "di-loket" || q.status === "pending") + ); + + // Pending queues: pasien dengan status pending yang tidak di current atau next + const pendingQueues = queues.filter( + (q) => + q.status === "pending" && + q.no !== currentQueue?.no && + !nextQueues.find(nq => nq.no === q.no) ); return { name: klinik.name, currentQueue: currentQueue, nextQueues: nextQueues, + pendingQueues: pendingQueues, totalQueues: queues.length, }; }); @@ -241,9 +287,19 @@ const currentCalledQueue = computed(() => { if (!screenData.value) return null; const screenKlinikNames = screenKliniks.value.map(k => k.name); + // Prioritaskan pasien yang sedang dipanggil (di-loket), lalu pending const calledQueues = klinikPatients.value - .filter((p) => p.status === "di-loket" && screenKlinikNames.includes(p.klinik)) - .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + .filter((p) => + (p.status === "di-loket" || p.status === "pending") && + screenKlinikNames.includes(p.klinik) + ) + .sort((a, b) => { + // Prioritaskan di-loket dulu, lalu pending + if (a.status === "di-loket" && b.status !== "di-loket") return -1; + if (a.status !== "di-loket" && b.status === "di-loket") return 1; + // Jika sama, sort by createdAt + return new Date(b.createdAt) - new Date(a.createdAt); + }); return calledQueues[0] || null; }); @@ -396,6 +452,21 @@ onUnmounted(() => { box-shadow: 0 12px 32px rgba(224, 21, 7, 0.35); } +.hero-queue.hero-pending { + background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%); + box-shadow: 0 12px 32px rgba(220, 38, 38, 0.5); + animation: pending-pulse 2s ease-in-out infinite; +} + +@keyframes pending-pulse { + 0%, 100% { + box-shadow: 0 12px 32px rgba(220, 38, 38, 0.5); + } + 50% { + box-shadow: 0 12px 48px rgba(220, 38, 38, 0.7); + } +} + .hero-content { text-align: center; color: var(--color-neutral-100); @@ -511,6 +582,20 @@ onUnmounted(() => { letter-spacing: 1px; } +.queue-status-label.status-pending { + color: var(--color-danger-600); + animation: pending-blink 1.5s ease-in-out infinite; +} + +@keyframes pending-blink { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.7; + } +} + .queue-number-large { font-size: 80px; font-weight: 900; @@ -520,6 +605,11 @@ onUnmounted(() => { letter-spacing: 4px; } +.queue-number-large.queue-pending { + color: var(--color-danger-600); + text-shadow: 0 2px 8px rgba(220, 38, 38, 0.4); +} + .next-queues-section { text-align: center; margin-top: 8px; @@ -553,6 +643,12 @@ onUnmounted(() => { text-align: center; } +.next-number-badge.badge-pending { + background: var(--color-danger-600); + box-shadow: 0 2px 8px rgba(220, 38, 38, 0.4); + border: 2px solid var(--color-danger-700); +} + .empty-queue { text-align: center; padding: 24px 0; @@ -565,6 +661,53 @@ onUnmounted(() => { font-weight: 600; } +/* ========== PENDING QUEUES SECTION ========== */ +.pending-queues-section { + text-align: center; + margin-top: 12px; + padding-top: 12px; + border-top: 2px solid var(--color-danger-200); +} + +.pending-label { + font-size: 13px; + font-weight: 700; + color: var(--color-danger-600); + margin-bottom: 8px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.pending-numbers-grid { + display: flex; + gap: 8px; + justify-content: center; + flex-wrap: wrap; +} + +.pending-number-badge { + background: var(--color-danger-600); + color: var(--color-neutral-100); + padding: 8px 16px; + border-radius: 12px; + font-size: 22px; + font-weight: 700; + box-shadow: 0 2px 8px rgba(220, 38, 38, 0.4); + border: 2px solid var(--color-danger-700); + min-width: 60px; + text-align: center; + animation: pending-badge-pulse 2s ease-in-out infinite; +} + +@keyframes pending-badge-pulse { + 0%, 100% { + box-shadow: 0 2px 8px rgba(220, 38, 38, 0.4); + } + 50% { + box-shadow: 0 2px 12px rgba(220, 38, 38, 0.6); + } +} + /* ========== FOOTER ========== */ .tv-footer { background: linear-gradient(135deg, var(--color-secondary-600) 0%, var(--color-secondary-700) 100%); diff --git a/pages/Anjungan/AntrianLoket/[id].vue b/pages/Anjungan/AntrianLoket/[id].vue index cf10d13..0fd6ff5 100644 --- a/pages/Anjungan/AntrianLoket/[id].vue +++ b/pages/Anjungan/AntrianLoket/[id].vue @@ -218,15 +218,13 @@ const currentProcessingPatient = computed(() => { return queueStore.currentProcessingPatient?.loket || null }) -// Get all patients with processStage "loket" and filter status "di-loket" (sudah check-in), "terlambat", dan "pending" +// Get all patients with processStage "loket" and filter status "di-loket" (sudah check-in) +// Hanya tampilkan nomor antrian yang sudah check-in dan berstatus "di-loket" const loketPatients = computed(() => { const allPatients = queueStore.getPatientsByStage('loket').value.all - // Tampilkan antrian dengan status: - // - "di-loket" (sudah check-in) - // - "terlambat" (terlambat) - // - "pending" (pending) - // Jangan tampilkan status "waiting" atau "menunggu" (belum check-in) - return allPatients.filter(p => p.status === 'di-loket' || p.status === 'terlambat' || p.status === 'pending') + // Hanya tampilkan antrian dengan status "di-loket" (sudah check-in di admin loket) + // Jangan tampilkan status "waiting", "menunggu", "terlambat", atau "pending" + return allPatients.filter(p => p.status === 'di-loket') }) // Helper untuk mendapatkan waktu check-in atau waktu dipanggil @@ -248,60 +246,13 @@ const getCheckInTime = (patient) => { } // Helper untuk mendapatkan nama klinik dari pasien -// Jika patient.klinik tidak ada atau tidak valid, coba parse dari noAntrian +// Membaca poli/klinik dari nomor antrian yang dibuat sebelumnya +// Nomor antrian dibuat dengan kode klinik tertentu, jadi kita baca dari data pasien const getKlinikNameFromPatient = (patient) => { - // Prioritas 1: Parse dari noAntrian (RA001 -> Radioterapi, AN001 -> Anak, dll) - // Ini prioritas tertinggi karena noAntrian adalah sumber kebenaran - if (patient.noAntrian) { - const noAntrianPart = patient.noAntrian.split(' |')[0] // Ambil bagian sebelum " |" - const match = noAntrianPart.match(/^([A-Z]+)/) // Extract prefix (RA, AN, EA, dll) - if (match) { - const kode = match[1] - - // Mapping khusus untuk kode yang berbeda (RA -> RT untuk Radioterapi) - // Cek semua kemungkinan kode di clinicStore - let allClinics = [] - if (clinicStore.getAllClinics) { - allClinics = typeof clinicStore.getAllClinics === 'function' - ? clinicStore.getAllClinics() - : (clinicStore.getAllClinics.value || []) - } - - let matchedClinic = null - - // Coba match langsung dengan kode - matchedClinic = allClinics.find(c => c.kode === kode) - - // Jika tidak match, coba mapping khusus - if (!matchedClinic) { - // Mapping khusus: RA -> RT (Radioterapi) - if (kode === 'RA') { - matchedClinic = allClinics.find(c => c.kode === 'RT' && c.name === 'RADIOTERAPI') - } - } - - if (matchedClinic && matchedClinic.name) { - return matchedClinic.name.trim() - } - - // Fallback ke masterStore jika clinicStore tidak ada - const klinikData = masterStore.getKlinikByKode ? masterStore.getKlinikByKode(kode) : null - if (klinikData && klinikData.nama) { - return klinikData.nama.trim() - } - - // Jika masih tidak match, coba dengan mapping RA -> RT - if (kode === 'RA') { - const rtData = masterStore.getKlinikByKode ? masterStore.getKlinikByKode('RT') : null - if (rtData && rtData.nama) { - return rtData.nama.trim() - } - } - } - } - - // Prioritas 2: Gunakan kodeKlinik jika ada + // Prioritas 1: Gunakan kodeKlinik dari data pasien (dari nomor antrian yang dibuat sebelumnya) + // Ini adalah sumber kebenaran karena kodeKlinik disimpan saat nomor antrian dibuat if (patient.kodeKlinik) { + // Cari di clinicStore terlebih dahulu const clinic = clinicStore.getClinicByKode ? clinicStore.getClinicByKode(patient.kodeKlinik) : null if (clinic && clinic.name) { return clinic.name.trim() @@ -313,11 +264,47 @@ const getKlinikNameFromPatient = (patient) => { } } - // Prioritas 3: Gunakan patient.klinik jika ada dan valid + // Prioritas 2: Gunakan patient.klinik jika ada dan valid + // Ini juga dari data pasien yang dibuat sebelumnya if (patient.klinik && patient.klinik.trim() !== '') { return patient.klinik.trim() } + // Prioritas 3: Coba parse dari noAntrian sebagai fallback + // Catatan: Format nomor antrian adalah RA001, EA001, dll (R/E + Loket + nomor) + // Kode klinik tidak langsung terlihat dari format ini, tapi kita coba match dengan kode yang ada + if (patient.noAntrian) { + const noAntrianPart = patient.noAntrian.split(' |')[0] // Ambil bagian sebelum " |" + // Hapus prefix R/E dan Fast Track (F) jika ada + const withoutPrefix = noAntrianPart.replace(/^[FR]?[RE]?/, '') // Hapus F, R, E di awal + const match = withoutPrefix.match(/^([A-Z]+)/) // Extract kode loket/klinik (A, B, C, dll) + + if (match) { + const kode = match[1] + + // Cek semua kemungkinan kode di clinicStore + let allClinics = [] + if (clinicStore.getAllClinics) { + allClinics = typeof clinicStore.getAllClinics === 'function' + ? clinicStore.getAllClinics() + : (clinicStore.getAllClinics.value || []) + } + + // Coba match dengan kode klinik yang ada + let matchedClinic = allClinics.find(c => c.kode === kode) + + if (matchedClinic && matchedClinic.name) { + return matchedClinic.name.trim() + } + + // Fallback ke masterStore + const klinikData = masterStore.getKlinikByKode ? masterStore.getKlinikByKode(kode) : null + if (klinikData && klinikData.nama) { + return klinikData.nama.trim() + } + } + } + // Fallback: UMUM return 'UMUM' } @@ -451,14 +438,15 @@ const displayedClinics = computed(() => { sortedQueues.forEach((queue, index) => { if (processed.has(queue.no)) return - if (queue.status === 'di-loket' || queue.status === 'terlambat' || queue.status === 'pending') { + // Hanya proses antrian dengan status 'di-loket' (sudah check-in) + if (queue.status === 'di-loket') { const callTime = queue.lastCalledAt ? new Date(queue.lastCalledAt) : (queue.createdAt ? new Date(queue.createdAt) : null) if (callTime) { const group = [queue] processed.add(queue.no) sortedQueues.forEach((otherQueue, otherIndex) => { - if (otherIndex !== index && !processed.has(otherQueue.no) && (otherQueue.status === 'di-loket' || otherQueue.status === 'terlambat' || otherQueue.status === 'pending')) { + if (otherIndex !== index && !processed.has(otherQueue.no) && otherQueue.status === 'di-loket') { const otherCallTime = otherQueue.lastCalledAt ? new Date(otherQueue.lastCalledAt) : (otherQueue.createdAt ? new Date(otherQueue.createdAt) : null) if (otherCallTime) { const timeDiff = Math.abs(callTime - otherCallTime) @@ -514,11 +502,10 @@ const displayedClinics = computed(() => { } } else { // Jika tidak ada di sortedQueues, gunakan langsung dari hero section - // Pastikan pasien sesuai dengan filter (processStage 'loket') - // Status bisa 'di-loket', 'terlambat', 'pending', atau 'waiting' (sudah dipanggil) + // Pastikan pasien sesuai dengan filter (processStage 'loket' dan status 'di-loket') if (heroPatient.processStage === 'loket' && - heroPatient.status !== 'menunggu') { - // Langsung gunakan pasien dari hero section karena sudah dipanggil + heroPatient.status === 'di-loket') { + // Langsung gunakan pasien dari hero section karena sudah dipanggil dan check-in currentQueue = heroPatient } } @@ -575,28 +562,21 @@ const displayedClinics = computed(() => { validatedCurrentQueue = null } - // Jika currentQueue berasal dari hero section (currentCalledQueue), tidak perlu filter status - // karena pasien yang dipanggil di hero section sudah valid - const isFromHero = currentCalledQueue.value && - (validatedCurrentQueue.no === currentCalledQueue.value.no || - validatedCurrentQueue.noAntrian === currentCalledQueue.value.noAntrian) - - // Jika bukan dari hero section, pastikan status 'di-loket' (sudah check-in di admin loket) - if (validatedCurrentQueue && !isFromHero && validatedCurrentQueue.status !== 'di-loket') { + // Pastikan status selalu 'di-loket' (sudah check-in di admin loket) + if (validatedCurrentQueue && validatedCurrentQueue.status !== 'di-loket') { validatedCurrentQueue = null } } // Validasi allQueues: pastikan semua pasien benar-benar dari klinik ini - // Ambil yang status 'di-loket', 'terlambat', atau 'pending' dan dipanggil dari admin loket + // Hanya ambil yang status 'di-loket' (sudah check-in) dan dipanggil dari admin loket const validatedAllQueues = sortedQueues.filter(q => { // Pastikan: - // 1. Status 'di-loket', 'terlambat', atau 'pending' (sudah dipanggil/di-loket di admin loket) + // 1. Status 'di-loket' (sudah check-in di admin loket) // 2. processStage masih 'loket' (masih di admin loket, belum pindah ke klinik) - // 3. Klinik-nya sesuai dengan card ini + // 3. Klinik-nya sesuai dengan card ini (dibaca dari nomor antrian) const queueKlinik = getKlinikNameFromPatient(q) - const isValidStatus = q.status === 'di-loket' || q.status === 'terlambat' || q.status === 'pending' - return isValidStatus && + return q.status === 'di-loket' && q.processStage === 'loket' && queueKlinik === normalizedKlinikName }) @@ -629,75 +609,24 @@ const isInTTSWindow = (queue) => { return remaining > 0 } -// Current called queue - tiket yang sedang diproses di admin loket atau sedang dalam TTS window +// Current called queue - tiket yang sedang diproses di admin loket +// Nomor antrian menjadi "dipanggil" jika diproses pada AdminLoket const currentCalledQueue = computed(() => { - // Prioritas 1: Pasien yang sedang diproses di admin loket + // Prioritas 1: Pasien yang sedang diproses di admin loket (currentProcessingPatient) + // Ini adalah pasien yang sedang dipanggil/diproses di AdminLoket if (currentProcessingPatient.value) { const processingPatient = currentProcessingPatient.value - // Pastikan pasien memiliki noAntrian dan klinik - if (processingPatient.noAntrian) { + // Pastikan pasien memiliki noAntrian dan status 'di-loket' (sudah check-in) + if (processingPatient.noAntrian && processingPatient.status === 'di-loket') { + // Dapatkan nama klinik dari nomor antrian + const klinikName = getKlinikNameFromPatient(processingPatient) return { ...processingPatient, - klinik: processingPatient.klinik || getKlinikNameFromPatient(processingPatient) || 'Klinik' + klinik: klinikName || processingPatient.klinik || 'Klinik' } } } - // Prioritas 2: Ambil semua tiket yang sudah dipanggil (status 'waiting') - const allCalledQueues = queueStore.allPatients.filter(p => - p.status === 'waiting' && p.lastCalledAt - ) - - // Filter berdasarkan pelayanan loket jika ada - const targetLoketId = loketId.value - let filteredQueues = allCalledQueues - - if (targetLoketId && loketData.value) { - const allowedPelayananCodes = loketData.value.pelayanan || [] - if (allowedPelayananCodes.length > 0) { - filteredQueues = allCalledQueues.filter(patient => { - const klinikName = patient.klinik || 'UMUM' - const clinic = clinicStore.getClinicByName ? clinicStore.getClinicByName(klinikName) : null - - if (clinic) { - return allowedPelayananCodes.includes(clinic.kode) - } else { - const matchedKode = allowedPelayananCodes.find(kode => { - const k = masterStore.getKlinikByKode ? masterStore.getKlinikByKode(kode) : null - return k && k.nama === klinikName - }) - return !!matchedKode - } - }) - } - } - - // Urutkan berdasarkan waktu panggilan (yang dipanggil duluan lebih dulu) - const sortedByCallTime = filteredQueues.sort((a, b) => { - const timeA = new Date(a.lastCalledAt || 0) - const timeB = new Date(b.lastCalledAt || 0) - return timeA - timeB - }) - - // Cari tiket pertama yang masih dalam TTS window - for (const queue of sortedByCallTime) { - if (isInTTSWindow(queue)) { - return { - ...queue, - klinik: queue.klinik || 'Klinik' - } - } - } - - // Jika tidak ada yang dalam TTS window, ambil yang paling baru dipanggil - if (sortedByCallTime.length > 0) { - const latest = sortedByCallTime[sortedByCallTime.length - 1] - return { - ...latest, - klinik: latest.klinik || 'Klinik' - } - } - return null }) @@ -795,27 +724,37 @@ const getTimerText = (queue) => { return `${seconds}s` } -// Next 5 tickets to be called - tiket yang sudah dipanggil tapi menunggu giliran TTS +// Next 5 tickets to be called - tiket yang sudah check-in (status 'di-loket') dan belum diproses const nextTicketsToCall = computed(() => { - // Ambil semua tiket yang sudah dipanggil (status 'waiting') - const allCalledQueues = queueStore.allPatients.filter(p => - p.status === 'waiting' && p.lastCalledAt + // Ambil semua tiket yang sudah check-in (status 'di-loket') dan processStage 'loket' + const allDiLoketQueues = loketPatients.value.filter(p => + p.status === 'di-loket' && p.processStage === 'loket' ) // Filter berdasarkan pelayanan loket jika ada const targetLoketId = loketId.value - let filteredQueues = allCalledQueues + let filteredQueues = allDiLoketQueues if (targetLoketId && loketData.value) { const allowedPelayananCodes = loketData.value.pelayanan || [] if (allowedPelayananCodes.length > 0) { - filteredQueues = allCalledQueues.filter(patient => { - const klinikName = patient.klinik || 'UMUM' + filteredQueues = allDiLoketQueues.filter(patient => { + // Dapatkan nama klinik dari nomor antrian + const klinikName = getKlinikNameFromPatient(patient) const clinic = clinicStore.getClinicByName ? clinicStore.getClinicByName(klinikName) : null if (clinic) { return allowedPelayananCodes.includes(clinic.kode) } else { + // Coba match dengan kode dari nomor antrian + if (patient.noAntrian) { + const noAntrianPart = patient.noAntrian.split(' |')[0] + const match = noAntrianPart.match(/^([A-Z]+)/) + if (match) { + const kodeFromNoAntrian = match[1] + return allowedPelayananCodes.includes(kodeFromNoAntrian) + } + } const matchedKode = allowedPelayananCodes.find(kode => { const k = masterStore.getKlinikByKode ? masterStore.getKlinikByKode(kode) : null return k && k.nama === klinikName @@ -826,23 +765,21 @@ const nextTicketsToCall = computed(() => { } } - // Urutkan berdasarkan waktu panggilan (yang dipanggil duluan lebih dulu) - const sortedByCallTime = filteredQueues.sort((a, b) => { - const timeA = new Date(a.lastCalledAt || 0) - const timeB = new Date(b.lastCalledAt || 0) - return timeA - timeB + // Urutkan berdasarkan waktu check-in (yang check-in duluan lebih dulu) + const sortedByCheckIn = filteredQueues.sort((a, b) => { + const checkInA = getCheckInTime(a) + const checkInB = getCheckInTime(b) + return checkInA - checkInB }) - // Exclude current called queue (yang sedang dalam TTS window) + // Exclude current called queue (yang sedang diproses) const currentCalledNo = currentCalledQueue.value?.no - const waitingForTTS = sortedByCallTime.filter(q => q.no !== currentCalledNo) + const waitingForProcess = sortedByCheckIn.filter(q => q.no !== currentCalledNo) - // Ambil 5 tiket berikutnya yang sudah dipanggil tapi TTS-nya belum dimulai - // (yang akan bergantian menjadi card utama ketika TTS selesai) - return waitingForTTS.slice(0, 5).map(queue => ({ + // Ambil 5 tiket berikutnya yang sudah check-in tapi belum diproses + return waitingForProcess.slice(0, 5).map(queue => ({ ...queue, - isMultipleCall: true, // Semua di section ini adalah tiket yang sudah dipanggil - klinik: queue.klinik || 'Klinik', + klinik: getKlinikNameFromPatient(queue) || queue.klinik || 'Klinik', loket: queue.loket || 'Loket' })) }) diff --git a/stores/loketStore.js b/stores/loketStore.js index 9528424..29a2b8b 100644 --- a/stores/loketStore.js +++ b/stores/loketStore.js @@ -31,7 +31,7 @@ const getPelayananContohDariAnjungan = (anjunganItems) => { // Distribusi pelayanan ke beberapa loket sebagai contoh // Menggunakan data klinik yang tersedia dari master anjungan return { - loket1: klinikAnjunganReguler.filter(k => ['RT', 'RM', 'TD'].includes(k)).length > 0 + loket1: klinikAnjunganReguler.filter(k => ['AN','RT', 'RM', 'TD'].includes(k)).length > 0 ? klinikAnjunganReguler.filter(k => ['RT', 'RM', 'TD'].includes(k)) : klinikAnjunganReguler.slice(0, 3), // Ambil 3 pertama jika tidak ada match loket2: klinikAnjunganReguler.filter(k => ['JW', 'SR'].includes(k)).length > 0