Files
web-antrean/composables/useQueueSync.ts
T

238 lines
8.6 KiB
TypeScript

import { ref } from 'vue';
import { useRuntimeConfig } from '#app';
import { useWebSocket } from '@/composables/useWebSocket';
import type { QueuePatient } from '@/types/queue';
export interface QueueSyncDeps {
allPatients: Ref<QueuePatient[]>;
currentProcessingPatient: Ref<Record<string, QueuePatient>>;
activeLoketInterest: Ref<Record<string, number>>;
activeClinicInterest: Ref<Record<string, number>>;
globalInterestCount: Ref<number>;
fetchPatientsForLoket: (id: string | number, force?: boolean) => void;
fetchPatientsForClinic: (id: string, force?: boolean) => void;
fetchAllPatients: () => void;
}
export const useQueueSync = (deps: QueueSyncDeps) => {
const isWsConnected = ref<boolean>(false);
const wsClientId = ref<string>(`client-${Math.random().toString(36).substring(7)}`);
const lastGlobalCall = ref<any>(null);
const lastKlinikCall = ref<any>(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
};
};