dasd
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user