// stores/queueStore.js import { defineStore } from 'pinia'; import { ref, computed } from 'vue'; import { useClinicStore } from './clinicStore'; import { usePenunjangStore } from './penunjangStore'; import { useLoketStore } from './loketStore'; export const useQueueStore = defineStore('queue', () => { const clinicStore = useClinicStore(); const penunjangStore = usePenunjangStore(); const loketStore = useLoketStore(); // Helper function untuk mendapatkan loket default (Loket 1) const getDefaultLoket = () => { const allLokets = loketStore.loketData?.value || loketStore.loketData || []; const loket1 = allLokets.find(l => l.id === 1 || l.no === 1); return loket1 ? loket1.namaLoket : 'Loket 1'; }; // Seed data for easy reset during dev const seedPatients = [ { no: 1, jamPanggil: "12:49", barcode: "250811100163", noAntrian: "UM1001 | Online - 250811100163", shift: "Shift 1", klinik: "KANDUNGAN", fastTrack: "YA", // Fast Track Patient pembayaran: "BPJS", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 2, jamPanggil: "10:52", barcode: "250811100155", noAntrian: "UM1002 | Online - 250811100155", shift: "Shift 1", klinik: "IPD", fastTrack: "TIDAK", pembayaran: "UMUM", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 3, jamPanggil: "09:30", barcode: "250811100200", noAntrian: "UM1003 | Online - 250811100200", shift: "Shift 1", klinik: "SARAF", fastTrack: "YA", // Fast Track Patient pembayaran: "BPJS", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 4, jamPanggil: "14:15", barcode: "250811100210", noAntrian: "UM1004 | Online - 250811100210", shift: "Shift 1", klinik: "THT", fastTrack: "TIDAK", pembayaran: "UMUM", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 5, jamPanggil: "12:49", barcode: "250811100163", noAntrian: "UM1005 | Online - 250811100163", shift: "Shift 2", klinik: "KANDUNGAN", fastTrack: "TIDAK", pembayaran: "UMUM", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 6, jamPanggil: "10:52", barcode: "250811100155", noAntrian: "UM1006 | Online - 250811100155", shift: "Shift 1", klinik: "IPD", fastTrack: "YA", // Fast Track Patient pembayaran: "BPJS", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 7, jamPanggil: "09:30", barcode: "250811100200", noAntrian: "UM1007 | Online - 250811100200", shift: "Shift 1", klinik: "SARAF", fastTrack: "TIDAK", pembayaran: "UMUM", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 8, jamPanggil: "14:15", barcode: "250811100210", noAntrian: "UM1008 | Online - 250811100210", shift: "Shift 1", klinik: "THT", fastTrack: "TIDAK", pembayaran: "UMUM", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 9, jamPanggil: "12:49", barcode: "250811100163", noAntrian: "UM1009 | Online - 250811100163", shift: "Shift 2", klinik: "KANDUNGAN", fastTrack: "YA", // Fast Track Patient pembayaran: "BPJS", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 10, jamPanggil: "10:52", barcode: "250811100155", noAntrian: "UM1010 | Online - 250811100155", shift: "Shift 1", klinik: "IPD", fastTrack: "TIDAK", pembayaran: "UMUM", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 11, jamPanggil: "09:30", barcode: "250811100200", noAntrian: "UM1011 | Online - 250811100200", shift: "Shift 1", klinik: "SARAF", fastTrack: "TIDAK", pembayaran: "UMUM", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, { no: 12, jamPanggil: "14:15", barcode: "250811100210", noAntrian: "UM1012 | Online - 250811100210", shift: "Shift 2", klinik: "THT", fastTrack: "YA", // Fast Track Patient pembayaran: "BPJS", status: "waiting", processStage: "loket", createdAt: new Date().toISOString(), }, ]; const cloneSeed = () => seedPatients.map(p => ({ ...p })); // Initialize state - will be automatically hydrated from localStorage by pinia-plugin-persistedstate // If localStorage has data, it will override these defaults const allPatients = ref(cloneSeed()); const quotaUsed = ref(5); const currentProcessingPatient = ref({ loket: null, klinik: null, penunjang: null, }); // Daftar klinik untuk dropdown diambil 1 pintu dari clinicStore const kliniks = computed(() => { const baseList = typeof clinicStore.getClinicsForDropdown === 'function' ? clinicStore.getClinicsForDropdown() : []; // Bentuk objek disesuaikan dengan yang dipakai di useQueue (id, name, kode) return baseList.map((c) => ({ id: c.id, name: c.name, kode: c.kode, icon: c.icon, available: c.available, })); }); // Penunjang data - reference dari penunjangStore (single source of truth) // Menggunakan computed untuk reactive reference const penunjangs = computed(() => { // Get penunjang list from penunjangStore dan map ke format yang diharapkan const penunjangList = penunjangStore.penunjangList || []; return penunjangList.map(p => ({ id: p.id, name: p.nama || p.name, // Support both nama and name for backward compatibility kode: p.kode })); }); // Computed - Filter berdasarkan process stage dan status const getPatientsByStage = (stage) => { return computed(() => { const patients = allPatients.value.filter(p => p.processStage === stage); // Debug log console.log(`getPatientsByStage(${stage}):`, patients.length, 'patients'); if (patients.length > 0) { console.log('Sample patient properties:', Object.keys(patients[0])); console.log('Sample fastTrack values:', patients.map(p => p.fastTrack)); } return { all: patients, waiting: patients.filter(p => p.status === 'waiting'), menunggu: patients.filter(p => p.status === 'menunggu'), // Pasien yang belum dipanggil diLoket: patients.filter(p => p.status === 'di-loket'), terlambat: patients.filter(p => p.status === 'terlambat'), pending: patients.filter(p => p.status === 'pending'), }; }); }; // Total pasien per stage const getTotalPasienByStage = (stage) => { return computed(() => allPatients.value.filter(p => p.processStage === stage).length ); }; const totalPasien = computed(() => allPatients.value.length); const resetPatients = () => { allPatients.value = cloneSeed(); quotaUsed.value = 5; currentProcessingPatient.value = { loket: null, klinik: null, penunjang: null }; }; // Actions const callNext = (adminType = 'loket') => { const stageMap = { 'loket': 'loket', 'klinik': 'klinik', 'penunjang': 'penunjang' }; const targetStage = stageMap[adminType]; // Prioritaskan pasien dengan status 'menunggu' (yang belum dipanggil) const nextPatient = allPatients.value.find(p => p.status === 'menunggu' && p.processStage === targetStage ) || allPatients.value.find(p => p.status === 'waiting' && p.processStage === targetStage ); if (!nextPatient) { return { success: false, message: "Tidak ada pasien selanjutnya" }; } // Hitung kuota yang tersedia const menungguCount = allPatients.value.filter(p => p.status === 'menunggu' && p.processStage === targetStage ).length; const diLoketCount = allPatients.value.filter(p => p.status === 'di-loket' && p.processStage === targetStage ).length; const availableQuota = 150 - diLoketCount; if (availableQuota <= 0) { return { success: false, message: "Kuota sudah penuh" }; } if (menungguCount === 0) { return { success: false, message: "Tidak ada pasien yang menunggu untuk dipanggil" }; } // Update status dari 'menunggu' menjadi 'waiting' (sudah dipanggil, bisa check-in) const index = allPatients.value.findIndex(p => p.no === nextPatient.no); if (index !== -1) { allPatients.value[index] = { ...allPatients.value[index], status: "waiting" }; } return { success: true, message: `Memanggil pasien ${nextPatient.noAntrian.split(" |")[0]}`, }; }; const callMultiplePatients = (count, adminType = 'loket') => { const stageMap = { 'loket': 'loket', 'klinik': 'klinik', 'penunjang': 'penunjang' }; const targetStage = stageMap[adminType]; // Prioritaskan pasien dengan status 'menunggu' (yang belum dipanggil) const menungguList = allPatients.value.filter(p => p.status === 'menunggu' && p.processStage === targetStage ); const waitingList = allPatients.value.filter(p => p.status === 'waiting' && p.processStage === targetStage ); // Gabungkan: menunggu dulu, baru waiting const combinedList = [...menungguList, ...waitingList]; if (combinedList.length === 0) { return { success: false, message: "Tidak ada pasien yang menunggu" }; } // Hitung kuota yang tersedia const diLoketCount = allPatients.value.filter(p => p.status === 'di-loket' && p.processStage === targetStage ).length; const availableQuota = 150 - diLoketCount; // Kuota yang bisa dipanggil = min(count yang diminta, jumlah pasien menunggu, kuota tersedia) const maxCallable = Math.min(count, menungguList.length, availableQuota); if (maxCallable <= 0) { if (menungguList.length === 0) { return { success: false, message: "Tidak ada pasien yang menunggu untuk dipanggil" }; } if (availableQuota <= 0) { return { success: false, message: "Kuota sudah penuh. Tidak bisa memanggil pasien lagi" }; } } const patientsToCall = combinedList.slice(0, maxCallable); // Update status dari 'menunggu' menjadi 'waiting' (sudah dipanggil, bisa check-in) patientsToCall.forEach((patient) => { const index = allPatients.value.findIndex(p => p.no === patient.no); if (index !== -1) { allPatients.value[index] = { ...allPatients.value[index], status: "waiting" }; } }); return { success: true, message: `Memanggil ${patientsToCall.length} pasien ke loket`, }; }; const processPatient = (patient, action, adminType = 'loket') => { const patientCode = patient.noAntrian.split(" |")[0]; let message = ""; // Find patient index for reactive update const patientIndex = allPatients.value.findIndex(p => p.no === patient.no); if (patientIndex === -1) { return { success: false, message: "Pasien tidak ditemukan" }; } switch (action) { case "check-in": // Jika check-in di loket, pindahkan ke klinik dengan status di-loket if (adminType === 'loket') { allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "di-loket", processStage: "klinik" }; message = `Pasien ${patientCode} berhasil check in dan masuk ke Tabel Loket Klinik`; } // Jika check-in di klinik, selesai else if (adminType === 'klinik') { allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "processed" }; message = `Pasien ${patientCode} berhasil check in di Klinik`; } // Jika check-in di penunjang, selesai else if (adminType === 'penunjang') { allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "processed" }; message = `Pasien ${patientCode} berhasil check in di Penunjang`; } // Clear current processing di admin yang melakukan check-in if (currentProcessingPatient.value[adminType]?.no === patient.no) { currentProcessingPatient.value[adminType] = null; } break; case "terlambat": allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "terlambat" }; if (currentProcessingPatient.value[adminType]?.no === patient.no) { currentProcessingPatient.value[adminType] = null; } message = `Pasien ${patientCode} ditandai terlambat`; break; case "pending": allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "pending" }; if (currentProcessingPatient.value[adminType]?.no === patient.no) { currentProcessingPatient.value[adminType] = null; } message = `Pasien ${patientCode} di-pending`; break; case "aktifkan": { const currentStatus = allPatients.value[patientIndex].status; if (currentStatus === "terlambat" || currentStatus === "pending") { // PERBAIKAN: Update dengan cara yang Vue reactive allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "di-loket" }; message = `Pasien ${patientCode} diaktifkan kembali dan masuk ke tabel Di Loket`; } else { message = `Pasien ${patientCode} tidak dapat diaktifkan (status: ${currentStatus})`; } break; } case "proses": { // Ambil data terbaru dari array const patient = allPatients.value[patientIndex]; // Jika adminType adalah 'loket', pastikan ada loket assignment if (adminType === 'loket') { // Pastikan antrian yang diproses memiliki loket assignment const currentLoket = patient.loket || getDefaultLoket(); const currentLoketId = patient.loketId || 1; // Update patient dengan loket assignment (jika belum ada) if (!patient.loket || !patient.loketId) { const updatedPatient = { ...patient, loket: currentLoket, loketId: currentLoketId }; allPatients.value[patientIndex] = updatedPatient; // Set currentProcessingPatient dengan loket assignment currentProcessingPatient.value[adminType] = updatedPatient; } else { // Jika sudah ada loket assignment, langsung set currentProcessingPatient currentProcessingPatient.value[adminType] = patient; } } else { // Untuk adminType selain loket, langsung set currentProcessingPatient currentProcessingPatient.value[adminType] = patient; } message = `Memproses pasien ${patientCode}`; break; } } return { success: true, message }; }; const processNextQueue = (adminType = 'loket') => { const stageMap = { 'loket': 'loket', 'klinik': 'klinik', 'penunjang': 'penunjang' }; const targetStage = stageMap[adminType]; const nextPatient = allPatients.value.find(p => p.status === 'di-loket' && p.processStage === targetStage ); if (!nextPatient) { return { success: false, message: "Tidak ada pasien di loket yang dapat diproses" }; } // Set sebagai current processing patient currentProcessingPatient.value[adminType] = nextPatient; return { success: true, message: `Memproses pasien ${nextPatient.noAntrian.split(" |")[0]}`, }; }; const createAntreanKlinik = (klinik, patient = null, adminType = 'loket') => { const newNo = allPatients.value.length + 1; const timestamp = new Date(); const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`; const newPatient = { no: newNo, jamPanggil: `${String(timestamp.getHours()).padStart(2, "0")}:${String( timestamp.getMinutes() ).padStart(2, "0")}`, barcode: barcode, noAntrian: `KL${String(newNo).padStart(4, "0")} | Klinik - ${barcode}`, shift: "Shift 1", klinik: klinik.name, fastTrack: "TIDAK", pembayaran: patient ? patient.pembayaran : "UMUM", status: "di-loket", processStage: "klinik", createdAt: timestamp.toISOString(), referencePatient: patient ? patient.noAntrian : null, }; allPatients.value.push(newPatient); return { success: true, message: `Antrean klinik ${klinik.name} berhasil dibuat dan masuk ke Tabel Loket Klinik`, patient: newPatient, }; }; const createAntreanPenunjang = (penunjang, patient = null, adminType = 'loket') => { const newNo = allPatients.value.length + 1; const timestamp = new Date(); const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`; const newPatient = { no: newNo, jamPanggil: `${String(timestamp.getHours()).padStart(2, "0")}:${String( timestamp.getMinutes() ).padStart(2, "0")}`, barcode: barcode, noAntrian: `PN${String(newNo).padStart(4, "0")} | Penunjang - ${barcode}`, shift: "Shift 1", klinik: penunjang.name, fastTrack: "TIDAK", pembayaran: patient ? patient.pembayaran : "UMUM", status: "di-loket", processStage: "penunjang", createdAt: timestamp.toISOString(), referencePatient: patient ? patient.noAntrian : null, }; allPatients.value.push(newPatient); return { success: true, message: `Antrean penunjang ${penunjang.name} berhasil dibuat dan masuk ke Tabel Loket Penunjang`, patient: newPatient, }; }; const createAntreanKlinikRuang = (klinikRuang, ruang, patient = null, adminType = 'klinik') => { const newNo = allPatients.value.length + 1; const timestamp = new Date(); const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`; const newPatient = { no: newNo, jamPanggil: `${String(timestamp.getHours()).padStart(2, "0")}:${String( timestamp.getMinutes() ).padStart(2, "0")}`, barcode: barcode, noAntrian: `KR${String(newNo).padStart(4, "0")} | ${klinikRuang.namaKlinik} - Ruang ${ruang.nomorRuang} - ${barcode}`, shift: "Shift 1", klinik: klinikRuang.namaKlinik, ruang: ruang.namaRuang, kodeKlinik: klinikRuang.kodeKlinik, nomorRuang: ruang.nomorRuang, nomorScreen: ruang.nomorScreen, fastTrack: "TIDAK", pembayaran: patient ? patient.pembayaran : "UMUM", status: "waiting", processStage: "klinik", createdAt: timestamp.toISOString(), referencePatient: patient ? patient.noAntrian : null, }; allPatients.value.push(newPatient); return { success: true, message: `Antrean ${klinikRuang.namaKlinik} Ruang ${ruang.nomorRuang} berhasil dibuat dan akan ditampilkan di layar antrian`, patient: newPatient, }; }; // Scan barcode dan generate antrean klinik ruang baru const scanAndCreateAntreanKlinikRuang = (barcodeInput, klinikRuang, ruang, tipeLayanan = 'Pemeriksaan Awal') => { // Clean input - remove whitespace and handle prefix letters const cleanInput = String(barcodeInput).trim().toUpperCase(); // Remove leading letters if any (e.g., "J200730100005" -> "200730100005") const numericInput = cleanInput.replace(/^[A-Z]+/, ''); // Find patient by barcode or noAntrian const sourcePatient = allPatients.value.find(p => { // Exact barcode match if (p.barcode === cleanInput || p.barcode === numericInput) return true; // Check if noAntrian includes the input const noAntrianUpper = (p.noAntrian || '').toUpperCase(); if (noAntrianUpper.includes(cleanInput) || noAntrianUpper.includes(numericInput)) return true; // Try extracting number from noAntrian const noAntrianNumber = noAntrianUpper.match(/([A-Z]+)(\d+)/); if (noAntrianNumber) { const extractedNumber = noAntrianNumber[2]; if (extractedNumber.includes(numericInput) || numericInput.includes(extractedNumber)) return true; } return false; }); if (!sourcePatient) { return { success: false, message: "Pasien tidak ditemukan. Pastikan barcode/nomor antrian benar." }; } // Check if patient already has antrean klinik ruang for this room const existingAntrean = allPatients.value.find(p => p.referencePatient === sourcePatient.noAntrian && p.kodeKlinik === klinikRuang.kodeKlinik && p.nomorRuang === ruang.nomorRuang && p.processStage === 'klinik-ruang' ); if (existingAntrean) { return { success: false, message: `Pasien sudah memiliki antrean di ${klinikRuang.namaKlinik} Ruang ${ruang.nomorRuang}` }; } // Generate queue number for this specific room and tipeLayanan const roomQueues = allPatients.value.filter(p => p.kodeKlinik === klinikRuang.kodeKlinik && p.nomorRuang === ruang.nomorRuang && p.tipeLayanan === tipeLayanan && p.processStage === 'klinik-ruang' ); const queueNumber = roomQueues.length + 1; const prefix = tipeLayanan === 'Pemeriksaan Awal' ? 'PA' : 'TD'; const newNo = allPatients.value.length + 1; const timestamp = new Date(); const newPatient = { no: newNo, jamPanggil: `${String(timestamp.getHours()).padStart(2, "0")}:${String( timestamp.getMinutes() ).padStart(2, "0")}`, barcode: sourcePatient.barcode, noAntrian: `${prefix}${String(queueNumber).padStart(3, "0")} | ${klinikRuang.namaKlinik} - ${ruang.namaRuang} - ${tipeLayanan}`, noAntrianRuang: `${klinikRuang.namaKlinik} - ${ruang.namaRuang} | ${prefix}${String(queueNumber).padStart(3, "0")}`, shift: sourcePatient.shift || "Shift 1", klinik: klinikRuang.namaKlinik, ruang: ruang.namaRuang, kodeKlinik: klinikRuang.kodeKlinik, nomorRuang: ruang.nomorRuang, nomorScreen: ruang.nomorScreen, tipeLayanan: tipeLayanan, fastTrack: sourcePatient.fastTrack || "TIDAK", pembayaran: sourcePatient.pembayaran || "UMUM", status: "waiting", processStage: "klinik-ruang", createdAt: timestamp.toISOString(), referencePatient: sourcePatient.noAntrian, sourcePatientNo: sourcePatient.no, }; allPatients.value.push(newPatient); return { success: true, message: `Antrean ${tipeLayanan} berhasil dibuat: ${newPatient.noAntrian.split(" |")[0]} untuk ${klinikRuang.namaKlinik} Ruang ${ruang.nomorRuang}`, patient: newPatient, }; }; // Get patients by klinik and ruang const getPatientsByKlinikRuang = (kodeKlinik, nomorRuang, tipeLayanan = null) => { return computed(() => { let patients = allPatients.value.filter(p => p.kodeKlinik === kodeKlinik && p.nomorRuang === nomorRuang && p.processStage === 'klinik-ruang' ); if (tipeLayanan) { patients = patients.filter(p => p.tipeLayanan === tipeLayanan); } return { all: patients, waiting: patients.filter(p => p.status === 'waiting'), diLoket: patients.filter(p => p.status === 'di-loket'), terlambat: patients.filter(p => p.status === 'terlambat'), pending: patients.filter(p => p.status === 'pending'), }; }); }; // Call next patient for a specific room and tipeLayanan const callNextKlinikRuang = (kodeKlinik, nomorRuang, tipeLayanan, allowMultiple = false) => { const patients = allPatients.value.filter(p => p.kodeKlinik === kodeKlinik && p.nomorRuang === nomorRuang && p.tipeLayanan === tipeLayanan && p.processStage === 'klinik-ruang' && (p.status === 'waiting' || (allowMultiple && p.status === 'di-loket')) ).sort((a, b) => { // Sort by status priority first const statusPriority = { 'waiting': 1, 'di-loket': 2 }; const priorityDiff = (statusPriority[a.status] || 99) - (statusPriority[b.status] || 99); if (priorityDiff !== 0) return priorityDiff; // Then sort by queue number (extract from noAntrian) const numA = parseInt(a.noAntrian.match(/\d+/)?.[0] || '999'); const numB = parseInt(b.noAntrian.match(/\d+/)?.[0] || '999'); return numA - numB; }); if (patients.length === 0) { return { success: false, message: `Tidak ada antrian ${tipeLayanan} yang menunggu di ruang ini` }; } const nextPatient = patients[0]; const patientIndex = allPatients.value.findIndex(p => p.no === nextPatient.no); if (patientIndex !== -1) { // If allowMultiple, we can call even if already di-loket (just update timestamp) // Otherwise, only update if status is waiting if (allowMultiple || nextPatient.status === 'waiting') { allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "di-loket", lastCalledAt: new Date().toISOString() // Track last call time }; } } return { success: true, message: `Memanggil pasien ${nextPatient.noAntrian.split(" |")[0]} untuk ${tipeLayanan}`, patient: allPatients.value[patientIndex], }; }; // Process patient in klinik ruang (set as current processing) const processPatientKlinikRuang = (patient, action, kodeKlinik, nomorRuang) => { const patientIndex = allPatients.value.findIndex(p => p.no === patient.no); if (patientIndex === -1) { return { success: false, message: "Pasien tidak ditemukan" }; } const patientCode = patient.noAntrian.split(" |")[0]; const key = `klinik-ruang-${kodeKlinik}-${nomorRuang}`; let message = ""; switch (action) { case "proses": // Set as current processing for this room if (!currentProcessingPatient.value[key]) { // Use Vue's reactive assignment currentProcessingPatient.value = { ...currentProcessingPatient.value, [key]: {} }; } // Update nested property reactively currentProcessingPatient.value[key] = { ...currentProcessingPatient.value[key], [patient.tipeLayanan]: allPatients.value[patientIndex] }; message = `Memproses pasien ${patientCode}`; break; case "selesai": allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "processed" }; // Clear current processing if (currentProcessingPatient.value[key]) { currentProcessingPatient.value[key] = { ...currentProcessingPatient.value[key], [patient.tipeLayanan]: null }; } message = `Pasien ${patientCode} selesai diproses`; break; case "terlambat": allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "terlambat" }; if (currentProcessingPatient.value[key]) { currentProcessingPatient.value[key] = { ...currentProcessingPatient.value[key], [patient.tipeLayanan]: null }; } message = `Pasien ${patientCode} ditandai terlambat`; break; case "pending": allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "pending" }; if (currentProcessingPatient.value[key]) { currentProcessingPatient.value[key] = { ...currentProcessingPatient.value[key], [patient.tipeLayanan]: null }; } message = `Pasien ${patientCode} di-pending`; break; } return { success: true, message }; }; const changeKlinik = (patient, newKlinik, adminType = 'loket') => { const patientIndex = allPatients.value.findIndex((p) => p.no === patient.no); if (patientIndex !== -1) { // Update dengan cara yang Vue reactive allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], klinik: newKlinik.name }; // Update current processing if it's the same patient if (currentProcessingPatient.value[adminType]?.no === patient.no) { currentProcessingPatient.value[adminType] = { ...currentProcessingPatient.value[adminType], klinik: newKlinik.name }; } return { success: true, message: `Klinik berhasil diubah ke ${newKlinik.name}`, }; } return { success: false, message: "Pasien tidak ditemukan" }; }; const getCurrentProcessing = (adminType) => { return computed(() => currentProcessingPatient.value[adminType]); }; const setCurrentProcessing = (patient, adminType) => { currentProcessingPatient.value[adminType] = patient; }; // Register patient from Anjungan (onsite registration) const registerPatientFromAnjungan = (clinic, paymentType, visitType = 'SEKARANG', visitDate = null, shift = 'Shift 1', namaDokter = null) => { const newNo = allPatients.value.length > 0 ? Math.max(...allPatients.value.map(p => p.no)) + 1 : 1; const timestamp = new Date(); const visitDateTime = visitDate ? new Date(visitDate) : timestamp; const jamPanggil = visitDate ? `${String(visitDateTime.getHours()).padStart(2, "0")}:${String(visitDateTime.getMinutes()).padStart(2, "0")}` : `${String(timestamp.getHours()).padStart(2, "0")}:${String(timestamp.getMinutes()).padStart(2, "0")}`; // Generate barcode (simulasi - bisa diganti dengan API call) const barcode = `250811${String(Date.now()).slice(-6)}${String(newNo).padStart(3, "0")}`; // Format nomor antrean: UMXXXX | Onsite - barcode const noAntrian = `UM${String(newNo).padStart(4, "0")} | Onsite - ${barcode}`; // Status awal untuk pasien dari anjungan adalah "menunggu" (belum dipanggil) // Hanya setelah dipanggil oleh admin loket, status berubah menjadi "waiting" (bisa check-in) const status = 'menunggu'; const newPatient = { no: newNo, jamPanggil: jamPanggil, barcode: barcode, noAntrian: noAntrian, shift: shift, klinik: clinic.name || clinic, fastTrack: "TIDAK", // Default, bisa diubah nanti pembayaran: paymentType, status: status, processStage: "loket", createdAt: timestamp.toISOString(), registrationType: 'onsite', visitType: visitType, visitDate: visitDate || timestamp.toISOString().substring(0, 10), namaDokter: namaDokter || null, // Nama dokter hanya untuk pasien Eksekutif }; allPatients.value.push(newPatient); return { success: true, message: `Pendaftaran ${clinic.name || clinic} untuk kunjungan ${visitType === 'SEKARANG' ? 'HARI INI' : visitDate} dengan pembayaran ${paymentType} berhasil diproses.`, patient: newPatient, }; }; // Check-in patient (update status from waiting to di-loket) const checkInPatient = (patientIdOrBarcode) => { console.log('🔍 checkInPatient called with:', patientIdOrBarcode); console.log('📊 Total patients in store:', allPatients.value.length); console.log('📋 First few patients:', allPatients.value.slice(0, 3).map(p => ({ no: p.no, noAntrian: p.noAntrian, barcode: p.barcode, status: p.status }))); // Clean input - remove whitespace and convert to uppercase for comparison const cleanInput = String(patientIdOrBarcode).trim().toUpperCase(); // Try to find by multiple criteria: // 1. Exact barcode match // 2. Parse as number and match with no // 3. Extract number from input (e.g., "UM0014" -> "0014" or "14") and match with noAntrian // 4. Check if noAntrian includes the input const patientIndex = allPatients.value.findIndex(p => { // Exact barcode match if (p.barcode === cleanInput || p.barcode === patientIdOrBarcode) { console.log('✅ Found by barcode:', p.barcode); return true; } // Try parsing as number const parsedNo = parseInt(cleanInput.replace(/[^0-9]/g, '')) || parseInt(patientIdOrBarcode); if (!isNaN(parsedNo) && p.no === parsedNo) { console.log('✅ Found by no:', p.no); return true; } // Check if noAntrian includes the input (case insensitive) const noAntrianUpper = (p.noAntrian || '').toUpperCase(); if (noAntrianUpper.includes(cleanInput) || noAntrianUpper.includes(patientIdOrBarcode)) { console.log('✅ Found by noAntrian:', p.noAntrian); return true; } // Try to extract number from noAntrian (e.g., "UM0014 | Onsite - ..." -> "0014") const noAntrianNumber = noAntrianUpper.match(/([A-Z]+)(\d+)/); if (noAntrianNumber) { const extractedNumber = noAntrianNumber[2]; const inputNumber = cleanInput.replace(/[^0-9]/g, ''); if (inputNumber && extractedNumber.includes(inputNumber) || inputNumber.includes(extractedNumber)) { console.log('✅ Found by extracted number from noAntrian'); return true; } } return false; }); if (patientIndex === -1) { console.log('❌ Patient not found. Searched for:', cleanInput); console.log('📋 Available noAntrian values:', allPatients.value.map(p => p.noAntrian)); return { success: false, message: "Pasien tidak ditemukan" }; } const patient = allPatients.value[patientIndex]; console.log('✅ Patient found:', patient); // Only allow check-in if status is waiting (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 { success: false, message: `Pasien belum dipanggil oleh admin loket. Mohon menunggu hingga nomor antrean Anda dipanggil.` }; } if (patient.status !== 'waiting' && patient.status !== 'pending') { console.log('⚠️ Patient status is not waiting/pending:', patient.status); return { success: false, message: `Pasien tidak dapat check-in. Status saat ini: ${patient.status}` }; } // Update status to di-loket allPatients.value[patientIndex] = { ...allPatients.value[patientIndex], status: "di-loket", }; console.log('✅ Check-in successful, patient status updated to di-loket'); return { success: true, message: `Check-in berhasil. Pasien ${patient.noAntrian.split(" |")[0]} siap diproses di loket.`, patient: allPatients.value[patientIndex], }; }; // State persistence is handled automatically by pinia-plugin-persistedstate // No need for manual watch or save logic return { // State allPatients, quotaUsed, currentProcessingPatient, kliniks, penunjangs, // Computed totalPasien, // Actions callNext, callMultiplePatients, processPatient, createAntreanKlinik, createAntreanPenunjang, createAntreanKlinikRuang, scanAndCreateAntreanKlinikRuang, getPatientsByKlinikRuang, callNextKlinikRuang, processPatientKlinikRuang, changeKlinik, processNextQueue, getPatientsByStage, getTotalPasienByStage, getCurrentProcessing, setCurrentProcessing, registerPatientFromAnjungan, checkInPatient, }; }, { persist: { key: 'queue-store-state', storage: typeof window !== 'undefined' ? localStorage : undefined, paths: ['allPatients', 'quotaUsed', 'currentProcessingPatient'], serializer: { deserialize: JSON.parse, serialize: JSON.stringify, }, restore: (value) => { // Ensure allPatients is always an array if (value && value.allPatients && !Array.isArray(value.allPatients)) { value.allPatients = []; } return value; }, }, });