This commit is contained in:
Fanrouver
2026-02-12 08:30:37 +07:00
parent 7fd99343bd
commit 1fbfa0e590
3 changed files with 119 additions and 149 deletions
+6 -4
View File
@@ -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),
+12 -49
View File
@@ -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(() => {
+101 -96
View File
@@ -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,