// composables/useQueue.js import { ref, computed } from "vue"; import { useQueueStore } from "../stores/queueStore"; import { useLoketStore } from "../stores/loketStore"; export const useQueue = (adminType = "loket", specificId = null) => { const queueStore = useQueueStore(); const loketStore = useLoketStore(); // Normalize specificId to a reactive value const idValue = computed(() => { if (typeof specificId === 'function') return specificId(); if (specificId && typeof specificId === 'object' && 'value' in specificId) return specificId.value; return specificId; }); // Local state const snackbar = ref(false); const snackbarText = ref(""); const snackbarColor = ref("success"); // Dialog states const showKlinikDialog = ref(false); const showPenunjangDialog = ref(false); const showChangeKlinikDialog = ref(false); const klinikSearch = ref(""); const penunjangSearch = ref(""); const changeKlinikSearch = ref(""); const selectedPatientForPenunjang = ref(null); // FIXED: Get stage patients once, then derive others from it const stagePatients = computed(() => { // Don't use .value here - getPatientsByStage returns a computed already const patients = queueStore.getPatientsByStage(adminType); return patients.value; }); // Computed from store - isolated by specificId if provided const currentProcessingPatient = computed(() => { const key = idValue.value ? `${adminType}-${idValue.value}` : adminType; return queueStore.currentProcessingPatient[key]; }); // Derive from stagePatients - ADD DEBUG LOGS const diLoketPatients = computed(() => { const patients = stagePatients.value.diLoket || []; console.log('🔍 useQueue - diLoketPatients:', patients.length); if (patients.length > 0) { console.log('🔍 First diLoket patient:', patients[0]); console.log('🔍 First diLoket patient fastTrack:', patients[0].fastTrack); } return patients; }); const terlambatPatients = computed(() => { const patients = stagePatients.value.terlambat || []; console.log('🔍 useQueue - terlambatPatients:', patients.length); return patients; }); const pendingPatients = computed(() => { const patients = stagePatients.value.pending || []; console.log('🔍 useQueue - pendingPatients:', patients.length); return patients; }); const anjunganPatients = computed(() => stagePatients.value.anjungan || []); // Pasien yang belum dipanggil (status "menunggu") const menungguPatients = computed(() => stagePatients.value.menunggu || []); const nextPatient = computed(() => { // Prioritaskan pasien menunggu, baru anjungan return menungguPatients.value[0] || anjunganPatients.value[0] || null; }); // Total pasien hanya untuk stage admin ini const totalPasien = computed(() => { const total = queueStore.getTotalPasienByStage(adminType); return total.value; }); // Helper to check if a patient is relevant to the current admin view const isPatientRelevant = (p) => { if (p.processStage !== adminType) return false; const targetId = idValue.value; if (!targetId) return true; // No specific ID, show all for this stage if (adminType === 'loket') { const thisLoket = loketStore.getLoketById(parseInt(targetId)); if (!thisLoket) return String(p.loketId) === String(targetId); // Must be assigned to this loket OR unassigned but clinic is served by this loket const isAssignedToThis = p.loketId && String(p.loketId) === String(targetId); if (isAssignedToThis) return true; const isServedByThis = !p.loketId && thisLoket.pelayanan && Array.isArray(thisLoket.pelayanan) && thisLoket.pelayanan.includes(p.kodeKlinik); return isServedByThis; } if (adminType === 'klinik') { // Filter by clinic code or clinic name return String(p.kodeKlinik) === String(targetId) || String(p.klinik) === String(targetId); } if (adminType === 'penunjang') { // Penunjang usually uses clinic name as the identifier in seeds return String(p.klinik) === String(targetId) || String(p.kodeKlinik) === String(targetId); } return true; }; // 1. Menunggu Count (Backlog ONLY - Strictly status 'menunggu') const menungguCount = computed(() => { let list = menungguPatients.value || []; return list.filter(isPatientRelevant).length; }); // 2. Calculated Quota Used (Dynamic - any ticket NOT 'menunggu' counts as used) const calculatedQuotaUsed = computed(() => { const list = queueStore.allPatients || []; return list.filter(p => { if (!isPatientRelevant(p)) return false; // Status is NOT 'menunggu' (meaning it's being served or finished) return p.status !== 'menunggu'; }).length; }); const quotaUsed = computed(() => calculatedQuotaUsed.value); // Expose dev helper to refresh seed data const resetPatients = () => queueStore.resetPatients(); // Filtered lists const filteredKliniks = computed(() => { let result = (queueStore.kliniks.value || queueStore.kliniks || []).filter(k => k.jenisLayanan === 'Reguler'); if (klinikSearch.value) { result = result.filter((k) => k.name.toLowerCase().includes(klinikSearch.value.toLowerCase()) ); } // Sort alphabetically by name return [...result].sort((a, b) => (a.name || "").localeCompare(b.name || "")); }); const filteredPenunjangs = computed(() => { let result = queueStore.penunjangs; if (penunjangSearch.value) { result = result.filter((p) => p.name.toLowerCase().includes(penunjangSearch.value.toLowerCase()) ); } // Sort alphabetically by name return [...result].sort((a, b) => (a.name || "").localeCompare(b.name || "")); }); const filteredChangeKliniks = computed(() => { let result = (queueStore.kliniks.value || queueStore.kliniks || []).filter(k => k.jenisLayanan === 'Reguler'); if (changeKlinikSearch.value) { result = result.filter((k) => k.name.toLowerCase().includes(changeKlinikSearch.value.toLowerCase()) ); } // Sort alphabetically by name return [...result].sort((a, b) => (a.name || "").localeCompare(b.name || "")); }); // Methods const showSnackbar = (text, color = "success") => { snackbarText.value = text; snackbarColor.value = color; snackbar.value = true; }; const callNext = (specificIdOverride = null) => { const targetId = specificIdOverride || idValue.value; const result = queueStore.callNext(adminType, targetId); showSnackbar(result.message, result.success ? "success" : "warning"); }; const callMultiplePatients = (count, specificIdOverride = null) => { const targetId = specificIdOverride || idValue.value; const result = queueStore.callMultiplePatients(count, adminType, targetId); showSnackbar(result.message, result.success ? "success" : "warning"); }; const processPatient = (patient, action) => { const result = queueStore.processPatient(patient, action, adminType, idValue.value); let color = "success"; if (action === "terlambat") color = "warning"; else if (action === "pending") color = "info"; showSnackbar(result.message, color); }; // Helper function untuk mendapatkan pasien yang sedang diproses dari store const getCurrentProcessingPatientFromStore = () => { try { const key = idValue.value ? `${adminType}-${idValue.value}` : adminType; const processingPatient = queueStore.currentProcessingPatient?.[key]; if (!processingPatient) return null; // Dapatkan data terbaru dari allPatients langsung (bukan dari getPatientsByStage yang sudah difilter) // allPatients adalah ref yang di-export dari Pinia store, bisa diakses langsung atau dengan .value // Coba akses langsung dulu, jika undefined baru coba dengan .value let allPatients = []; if (queueStore.allPatients) { // Jika allPatients adalah ref (punya .value) allPatients = Array.isArray(queueStore.allPatients) ? queueStore.allPatients : (queueStore.allPatients.value || []); } // Cari pasien berdasarkan no, barcode, atau noAntrian const latestPatient = allPatients.find( p => (p && p.no === processingPatient.no) || (p && p.barcode && p.barcode === processingPatient.barcode) || (p && p.noAntrian && p.noAntrian === processingPatient.noAntrian) ); // Jika ditemukan, return data terbaru, jika tidak return data dari currentProcessingPatient return latestPatient || processingPatient; } catch (error) { console.error('Error in getCurrentProcessingPatientFromStore:', error); // Fallback: return processingPatient dari store jika ada return queueStore.currentProcessingPatient?.[adminType] || null; } }; const selectKlinik = (klinik) => { // Pastikan currentProcessingPatient valid, jika tidak, coba dapatkan dari store let patient = currentProcessingPatient.value || getCurrentProcessingPatientFromStore(); if (!patient) { showSnackbar("Tidak ada pasien yang sedang diproses", "error"); showKlinikDialog.value = false; return; } const result = queueStore.createAntreanKlinik(klinik, patient, adminType); showSnackbar(result.message, "success"); showKlinikDialog.value = false; }; const selectPenunjang = (penunjang) => { // Pastikan currentProcessingPatient valid, jika tidak, coba dapatkan dari store let patient = currentProcessingPatient.value || getCurrentProcessingPatientFromStore(); if (!patient) { showSnackbar("Tidak ada pasien yang sedang diproses", "error"); showPenunjangDialog.value = false; selectedPatientForPenunjang.value = null; return; } const result = queueStore.createAntreanPenunjang( penunjang, patient, adminType ); showSnackbar(result.message, "success"); showPenunjangDialog.value = false; selectedPatientForPenunjang.value = null; }; const openPenunjangDialog = (patient = null) => { selectedPatientForPenunjang.value = patient; showPenunjangDialog.value = true; }; const changeKlinik = (klinik) => { // Pastikan currentProcessingPatient valid, jika tidak, coba dapatkan dari store let patient = currentProcessingPatient.value || getCurrentProcessingPatientFromStore(); if (!patient) { showSnackbar("Tidak ada pasien yang sedang diproses", "error"); showChangeKlinikDialog.value = false; return { success: false }; } const result = queueStore.changeKlinik( patient, klinik, adminType, idValue.value ); showSnackbar(result.message, result.success ? "success" : "error"); showChangeKlinikDialog.value = false; return result; }; const processNextQueue = () => { const result = queueStore.processNextQueue(adminType, idValue.value); showSnackbar(result.message, result.success ? "success" : "warning"); }; const getRowClass = (item) => { if (item.status === "current") { return "text-success font-weight-bold"; } return ""; }; return { // State snackbar, snackbarText, snackbarColor, showKlinikDialog, showPenunjangDialog, showChangeKlinikDialog, klinikSearch, penunjangSearch, changeKlinikSearch, selectedPatientForPenunjang, // Computed currentProcessingPatient, diLoketPatients, terlambatPatients, pendingPatients, anjunganPatients, menungguPatients, nextPatient, totalPasien, quotaUsed, filteredKliniks, filteredPenunjangs, filteredChangeKliniks, stagePatients, // Methods showSnackbar, callNext, callMultiplePatients, processPatient, selectKlinik, selectPenunjang, openPenunjangDialog, changeKlinik, processNextQueue, getRowClass, }; };