238 lines
8.6 KiB
TypeScript
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
|
|
};
|
|
};
|