From 1fbfa0e590e39e82d973f3bf695512bab264e16a Mon Sep 17 00:00:00 2001 From: Fanrouver Date: Thu, 12 Feb 2026 08:30:37 +0700 Subject: [PATCH] dasd --- composables/useWebSocket.ts | 10 +- pages/AdminKlinikRuang/[kodeKlinik].vue | 61 ++------ stores/queueStore.js | 197 ++++++++++++------------ 3 files changed, 119 insertions(+), 149 deletions(-) diff --git a/composables/useWebSocket.ts b/composables/useWebSocket.ts index cca7294..e59d7d3 100644 --- a/composables/useWebSocket.ts +++ b/composables/useWebSocket.ts @@ -1,5 +1,5 @@ // composables/useWebSocket.ts -import { ref, computed, onUnmounted } from 'vue' +import { ref, computed, onUnmounted, getCurrentInstance } from 'vue' export interface WebSocketConfig { url: string @@ -168,9 +168,11 @@ export const useWebSocket = (config: WebSocketConfig) => { } } - onUnmounted(() => { - disconnect() - }) + if (getCurrentInstance()) { + onUnmounted(() => { + disconnect() + }) + } return { ws: computed(() => ws.value), diff --git a/pages/AdminKlinikRuang/[kodeKlinik].vue b/pages/AdminKlinikRuang/[kodeKlinik].vue index cd27871..f9d9443 100644 --- a/pages/AdminKlinikRuang/[kodeKlinik].vue +++ b/pages/AdminKlinikRuang/[kodeKlinik].vue @@ -933,7 +933,6 @@ const filterOptionsList = { // WebSocket configuration const config = useRuntimeConfig(); -const wsBaseUrl = config.public?.wsBaseUrl || 'ws://10.10.150.100:8084/api/v1/ws'; // Generate a unique session suffix (random ID) const uniqueSessionSuffix = ref(process.client ? Math.random().toString(36).substring(2, 8) : '') @@ -943,34 +942,28 @@ const adminClientId = computed(() => { return `admin-klinik-ruang-${kodeKlinik.value}-${uniqueSessionSuffix.value}` }) +const isConnected = computed(() => queueStore.isWsConnected); +const sendViaPost = (data) => queueStore.sendViaPost(data); + const fetchAllData = async () => { if (!kodeKlinik.value) return; - console.log('🔄 AdminKlinikRuang refresh: Syncing data...'); + // console.log('🔄 AdminKlinikRuang refresh: Syncing data...'); try { await queueStore.fetchPatientsForClinic(kodeKlinik.value); queueStore.ensureInitialData(); - console.log('✅ AdminKlinikRuang refresh: Success'); + // console.log('✅ AdminKlinikRuang refresh: Success'); } catch (err) { console.error('❌ AdminKlinikRuang refresh error:', err); } }; -// Initialize WebSocket for sending messages -const { sendViaPost } = useWebSocket({ - url: wsBaseUrl, - clientId: adminClientId.value, - fallbackPostUrl: '/stats-api/ws' -}); - -const isConnected = computed(() => queueStore.isWsConnected); - // Watch for clientId changes and reconnect if needed watch(adminClientId, (newClientId, oldClientId) => { if (newClientId && newClientId !== oldClientId) { console.log('🔄 Client ID changed, reconnecting centralized WebSocket...') queueStore.initWebSocket(newClientId) } -}) +}, { immediate: true }) const showSnackbar = (message, color = 'success') => { snackbarText.value = message; @@ -1614,8 +1607,6 @@ const broadcastUpdate = async () => { } }); - console.log('📡 [AdminKlinikRuang] Broadcasting update trigger to:', anjunganClientIds); - for (const clientId of anjunganClientIds) { await sendViaPost({ to_client: clientId, @@ -1659,24 +1650,20 @@ const handleCallPatientByTipe = async (ruang, tipeLayanan) => { return; } - // Update tipeLayanan pasien untuk tracking panggilan terakhir const patientIndex = queueStore.allPatients.findIndex(p => p.no === current.no); if (patientIndex === -1) { showSnackbar('Pasien tidak ditemukan', 'error'); return; } - // Update tracking panggilan berdasarkan tipeLayanan - // Update tipeLayanan pasien agar muncul di kolom yang sesuai di Anjungan const updateData = { ...queueStore.allPatients[patientIndex], status: 'di-loket', - tipeLayanan: tipeLayanan, // Update tipeLayanan untuk display di Anjungan + tipeLayanan: tipeLayanan, lastCalledAt: new Date().toISOString(), lastCalledTipeLayanan: tipeLayanan }; - // Set penanda panggilan sesuai tipeLayanan if (tipeLayanan === 'Pemeriksaan Awal') { updateData.calledPemeriksaanAwal = true; } else if (tipeLayanan === 'Tindakan') { @@ -1685,7 +1672,6 @@ const handleCallPatientByTipe = async (ruang, tipeLayanan) => { queueStore.allPatients[patientIndex] = updateData; - // Update current processing (tetap 1 pasien, tidak dipisah per tipe) const key = `klinik-ruang-${klinikData.value.kodeKlinik}-${ruang.nomorRuang}`; queueStore.currentProcessingPatient[key] = queueStore.allPatients[patientIndex]; @@ -1693,15 +1679,10 @@ const handleCallPatientByTipe = async (ruang, tipeLayanan) => { queueStore.allPatients[patientIndex].barcode || '-'; - // Send via WebSocket to Anjungan clients try { - // Get all possible client IDs for this klinik (anjungan screens) const anjunganClientIds = []; - - // Always send to client ID without screen number (broadcast to all screens for this klinik) anjunganClientIds.push(`anjungan-klinik-ruang-${klinikData.value.kodeKlinik}`); - // Also send to specific screen if ruang has nomorScreen if (ruang.nomorScreen) { const specificScreenId = `anjungan-klinik-ruang-${klinikData.value.kodeKlinik}-screen-${ruang.nomorScreen}`; if (!anjunganClientIds.includes(specificScreenId)) { @@ -1709,7 +1690,6 @@ const handleCallPatientByTipe = async (ruang, tipeLayanan) => { } } - // Also send to all other screens for this klinik (from ruangList) ruangList.value.forEach(r => { if (r.nomorScreen) { const screenId = `anjungan-klinik-ruang-${klinikData.value.kodeKlinik}-screen-${r.nomorScreen}`; @@ -1719,36 +1699,20 @@ const handleCallPatientByTipe = async (ruang, tipeLayanan) => { } }); - console.log('📋 All target client IDs:', anjunganClientIds); - - // Extract nomor antrian (ambil bagian sebelum " |") const nomorAntrian = updateData.noAntrian?.split(" |")[0] || updateData.barcode || '-'; - console.log('📤 Sending WebSocket message via POST...'); - console.log('📍 Target clients:', anjunganClientIds); - console.log('📦 Nomor antrian:', nomorAntrian); - console.log('📦 Tipe layanan:', tipeLayanan); - - // Send to all relevant clients for (const clientId of anjunganClientIds) { - // Kirim nomor antrian + tipe layanan agar Anjungan tahu harus tampilkan di kolom mana const message = { to_client: clientId, data: { noantrian: nomorAntrian, - tipeLayanan: tipeLayanan // "Pemeriksaan Awal" atau "Tindakan" + tipeLayanan: tipeLayanan }, }; - - console.log(`📨 Sending to ${clientId}:`, message); - const result = await sendViaPost(message); - console.log(`✅ Response from ${clientId}:`, result); + await sendViaPost(message); } - - console.log('✅ All WebSocket messages sent successfully'); } catch (error) { console.error('❌ Error sending WebSocket message:', error); - // Don't block the UI if WebSocket fails } showSnackbar(`Memanggil pasien ${patientCode} untuk ${tipeLayanan}`, 'success'); @@ -1920,12 +1884,11 @@ onMounted(async () => { console.error('❌ Error syncing data in AdminKlinikRuang:', error); } - // 2. Initial fetch and periodic polling + // 2. Initial fetch await fetchAllData(); // 3. Centralized WebSocket & interest registration - queueStore.initWebSocket(adminClientId.value); - + // initWebSocket is already called via the watcher on adminClientId if (kodeKlinik.value) { queueStore.registerClinicInterest(kodeKlinik.value); } @@ -1934,7 +1897,7 @@ onMounted(async () => { // 4. Lifecycle cleanup (Keep outside async block to preserve context) let pollInterval; onMounted(() => { - pollInterval = setInterval(fetchAllData, 30000); + pollInterval = setInterval(fetchAllData, 60000); // Reduce fallback polling to 60s }); onUnmounted(() => { diff --git a/stores/queueStore.js b/stores/queueStore.js index a00e55a..d01f441 100644 --- a/stores/queueStore.js +++ b/stores/queueStore.js @@ -247,123 +247,127 @@ export const useQueueStore = defineStore('queue', () => { // ============================================ // WEBSOCKET INTEGRATION (CENTRALIZED) // ============================================ - const wsInstance = ref(null); + // ============================================ + // WEBSOCKET INTEGRATION (CENTRALIZED) + // ============================================ const isWsConnected = ref(false); const wsClientId = ref(`client-${Math.random().toString(36).substring(7)}`); + + const onWsMessage = (data) => { + const messageData = data?.data || data; + const targetLoketId = messageData?.loketId || messageData?.idloket; + const targetKlinikId = messageData?.klinikId || messageData?.idklinik; + const isTrigger = messageData?.triggerRefresh || targetLoketId || targetKlinikId; + + if (!isTrigger) { + // console.log('📨 [queueStore] Global WS Message received (No trigger, skipping refresh)'); + return; + } + + console.log('📨 [queueStore] Global WS Trigger Message received:', data); + + // TRIGGER STRATEGIC REFRESHES + let refreshedSomething = false; + + if (targetLoketId) { + // 1. If message specifies a loket, refresh that one specifically + console.log(`🎯 [queueStore] WS targeting Loket ${targetLoketId}: Refreshing...`); + fetchPatientsForLoket(targetLoketId); + refreshedSomething = true; + } + + if (targetKlinikId) { + // 2. If message specifies a klinik, refresh those clinics + // Check if it matches any of our active interests or if it's a general trigger + const interestingClinics = Object.keys(activeClinicInterest.value); + if (interestingClinics.includes(String(targetKlinikId)) || targetKlinikId === 'broadcast') { + console.log(`🎯 [queueStore] WS targeting Clinic ${targetKlinikId}: Refreshing...`); + fetchPatientsForClinic(targetKlinikId === 'broadcast' ? interestingClinics[0] : targetKlinikId); + refreshedSomething = true; + } + } + + // 3. If we have global interest (e.g. Check-in page open), always refresh everything on ANY trigger + if (globalInterestCount.value > 0) { + console.log(`📡 [queueStore] WS trigger: Global Interest active, triggering staggered bulk refresh...`); + fetchAllPatients(); + refreshedSomething = true; + } + + // 4. Fallback: If nothing specific was refreshed but we have generic interest, refresh all active things + if (!refreshedSomething) { + const interestingLokets = Object.keys(activeLoketInterest.value); + const interestingClinics = Object.keys(activeClinicInterest.value); + + if (interestingLokets.length > 0) { + console.log(`🌐 [queueStore] WS trigger: Refreshing ${interestingLokets.length} active lokets:`, interestingLokets); + interestingLokets.forEach(loketId => { + fetchPatientsForLoket(loketId); + }); + refreshedSomething = true; + } + + if (interestingClinics.length > 0) { + console.log(`🌐 [queueStore] WS trigger: Refreshing ${interestingClinics.length} active clinics:`, interestingClinics); + interestingClinics.forEach(kodeKlinik => { + fetchPatientsForClinic(kodeKlinik); + }); + refreshedSomething = true; + } + } + + if (!refreshedSomething) { + console.log(`🔕 [queueStore] WS trigger received but no active interest matched. Skipping.`); + } + }; + + const config = useRuntimeConfig(); + const wsBaseUrl = config.public?.wsBaseUrl || "ws://10.10.150.100:8084/api/v1/ws"; + + const { connect, disconnect, sendViaPost, isConnected } = useWebSocket({ + url: wsBaseUrl, + clientId: wsClientId.value, + onOpen: () => { + console.log('✅ [queueStore] WebSocket connected'); + isWsConnected.value = true; + }, + onClose: () => { + console.log('❌ [queueStore] WebSocket disconnected'); + isWsConnected.value = false; + }, + onError: (err) => { + console.error('⚠️ [queueStore] WebSocket error:', err); + isWsConnected.value = false; + }, + onMessage: onWsMessage + }); /** * Initialize Global WebSocket */ const initWebSocket = (customClientId = null) => { - if (wsInstance.value && isWsConnected.value) { - console.log('🔌 [queueStore] WebSocket already connected.'); + if (isConnected.value && customClientId === wsClientId.value) { + console.log('🔌 [queueStore] WebSocket already connected with same ID.'); return; } if (customClientId) { wsClientId.value = customClientId; + // Re-connect with new ID if changed + disconnect(); + // useWebSocket will use the new wsClientId.value if it's reactive } - const config = useRuntimeConfig(); - const wsBaseUrl = config.public?.wsBaseUrl || "ws://10.10.150.100:8084/api/v1/ws"; - console.log(`🔌 [queueStore] Connecting to WebSocket: ${wsBaseUrl} as ${wsClientId.value}`); - - wsInstance.value = useWebSocket({ - url: wsBaseUrl, - clientId: wsClientId.value, - onOpen: () => { - console.log('✅ [queueStore] WebSocket connected'); - isWsConnected.value = true; - }, - onClose: () => { - console.log('❌ [queueStore] WebSocket disconnected'); - isWsConnected.value = false; - }, - onError: (err) => { - console.error('⚠️ [queueStore] WebSocket error:', err); - isWsConnected.value = false; - }, - onMessage: (data) => { - const messageData = data?.data || data; - const targetLoketId = messageData?.loketId || messageData?.idloket; - const targetKlinikId = messageData?.klinikId || messageData?.idklinik; - const isTrigger = messageData?.triggerRefresh || targetLoketId || targetKlinikId; - - if (!isTrigger) { - console.log('📨 [queueStore] Global WS Message received (No trigger, skipping refresh)'); - return; - } - - console.log('📨 [queueStore] Global WS Trigger Message received:', data); - - // TRIGGER STRATEGIC REFRESHES - let refreshedSomething = false; - - if (targetLoketId) { - // 1. If message specifies a loket, refresh that one specifically - console.log(`🎯 [queueStore] WS targeting Loket ${targetLoketId}: Refreshing...`); - fetchPatientsForLoket(targetLoketId); - refreshedSomething = true; - } - - if (targetKlinikId) { - // 2. If message specifies a klinik, refresh those clinics - console.log(`🎯 [queueStore] WS targeting Clinic ${targetKlinikId}: Refreshing active clinic interests...`); - const interestingClinics = Object.keys(activeClinicInterest.value); - interestingClinics.forEach(kodeKlinik => { - fetchPatientsForClinic(kodeKlinik); - }); - refreshedSomething = true; - } - - // 3. If we have global interest (e.g. Check-in page open), always refresh everything on ANY trigger - if (globalInterestCount.value > 0) { - console.log(`📡 [queueStore] WS trigger: Global Interest active, triggering staggered bulk refresh...`); - fetchAllPatients(); - refreshedSomething = true; - } - - // 4. Fallback: If nothing specific was refreshed but we have generic interest, refresh all active things - if (!refreshedSomething) { - const interestingLokets = Object.keys(activeLoketInterest.value); - const interestingClinics = Object.keys(activeClinicInterest.value); - - if (interestingLokets.length > 0) { - console.log(`🌐 [queueStore] WS trigger: Refreshing ${interestingLokets.length} active lokets:`, interestingLokets); - interestingLokets.forEach(loketId => { - fetchPatientsForLoket(loketId); - }); - refreshedSomething = true; - } - - if (interestingClinics.length > 0) { - console.log(`🌐 [queueStore] WS trigger: Refreshing ${interestingClinics.length} active clinics:`, interestingClinics); - interestingClinics.forEach(kodeKlinik => { - fetchPatientsForClinic(kodeKlinik); - }); - refreshedSomething = true; - } - } - - if (!refreshedSomething) { - console.log(`🔕 [queueStore] WS trigger received but no active interest matched. Skipping.`); - } - } - }); - - wsInstance.value.connect(); + connect(); }; /** * Disconnect Global WebSocket */ const disconnectWebSocket = () => { - if (wsInstance.value) { - wsInstance.value.disconnect(); - wsInstance.value = null; - isWsConnected.value = false; - } + disconnect(); + isWsConnected.value = false; }; /** @@ -3173,6 +3177,7 @@ export const useQueueStore = defineStore('queue', () => { initWebSocket, disconnectWebSocket, isWsConnected, + sendViaPost, registerInterest, unregisterInterest, activeLoketInterest,