import { ref } from 'vue'; import { useRuntimeConfig } from '#app'; import { useWebSocket } from '@/composables/useWebSocket'; import type { QueuePatient } from '@/types/queue'; export interface QueueSyncDeps { allPatients: Ref; currentProcessingPatient: Ref>; activeLoketInterest: Ref>; activeClinicInterest: Ref>; globalInterestCount: Ref; fetchPatientsForLoket: (id: string | number, force?: boolean) => void; fetchPatientsForClinic: (id: string, force?: boolean) => void; fetchAllPatients: () => void; } export const useQueueSync = (deps: QueueSyncDeps) => { const isWsConnected = ref(false); const wsClientId = ref(`client-${Math.random().toString(36).substring(7)}`); const lastGlobalCall = ref(null); const lastKlinikCall = ref(null); const onWsMessage = (data: any) => { // Robust data extraction: some relays wrap data in another 'data' property let messageData = data?.data || data; if (messageData?.data && !messageData.callKlinikEvent && !messageData.callEvent) { messageData = messageData.data; // Double wrap check } const targetLoketId = messageData?.loketId || messageData?.idloket; const targetKlinikId = messageData?.klinikId || messageData?.idklinik; // Handle Call Events and WS messages if (messageData?.triggerRefresh) { if (messageData.klinikId) { // console.log(`🔄 [queueSync] Received refresh trigger for clinic ${messageData.klinikId}`); // Handle current processing update if provided if (messageData.currentProcessingUpdate) { // console.log(`đŸŽ¯ [queueSync] Applying current processing update:`, messageData.currentProcessingUpdate); Object.keys(messageData.currentProcessingUpdate).forEach(key => { deps.currentProcessingPatient.value[key] = messageData.currentProcessingUpdate[key]; // Also patch the status in allPatients if possible const processingPatient = messageData.currentProcessingUpdate[key]; if (processingPatient && processingPatient.no) { const idx = deps.allPatients.value.findIndex(p => p.no === processingPatient.no); if (idx !== -1) { deps.allPatients.value[idx] = { ...deps.allPatients.value[idx], status: 'di-loket' }; } } }); } deps.fetchPatientsForClinic(messageData.klinikId, true); } } if (messageData?.callEvent) { lastGlobalCall.value = messageData.callEvent; } // Handle Klinik Call Events (cross-device sync for AntrianKlinikRuang display) if (messageData?.callKlinikEvent) { const ev = messageData.callKlinikEvent; // console.log('đŸĨ [queueSync] Klinik call event received:', ev); // PERSISTENCE FIX: Save to lastKlinikCall for displays to watch lastKlinikCall.value = ev; // Find the patient in allPatients and patch directly for immediate UI update const idx = deps.allPatients.value.findIndex(p => p.processStage === 'klinik-ruang' && p.kodeKlinik === ev.kodeKlinik && ( (p.barcode && String(p.barcode) === String(ev.barcode)) || (p.noAntrian && p.noAntrian.split(' |')[0] === ev.noantrian) ) ); if (idx !== -1) { const updatedPatient = { ...deps.allPatients.value[idx], tipeLayanan: ev.tipeLayanan, lastCalledAt: ev.lastCalledAt || new Date().toISOString(), lastCalledTipeLayanan: ev.tipeLayanan, status: 'di-loket' as any, calledPemeriksaanAwal: ev.tipeLayanan === 'Pemeriksaan Awal' ? true : deps.allPatients.value[idx].calledPemeriksaanAwal, calledTindakan: ev.tipeLayanan === 'Tindakan' ? true : deps.allPatients.value[idx].calledTindakan }; deps.allPatients.value[idx] = updatedPatient; // console.log(`✅ [queueSync] Successfully patched patient ${ev.noantrian} status to di-loket (lastCalledAt: ${updatedPatient.lastCalledAt})`); } else { console.warn(`âš ī¸ [queueSync] Patient ${ev.noantrian} not found in store for clinic ${ev.kodeKlinik}.`); } } // TRIGGER STRATEGIC REFRESHES let refreshedSomething = false; if (targetLoketId) { deps.fetchPatientsForLoket(targetLoketId, true); refreshedSomething = true; } if (targetKlinikId) { const interestingClinics = Object.keys(deps.activeClinicInterest.value); if (interestingClinics.includes(String(targetKlinikId)) || targetKlinikId === 'broadcast') { const clinicToFetch = targetKlinikId === 'broadcast' ? interestingClinics[0] : targetKlinikId; deps.fetchPatientsForClinic(clinicToFetch, true); refreshedSomething = true; } } if (deps.globalInterestCount.value > 0) { deps.fetchAllPatients(); refreshedSomething = true; } // ALWAYS refresh our own active interests when a WebSocket message is received const interestingLokets = Object.keys(deps.activeLoketInterest.value); const interestingClinics = Object.keys(deps.activeClinicInterest.value); if (interestingLokets.length > 0) { interestingLokets.forEach(loketId => { if (String(loketId) !== String(targetLoketId)) { deps.fetchPatientsForLoket(loketId, true); refreshedSomething = true; } }); } if (interestingClinics.length > 0) { interestingClinics.forEach(kodeKlinik => { if (String(kodeKlinik) !== String(targetKlinikId)) { deps.fetchPatientsForClinic(kodeKlinik, true); refreshedSomething = true; } }); } if (!refreshedSomething) { // console.log(`🔕 [queueSync] WS trigger received but no active interest matched. Skipping.`); } }; const config = useRuntimeConfig(); const wsBaseUrl = config.public?.wsBaseUrl || "ws://10.10.123.135:8084/api/v1/ws"; const { connect, disconnect, sendViaPost, isConnected } = useWebSocket({ url: wsBaseUrl, clientId: wsClientId, fallbackPostUrl: `${config.public.externalApiBaseUrl}/ws`, reconnectInterval: 2000, maxReconnectAttempts: 9999, onOpen: () => { // console.log('✅ [queueSync] WebSocket connected'); isWsConnected.value = true; }, onClose: () => { // console.log('❌ [queueSync] WebSocket disconnected'); isWsConnected.value = false; }, onError: (err: any) => { console.error('âš ī¸ [queueSync] WebSocket error:', err); isWsConnected.value = false; }, onMessage: onWsMessage }); let _autoSyncInterval: any = null; const startAutoSync = () => { if (typeof window === 'undefined') return; if (_autoSyncInterval) return; // console.log('🔄 [queueSync] Starting store-level auto-sync (30s interval)'); _autoSyncInterval = setInterval(async () => { const hasLoketInterest = Object.keys(deps.activeLoketInterest.value).length > 0; const hasClinicInterest = Object.keys(deps.activeClinicInterest.value).length > 0; const hasGlobalInterest = deps.globalInterestCount.value > 0; if (hasGlobalInterest) { deps.fetchAllPatients(); } else { if (hasLoketInterest) { Object.keys(deps.activeLoketInterest.value).forEach(loketId => { deps.fetchPatientsForLoket(loketId, true); }); } if (hasClinicInterest) { Object.keys(deps.activeClinicInterest.value).forEach(kodeKlinik => { deps.fetchPatientsForClinic(kodeKlinik, true); }); } } }, 30000); // 30 seconds }; const stopAutoSync = () => { if (_autoSyncInterval) { clearInterval(_autoSyncInterval); _autoSyncInterval = null; // console.log('âšī¸ [queueSync] Store-level auto-sync stopped'); } }; const initWebSocket = (customClientId: string | null = null) => { if (isConnected.value && customClientId === wsClientId.value) { // console.log('🔌 [queueSync] WebSocket already connected with same ID.'); startAutoSync(); return; } if (customClientId) { wsClientId.value = customClientId; disconnect(); } // console.log(`🔌 [queueSync] Connecting to WebSocket: ${wsBaseUrl} as ${wsClientId.value}`); connect(); startAutoSync(); }; return { isWsConnected, wsClientId, lastGlobalCall, lastKlinikCall, initWebSocket, disconnectWebSocket: disconnect, sendViaPost, startAutoSync, stopAutoSync }; };