push update
This commit is contained in:
@@ -986,40 +986,38 @@ const showSnackbar = (message, color = 'success') => {
|
||||
|
||||
// Get all patients for room (menggunakan data dari API)
|
||||
const getAllPatientsForRoom = (ruang) => {
|
||||
// Debug logging
|
||||
console.log('🔍 Filtering patients for room:', {
|
||||
ruangName: ruang.namaRuang,
|
||||
ruangNomor: ruang.nomorRuang,
|
||||
klinikCode: klinikData.value?.kodeKlinik
|
||||
});
|
||||
|
||||
// Prioritize API patients, fallback to queueStore patients
|
||||
const patients = queueStore.allPatients
|
||||
.filter(p => {
|
||||
const matches =
|
||||
p.kodeKlinik === klinikData.value?.kodeKlinik &&
|
||||
p.nomorRuang === ruang.nomorRuang &&
|
||||
p.processStage === 'klinik-ruang' &&
|
||||
// CRITICAL: Only show patients with status "pemeriksaan"
|
||||
// This prevents duplicate patients with different statuses
|
||||
p.status === 'pemeriksaan';
|
||||
|
||||
if (!matches && p.kodeKlinik === klinikData.value?.kodeKlinik) {
|
||||
console.log('❌ Patient did not match room:', {
|
||||
ticket: p.noAntrian,
|
||||
patientRoom: p.nomorRuang,
|
||||
expectedRoom: ruang.nomorRuang,
|
||||
stage: p.processStage,
|
||||
status: p.status
|
||||
});
|
||||
}
|
||||
p.processStage === 'klinik-ruang';
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
console.log(` ✅ Found ${patients.length} patients for room ${ruang.namaRuang}`);
|
||||
// Deduplicate by barcode/visitId (keep the one with most advanced status)
|
||||
const deduplicatedMap = new Map();
|
||||
const statusPriority = { 'processed': 4, 'diproses': 3, 'pemeriksaan': 2, 'di-loket': 1 };
|
||||
|
||||
return patients;
|
||||
patients.forEach(p => {
|
||||
const key = p.visitId || p.barcode || p.no;
|
||||
const existing = deduplicatedMap.get(key);
|
||||
|
||||
if (!existing) {
|
||||
deduplicatedMap.set(key, p);
|
||||
} else {
|
||||
const pPrio = statusPriority[p.status] || 0;
|
||||
const ePrio = statusPriority[existing.status] || 0;
|
||||
|
||||
if (pPrio > ePrio) {
|
||||
deduplicatedMap.set(key, p);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(deduplicatedMap.values());
|
||||
};
|
||||
|
||||
// Get filtered and sorted patients for room
|
||||
@@ -1350,6 +1348,9 @@ const confirmPindahRuang = () => {
|
||||
'success'
|
||||
);
|
||||
|
||||
// Trigger real-time update
|
||||
broadcastUpdate();
|
||||
|
||||
closeKelolaPasienDialog();
|
||||
};
|
||||
|
||||
@@ -1374,6 +1375,7 @@ const confirmPindahKlinik = async () => {
|
||||
|
||||
if (result.success) {
|
||||
showSnackbar(result.message, 'success');
|
||||
broadcastUpdate();
|
||||
closeKelolaPasienDialog();
|
||||
} else {
|
||||
showSnackbar(result.message, 'error');
|
||||
@@ -1414,6 +1416,7 @@ const confirmKonsultasi = async () => {
|
||||
}
|
||||
|
||||
closeKelolaPasienDialog();
|
||||
broadcastUpdate();
|
||||
} else {
|
||||
showSnackbar(result.message, 'error');
|
||||
}
|
||||
@@ -1589,6 +1592,42 @@ const toggleLayananStatus = (tipeLayanan) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger real-time update for Anjungan
|
||||
broadcastUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Broadcast update to all Anjungan screens for this clinic
|
||||
*/
|
||||
const broadcastUpdate = async () => {
|
||||
try {
|
||||
const anjunganClientIds = [];
|
||||
|
||||
// Base broadcast ID
|
||||
anjunganClientIds.push(`anjungan-klinik-ruang-${klinikData.value.kodeKlinik}`);
|
||||
|
||||
// Screen-specific IDs
|
||||
ruangList.value.forEach(r => {
|
||||
if (r.nomorScreen) {
|
||||
anjunganClientIds.push(`anjungan-klinik-ruang-${klinikData.value.kodeKlinik}-screen-${r.nomorScreen}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📡 [AdminKlinikRuang] Broadcasting update trigger to:', anjunganClientIds);
|
||||
|
||||
for (const clientId of anjunganClientIds) {
|
||||
await sendViaPost({
|
||||
to_client: clientId,
|
||||
data: {
|
||||
klinikId: klinikData.value.kodeKlinik,
|
||||
triggerRefresh: true
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ [AdminKlinikRuang] Error broadcasting update:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
@@ -1743,6 +1782,9 @@ const handlePatientAction = (ruang, action) => {
|
||||
}
|
||||
|
||||
showSnackbar(result.message, result.success ? 'success' : 'error');
|
||||
if (result.success) {
|
||||
broadcastUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle proses pasien (untuk pasien yang belum diproses)
|
||||
@@ -1762,6 +1804,9 @@ const handleProcessPatient = (ruang, patient) => {
|
||||
);
|
||||
|
||||
showSnackbar(result.message, result.success ? 'success' : 'error');
|
||||
if (result.success) {
|
||||
broadcastUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
const handleProcessPendingPatient = (ruang, patient) => {
|
||||
@@ -1787,6 +1832,9 @@ const handleProcessPendingPatient = (ruang, patient) => {
|
||||
);
|
||||
|
||||
showSnackbar(result.message, result.success ? 'success' : 'error');
|
||||
if (result.success) {
|
||||
broadcastUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
const paginatedAllPatients = (ruang) => {
|
||||
|
||||
+42
-18
@@ -378,9 +378,6 @@ onMounted(async () => {
|
||||
}
|
||||
}, 60000); // Check every minute
|
||||
|
||||
// Polling every 10 seconds to keep data synchronized as fallback
|
||||
const pollingInterval = setInterval(fetchAllData, 10000);
|
||||
|
||||
// Initialize and connect WebSocket (Centralized)
|
||||
queueStore.initWebSocket(anjunganClientId.value);
|
||||
|
||||
@@ -388,7 +385,6 @@ onMounted(async () => {
|
||||
queueStore.registerInterest(loketId.value);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(pollingInterval);
|
||||
// Unregister interest when leaving the page
|
||||
queueStore.unregisterInterest(loketId.value);
|
||||
});
|
||||
@@ -785,16 +781,50 @@ const handleCall = async (count) => {
|
||||
} else {
|
||||
callMultiplePatients(count, loketId.value);
|
||||
}
|
||||
|
||||
// Real-time broadcast
|
||||
broadcastUpdate();
|
||||
|
||||
// Force Vue to process reactivity updates
|
||||
await nextTick();
|
||||
};
|
||||
|
||||
const handleTableAction = (item, action) => {
|
||||
processPatient(item, action);
|
||||
// Real-time broadcast after process
|
||||
broadcastUpdate();
|
||||
};
|
||||
|
||||
const handleProcessNext = () => {
|
||||
processNextQueue();
|
||||
// Real-time broadcast after process next
|
||||
broadcastUpdate();
|
||||
};
|
||||
|
||||
const { sendViaPost } = useWebSocket();
|
||||
|
||||
const broadcastUpdate = async () => {
|
||||
try {
|
||||
const displayClientIds = [];
|
||||
|
||||
// Base broadcast IDs for this loket
|
||||
displayClientIds.push(`anjungan-loket-${loketId.value}`);
|
||||
displayClientIds.push(`anjungan-masuk-${loketId.value}`);
|
||||
|
||||
console.log('📡 [AdminLoket] Broadcasting update trigger to:', displayClientIds);
|
||||
|
||||
for (const clientId of displayClientIds) {
|
||||
await sendViaPost({
|
||||
to_client: clientId,
|
||||
data: {
|
||||
loketId: loketId.value,
|
||||
triggerRefresh: true
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ [AdminLoket] Error broadcasting update:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCallPatient = () => {
|
||||
@@ -805,7 +835,10 @@ const handleCallPatient = () => {
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
|
||||
// BROADCAST CALL EVENT
|
||||
// WebSocket Broadcast for remote displays
|
||||
broadcastUpdate();
|
||||
|
||||
// local BROADCAST CALL EVENT for same-browser tabs
|
||||
if (broadcastChannel) {
|
||||
broadcastChannel.postMessage({
|
||||
type: "CALL_PATIENT",
|
||||
@@ -1096,20 +1129,11 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
|
||||
);
|
||||
|
||||
if (result.success && result.patient) {
|
||||
// CRITICAL: Update source patient status to "processed" after klinik ruang creation
|
||||
// This prevents duplicate patients with different statuses in Admin Klinik Ruang
|
||||
const sourcePatientIndex = queueStore.allPatients.findIndex(
|
||||
(p) => p.no === patient.no,
|
||||
// Patient remains in current status until admin clicks "Selesai"
|
||||
// This ensures the workflow can be completed properly
|
||||
console.log(
|
||||
`✅ Room queue created for patient ${patient.noAntrian}. Patient remains in current status.`,
|
||||
);
|
||||
if (sourcePatientIndex !== -1) {
|
||||
queueStore.allPatients[sourcePatientIndex] = {
|
||||
...queueStore.allPatients[sourcePatientIndex],
|
||||
status: "processed",
|
||||
};
|
||||
console.log(
|
||||
`✅ Updated source patient ${patient.noAntrian} status to "processed"`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await printTicketFromPatient(result.patient);
|
||||
|
||||
@@ -784,14 +784,11 @@ onMounted(async () => {
|
||||
// Initial fetch
|
||||
await fetchAllData();
|
||||
|
||||
// Polling every 10 seconds to keep data synchronized as fallback
|
||||
const pollingInterval = setInterval(fetchAllData, 10000);
|
||||
|
||||
// Use centralized WebSocket
|
||||
queueStore.initWebSocket(anjunganClientId.value);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(pollingInterval);
|
||||
// wsInstance is now global
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -588,7 +588,7 @@ onMounted(() => {
|
||||
|
||||
// Wait for stores to be hydrated from persisted state
|
||||
// Use nextTick to ensure stores are ready
|
||||
nextTick(() => {
|
||||
nextTick(async () => {
|
||||
// Redirect to index if screen not found
|
||||
if (!screenData.value) {
|
||||
navigateTo('/anjungan/antreanmasuk')
|
||||
@@ -596,21 +596,21 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
fetchAllData();
|
||||
|
||||
// Polling every 10 seconds to keep data synchronized as fallback
|
||||
const pollingInterval = setInterval(fetchAllData, 10000);
|
||||
await fetchAllData();
|
||||
|
||||
// Initialize and connect WebSocket (Centralized)
|
||||
queueStore.initWebSocket(anjunganClientId.value);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(pollingInterval);
|
||||
// wsInstance is now global
|
||||
});
|
||||
// Register global interest to receive staggered bulk refreshes on generic WS messages
|
||||
queueStore.registerGlobalInterest();
|
||||
|
||||
updateTime()
|
||||
timeInterval = setInterval(updateTime, 1000)
|
||||
onUnmounted(() => {
|
||||
// Unregister global interest when leaving the page
|
||||
queueStore.unregisterGlobalInterest();
|
||||
});
|
||||
|
||||
updateTime();
|
||||
timeInterval = setInterval(updateTime, 1000);
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -353,12 +353,23 @@ onMounted(() => {
|
||||
|
||||
updateTime();
|
||||
timeInterval = setInterval(updateTime, 1000);
|
||||
refreshInterval = setInterval(() => {}, 5000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) clearInterval(timeInterval);
|
||||
if (refreshInterval) clearInterval(refreshInterval);
|
||||
// Initialize and connect WebSocket (Centralized)
|
||||
const anjunganClientId = `anjungan-klinik-${screenId.value}`;
|
||||
queueStore.initWebSocket(anjunganClientId);
|
||||
|
||||
// Register interest in each clinic on this screen for scoped WebSocket refreshes
|
||||
screenKliniks.value.forEach(k => {
|
||||
queueStore.registerClinicInterest(k.kode);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeInterval) clearInterval(timeInterval);
|
||||
// Unregister clinic interests
|
||||
screenKliniks.value.forEach(k => {
|
||||
queueStore.unregisterClinicInterest(k.kode);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -579,17 +579,10 @@ onMounted(async () => {
|
||||
updateTime()
|
||||
timeInterval = setInterval(updateTime, 1000)
|
||||
|
||||
// Polling every 10 seconds to keep data synchronized as fallback
|
||||
const pollingInterval = setInterval(fetchAllData, 10000);
|
||||
|
||||
// Initialize and connect WebSocket (Centralized)
|
||||
// WebSocket initialization is now centralized in queueStore
|
||||
if (kodeKlinik.value) {
|
||||
queueStore.initWebSocket(anjunganClientId.value);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(pollingInterval);
|
||||
});
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -995,9 +995,6 @@ onMounted(async () => {
|
||||
// Initial fetch
|
||||
await fetchAllData();
|
||||
|
||||
// Polling every 10 seconds to keep data synchronized as fallback
|
||||
const pollingInterval = setInterval(fetchAllData, 10000);
|
||||
|
||||
// Initialize and connect WebSocket (Centralized)
|
||||
queueStore.initWebSocket(anjunganClientId.value);
|
||||
|
||||
@@ -1005,7 +1002,6 @@ onMounted(async () => {
|
||||
queueStore.registerInterest(loketId.value);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(pollingInterval);
|
||||
// Unregister interest when leaving the page
|
||||
queueStore.unregisterInterest(loketId.value);
|
||||
});
|
||||
|
||||
@@ -2724,9 +2724,6 @@ onMounted(async () => {
|
||||
// Initial fetch/sync
|
||||
await fetchAllData();
|
||||
|
||||
// Polling every 10 seconds to keep data synchronized as fallback
|
||||
const pollingInterval = setInterval(fetchAllData, 10000);
|
||||
|
||||
// Initialize WebSocket (Centralized)
|
||||
queueStore.initWebSocket(checkInClientId.value);
|
||||
|
||||
@@ -2739,7 +2736,6 @@ onMounted(async () => {
|
||||
}, 60000); // Check every minute
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(pollingInterval);
|
||||
clearInterval(resetCheckInterval);
|
||||
// Unregister global interest
|
||||
queueStore.unregisterGlobalInterest();
|
||||
|
||||
+356
-48
@@ -172,10 +172,28 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
newPatientMap.set(key, p);
|
||||
});
|
||||
|
||||
// CRITICAL FIX: Collect currently processing patient numbers to protect them
|
||||
const processingPatientNos = new Set(
|
||||
Object.values(currentProcessingPatient.value || {})
|
||||
.filter(p => p)
|
||||
.map(p => p.no)
|
||||
);
|
||||
|
||||
// Update existing allPatients while preserving local UI state
|
||||
allPatients.value = allPatients.value.map(p => {
|
||||
if (p.processStage !== 'klinik-ruang' || p.kodeKlinik !== kodeKlinik) return p;
|
||||
|
||||
// CRITICAL FIX: Protect currently processing patients from being removed
|
||||
if (processingPatientNos.has(p.no)) {
|
||||
// console.log(`🛡️ [queueStore] Protecting currently processing patient ${p.noAntrian} from clinic refresh`);
|
||||
const key = p.visitId ? `vid-${p.visitId}` : (p.barcode ? `bc-${p.barcode}` : `no-${p.no}`);
|
||||
// Remove from newPatientMap to prevent duplicate
|
||||
if (newPatientMap.has(key)) {
|
||||
newPatientMap.delete(key);
|
||||
}
|
||||
return p; // Keep the currently processing patient as-is
|
||||
}
|
||||
|
||||
const key = p.visitId ? `vid-${p.visitId}` : (p.barcode ? `bc-${p.barcode}` : `no-${p.no}`);
|
||||
const apiP = newPatientMap.get(key);
|
||||
|
||||
@@ -214,7 +232,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
// Add remaining new patients
|
||||
allPatients.value.push(...newPatientMap.values());
|
||||
|
||||
console.log(`✅ [queueStore] Successfully fetched ${mappedClinicPatients.length} patients for clinic ${kodeKlinik}`);
|
||||
// console.log(`✅ [queueStore] Successfully fetched ${mappedClinicPatients.length} patients for clinic ${kodeKlinik}`);
|
||||
return { success: true, message: `${mappedClinicPatients.length} pasien dimuat` };
|
||||
|
||||
} catch (error) {
|
||||
@@ -267,58 +285,70 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
isWsConnected.value = false;
|
||||
},
|
||||
onMessage: (data) => {
|
||||
console.log('📨 [queueStore] Global WS Message received:', data);
|
||||
|
||||
// TRIGGER STRATEGIC REFRESHES
|
||||
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);
|
||||
} else if (targetKlinikId) {
|
||||
refreshedSomething = true;
|
||||
}
|
||||
|
||||
if (targetKlinikId) {
|
||||
// 2. If message specifies a klinik, refresh those clinics
|
||||
// Since the WS might only send numeric ID, we check active interests
|
||||
// that might correspond to this ID or just refresh all active clinics
|
||||
console.log(`🎯 [queueStore] WS targeting Clinic ${targetKlinikId}: Refreshing active clinic interests...`);
|
||||
const interestingClinics = Object.keys(activeClinicInterest.value);
|
||||
interestingClinics.forEach(kodeKlinik => {
|
||||
fetchPatientsForClinic(kodeKlinik);
|
||||
});
|
||||
} else if (globalInterestCount.value > 0) {
|
||||
// 3. If it's a generic message and we have global interest (Check-in page open)
|
||||
// Trigger bulk staggered refresh
|
||||
console.log(`📡 [queueStore] WS generic message: Global Interest active, triggering staggered bulk refresh...`);
|
||||
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();
|
||||
} else {
|
||||
// 4. Otherwise, only refresh lokets/clinics that currently have active interest
|
||||
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 generic message: Refreshing ${interestingLokets.length} active lokets:`, interestingLokets);
|
||||
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 generic message: Refreshing ${interestingClinics.length} active clinics:`, interestingClinics);
|
||||
console.log(`🌐 [queueStore] WS trigger: Refreshing ${interestingClinics.length} active clinics:`, interestingClinics);
|
||||
interestingClinics.forEach(kodeKlinik => {
|
||||
fetchPatientsForClinic(kodeKlinik);
|
||||
});
|
||||
}
|
||||
|
||||
if (interestingLokets.length === 0 && interestingClinics.length === 0) {
|
||||
console.log(`🔕 [queueStore] WS generic message: No active interest, skipping refresh.`);
|
||||
refreshedSomething = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Base data sync
|
||||
clinicStore.fetchRegulerClinics();
|
||||
ensureInitialData();
|
||||
if (!refreshedSomething) {
|
||||
console.log(`🔕 [queueStore] WS trigger received but no active interest matched. Skipping.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -379,12 +409,16 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
4: 'anjungan',
|
||||
5: 'di-loket',
|
||||
6: 'di-loket',
|
||||
28: 'pending', // PE PENDAFTARAN
|
||||
29: 'terlambat', // TR PENDAFTARAN
|
||||
"1": 'menunggu',
|
||||
"2": 'menunggu',
|
||||
"3": 'anjungan',
|
||||
"4": 'anjungan',
|
||||
"5": 'di-loket',
|
||||
"6": 'di-loket'
|
||||
"6": 'di-loket',
|
||||
"28": 'pending', // PE PENDAFTARAN
|
||||
"29": 'terlambat' // TR PENDAFTARAN
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -441,8 +475,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
|
||||
if (apiPatient.posisi && Array.isArray(apiPatient.posisi) && apiPatient.posisi.length > 0) {
|
||||
// Find the "best" status among all positions
|
||||
// Priority: di-loket > anjungan > menunggu
|
||||
const statusPriority = { 'di-loket': 3, 'anjungan': 2, 'menunggu': 1 };
|
||||
// Priority: di-loket > anjungan > menunggu > pending > terlambat
|
||||
const statusPriority = { 'di-loket': 5, 'anjungan': 4, 'menunggu': 3, 'pending': 2, 'terlambat': 1 };
|
||||
let bestPriority = 0;
|
||||
|
||||
apiPatient.posisi.forEach(pos => {
|
||||
@@ -573,8 +607,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
if (!existing) {
|
||||
deduplicatedMap.set(id, p);
|
||||
} else {
|
||||
// Priority: di-loket > anjungan > menunggu
|
||||
const statusPriority = { 'di-loket': 3, 'anjungan': 2, 'menunggu': 1 };
|
||||
// Priority: di-loket > anjungan > menunggu > pending > terlambat
|
||||
const statusPriority = { 'di-loket': 5, 'anjungan': 4, 'menunggu': 3, 'pending': 2, 'terlambat': 1 };
|
||||
const pPrio = statusPriority[p.status] || 0;
|
||||
const ePrio = statusPriority[existing.status] || 0;
|
||||
|
||||
@@ -612,7 +646,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
if (newPatientMap.has(key)) {
|
||||
// If existing patient has more advanced status (anjungan/di-loket), preserve it!
|
||||
// This handles cases where we updated status locally ('onsite' or 'api') but API is lagging
|
||||
const statusPriority = { 'di-loket': 3, 'anjungan': 2, 'menunggu': 1 };
|
||||
const statusPriority = { 'di-loket': 5, 'anjungan': 4, 'menunggu': 3, 'pending': 2, 'terlambat': 1 };
|
||||
const existingPrio = statusPriority[p.status] || 0;
|
||||
const newPatient = newPatientMap.get(key);
|
||||
const newPrio = statusPriority[newPatient.status] || 0;
|
||||
@@ -651,6 +685,20 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
return true; // Keep the advanced patient
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Protect currently processing patients from being removed
|
||||
// Check if this patient is currently being processed in any admin interface
|
||||
const isCurrentlyProcessing = Object.values(currentProcessingPatient.value || {}).some(
|
||||
processingPatient => processingPatient && processingPatient.no === p.no
|
||||
);
|
||||
if (isCurrentlyProcessing) {
|
||||
console.log(`🛡️ [queueStore] Protecting currently processing patient ${p.noAntrian} from WebSocket refresh`);
|
||||
// Remove from newPatientMap to prevent duplicate
|
||||
if (newPatientMap.has(key)) {
|
||||
newPatientMap.delete(key);
|
||||
}
|
||||
return true; // Keep the currently processing patient
|
||||
}
|
||||
|
||||
// If it's still in loket stage but marked as processed, and it's in the API batch,
|
||||
// we might be experiencing API lag. Keep it as processed.
|
||||
if (p.status === 'processed' && newPatientMap.has(key)) {
|
||||
@@ -658,6 +706,14 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
newPatientMap.delete(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Protect terlambat and pending status from being overwritten
|
||||
// API might have lag in updating these statuses, so keep local version
|
||||
if ((p.status === 'terlambat' || p.status === 'pending') && newPatientMap.has(key)) {
|
||||
console.log(`🛡️ [queueStore] Protecting ${p.status} patient ${p.noAntrian} from API overwrite`);
|
||||
newPatientMap.delete(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove if it's being replaced by new API batch
|
||||
if (newPatientMap.has(key)) return false;
|
||||
@@ -674,11 +730,75 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
return true;
|
||||
});
|
||||
|
||||
// 5. Add merged patients
|
||||
allPatients.value.push(...patientsWithLoketId);
|
||||
|
||||
// 5. Add merged patients (with deduplication check)
|
||||
// Don't add if patient already exists in allPatients (prevents duplication with LocalStorage fallback)
|
||||
patientsWithLoketId.forEach(newPatient => {
|
||||
const existingIndex = allPatients.value.findIndex(p =>
|
||||
(p.barcode && newPatient.barcode && p.barcode === newPatient.barcode) ||
|
||||
(p.idtiket && newPatient.idtiket && p.idtiket === newPatient.idtiket)
|
||||
);
|
||||
|
||||
if (existingIndex === -1) {
|
||||
// Patient doesn't exist, add it
|
||||
allPatients.value.push(newPatient);
|
||||
} else {
|
||||
// Patient exists - only update if new status has higher priority
|
||||
const existing = allPatients.value[existingIndex];
|
||||
const statusPriority = { 'di-loket': 5, 'anjungan': 4, 'menunggu': 3, 'pending': 2, 'terlambat': 1 };
|
||||
const existingPrio = statusPriority[existing.status] || 0;
|
||||
const newPrio = statusPriority[newPatient.status] || 0;
|
||||
|
||||
// Only update if new status has higher priority (e.g., di-loket > terlambat)
|
||||
// This prevents API data from overwriting LocalStorage terlambat/pending status
|
||||
if (newPrio > existingPrio) {
|
||||
console.log(`🔄 [queueStore] Updating patient ${newPatient.barcode} from ${existing.status} to ${newPatient.status}`);
|
||||
allPatients.value[existingIndex] = {
|
||||
...newPatient,
|
||||
// Preserve some local state
|
||||
calledByAdmin: existing.calledByAdmin,
|
||||
lastCalledAt: existing.lastCalledAt
|
||||
};
|
||||
} else {
|
||||
console.log(`🛡️ [queueStore] Protecting patient ${existing.barcode} with status ${existing.status} from API overwrite (${newPatient.status})`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`✅ [queueStore] Successfully fetched ${finalPatients.length} unique patients for loket ${loketId} (Original: ${mappedPatients.length})`);
|
||||
console.log(`📊 [queueStore] Total patients in allPatients: ${allPatients.value.length}`);
|
||||
// 6. RESTORE terlambat/pending status from LocalStorage (hybrid fallback)
|
||||
// This ensures status persists even if API doesn't save it
|
||||
allPatients.value.forEach((patient, index) => {
|
||||
if (patient.barcode) {
|
||||
const storageKey = `patient-status-${patient.barcode}`;
|
||||
const savedData = localStorage.getItem(storageKey);
|
||||
|
||||
if (savedData) {
|
||||
try {
|
||||
const { status, timestamp } = JSON.parse(savedData);
|
||||
|
||||
// Only apply if status is terlambat or pending and not too old (24 hours)
|
||||
const isRecent = Date.now() - timestamp < 24 * 60 * 60 * 1000;
|
||||
|
||||
if ((status === 'terlambat' || status === 'pending') && isRecent) {
|
||||
console.log(`🔄 [LocalStorage] Restoring ${status} status for patient ${patient.barcode}`);
|
||||
allPatients.value[index] = {
|
||||
...patient,
|
||||
status: status
|
||||
};
|
||||
} else if (!isRecent) {
|
||||
// Clean up old data
|
||||
localStorage.removeItem(storageKey);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing LocalStorage data:', e);
|
||||
localStorage.removeItem(storageKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// console.log(`✅ [queueStore] Successfully fetched ${finalPatients.length} unique patients for loket ${loketId} (Original: ${mappedPatients.length})`);
|
||||
// console.log(`📊 [queueStore] Total patients in allPatients: ${allPatients.value.length}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -1214,7 +1334,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🔄 Syncing state to version: ${incomingVersion}`);
|
||||
// console.log(`🔄 Syncing state to version: ${incomingVersion}`);
|
||||
|
||||
if (newState) {
|
||||
isSyncing = true; // Block local watch from updating timestamp
|
||||
@@ -1583,7 +1703,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const processPatient = (patient, action, adminType = 'loket', specificId = null) => {
|
||||
const processPatient = async (patient, action, adminType = 'loket', specificId = null) => {
|
||||
const storageKey = specificId ? `${adminType}-${specificId}` : adminType;
|
||||
const patientCode = patient.noAntrian.split(" |")[0];
|
||||
let message = "";
|
||||
@@ -1659,29 +1779,189 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
}
|
||||
break;
|
||||
|
||||
case "terlambat":
|
||||
allPatients.value[patientIndex] = {
|
||||
...allPatients.value[patientIndex],
|
||||
status: "terlambat",
|
||||
calledByAdmin: false // Reset flag
|
||||
};
|
||||
case "terlambat": {
|
||||
// DON'T update local state immediately - wait for API success then refresh from DB
|
||||
const loketId = patient.loketId;
|
||||
|
||||
if (currentProcessingPatient.value[storageKey]?.no === patient.no) {
|
||||
currentProcessingPatient.value[storageKey] = null;
|
||||
}
|
||||
message = `Pasien ${patientCode} ditandai terlambat`;
|
||||
|
||||
message = `Menandai pasien ${patientCode} sebagai terlambat...`;
|
||||
|
||||
const payload = {
|
||||
barcode: patient.barcode || "",
|
||||
statuspasien: "29",
|
||||
statuspasien2: "29",
|
||||
idklinikstatus: "2",
|
||||
idklinikstatus2: "2"
|
||||
};
|
||||
|
||||
console.log('📤 [TERLAMBAT] Sending API request:', payload);
|
||||
|
||||
// POST to external API and WAIT for response
|
||||
try {
|
||||
const response = await fetch('http://10.10.150.131:8089/api/v1/tiket/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
console.log('📥 [TERLAMBAT] API response status:', response.status);
|
||||
|
||||
// Read response body
|
||||
const responseData = await response.json().catch(() => ({}));
|
||||
console.log('📥 [TERLAMBAT] API response data:', responseData);
|
||||
|
||||
if (response.ok) {
|
||||
console.log(`✅ [TERLAMBAT] API accepted request`);
|
||||
|
||||
// Add delay to ensure database is updated
|
||||
console.log('⏳ [TERLAMBAT] Waiting 500ms for database update...');
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// REFRESH data from database to get updated status
|
||||
console.log(`🔄 [TERLAMBAT] Refreshing data from database...`);
|
||||
await fetchPatientsForLoket(loketId);
|
||||
|
||||
// Verify status after refresh
|
||||
const updatedPatient = allPatients.value.find(p => p.barcode === patient.barcode);
|
||||
console.log('🔍 [TERLAMBAT] Patient after refresh:', updatedPatient);
|
||||
|
||||
if (updatedPatient?.status === 'terlambat') {
|
||||
// API successfully saved status
|
||||
console.log('✅ [TERLAMBAT] Status persisted in database!');
|
||||
message = `Pasien ${patientCode} ditandai terlambat`;
|
||||
} else {
|
||||
// API didn't save status - use LocalStorage fallback
|
||||
console.warn('⚠️ [TERLAMBAT] API did not persist status. Using LocalStorage fallback.');
|
||||
|
||||
// Save to LocalStorage
|
||||
const storageKey = `patient-status-${patient.barcode}`;
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
status: 'terlambat',
|
||||
timestamp: Date.now(),
|
||||
barcode: patient.barcode
|
||||
}));
|
||||
|
||||
// Manually update local state since API didn't save
|
||||
const patientIndex = allPatients.value.findIndex(p => p.barcode === patient.barcode);
|
||||
if (patientIndex !== -1) {
|
||||
allPatients.value[patientIndex] = {
|
||||
...allPatients.value[patientIndex],
|
||||
status: 'terlambat',
|
||||
calledByAdmin: false
|
||||
};
|
||||
syncApiPatientStatus(allPatients.value[patientIndex], 'terlambat');
|
||||
}
|
||||
|
||||
message = `Pasien ${patientCode} ditandai terlambat (local)`;
|
||||
}
|
||||
} else {
|
||||
console.error(`❌ [TERLAMBAT] API rejected request:`, responseData);
|
||||
message = `Gagal: ${responseData.message || 'API error'}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ [TERLAMBAT] Error:`, error);
|
||||
message = `Error: ${error.message}`;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "pending":
|
||||
allPatients.value[patientIndex] = {
|
||||
...allPatients.value[patientIndex],
|
||||
status: "pending",
|
||||
calledByAdmin: false // Reset flag
|
||||
};
|
||||
case "pending": {
|
||||
// DON'T update local state immediately - wait for API success then refresh from DB
|
||||
const loketId = patient.loketId;
|
||||
|
||||
if (currentProcessingPatient.value[storageKey]?.no === patient.no) {
|
||||
currentProcessingPatient.value[storageKey] = null;
|
||||
}
|
||||
message = `Pasien ${patientCode} di-pending`;
|
||||
|
||||
message = `Menandai pasien ${patientCode} sebagai pending...`;
|
||||
|
||||
const payload = {
|
||||
barcode: patient.barcode || "",
|
||||
statuspasien: "28",
|
||||
statuspasien2: "28",
|
||||
idklinikstatus: "2",
|
||||
idklinikstatus2: "2"
|
||||
};
|
||||
|
||||
console.log('📤 [PENDING] Sending API request:', payload);
|
||||
|
||||
// POST to external API and WAIT for response
|
||||
try {
|
||||
const response = await fetch('http://10.10.150.131:8089/api/v1/tiket/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
console.log('📥 [PENDING] API response status:', response.status);
|
||||
|
||||
// Read response body
|
||||
const responseData = await response.json().catch(() => ({}));
|
||||
console.log('📥 [PENDING] API response data:', responseData);
|
||||
|
||||
if (response.ok) {
|
||||
console.log(`✅ [PENDING] API accepted request`);
|
||||
|
||||
// Add delay to ensure database is updated
|
||||
console.log('⏳ [PENDING] Waiting 500ms for database update...');
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// REFRESH data from database to get updated status
|
||||
console.log(`🔄 [PENDING] Refreshing data from database...`);
|
||||
await fetchPatientsForLoket(loketId);
|
||||
|
||||
// Verify status after refresh
|
||||
const updatedPatient = allPatients.value.find(p => p.barcode === patient.barcode);
|
||||
console.log('🔍 [PENDING] Patient after refresh:', updatedPatient);
|
||||
|
||||
if (updatedPatient?.status === 'pending') {
|
||||
// API successfully saved status
|
||||
console.log('✅ [PENDING] Status persisted in database!');
|
||||
message = `Pasien ${patientCode} di-pending`;
|
||||
} else {
|
||||
// API didn't save status - use LocalStorage fallback
|
||||
console.warn('⚠️ [PENDING] API did not persist status. Using LocalStorage fallback.');
|
||||
|
||||
// Save to LocalStorage
|
||||
const storageKey = `patient-status-${patient.barcode}`;
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
status: 'pending',
|
||||
timestamp: Date.now(),
|
||||
barcode: patient.barcode
|
||||
}));
|
||||
|
||||
// Manually update local state since API didn't save
|
||||
const patientIndex = allPatients.value.findIndex(p => p.barcode === patient.barcode);
|
||||
if (patientIndex !== -1) {
|
||||
allPatients.value[patientIndex] = {
|
||||
...allPatients.value[patientIndex],
|
||||
status: 'pending',
|
||||
calledByAdmin: false
|
||||
};
|
||||
syncApiPatientStatus(allPatients.value[patientIndex], 'pending');
|
||||
}
|
||||
|
||||
message = `Pasien ${patientCode} di-pending (local)`;
|
||||
}
|
||||
} else {
|
||||
console.error(`❌ [PENDING] API rejected request:`, responseData);
|
||||
message = `Gagal: ${responseData.message || 'API error'}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ [PENDING] Error:`, error);
|
||||
message = `Error: ${error.message}`;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "aktifkan": {
|
||||
const currentStatus = allPatients.value[patientIndex].status;
|
||||
@@ -1691,7 +1971,35 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
...allPatients.value[patientIndex],
|
||||
status: "di-loket"
|
||||
};
|
||||
syncApiPatientStatus(allPatients.value[patientIndex], "di-loket");
|
||||
message = `Pasien ${patientCode} diaktifkan kembali dan masuk ke tabel Di Loket`;
|
||||
|
||||
// POST to external API to revert status to di-loket
|
||||
try {
|
||||
fetch('http://10.10.150.131:8089/api/v1/tiket/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
barcode: patient.barcode || "",
|
||||
statuspasien: "2", // Status code for "di-loket"
|
||||
statuspasien2: "2",
|
||||
idklinikstatus: "1",
|
||||
idklinikstatus2: "1"
|
||||
})
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
console.log(`✅ [queueStore] Successfully activated patient ${patient.barcode}`);
|
||||
} else {
|
||||
console.error(`⚠️ [queueStore] Failed to activate patient ${patient.barcode}:`, response.status);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(`❌ [queueStore] Error activating patient ${patient.barcode}:`, error);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`❌ [queueStore] Error initiating activation for patient ${patient.barcode}:`, error);
|
||||
}
|
||||
} else {
|
||||
message = `Pasien ${patientCode} tidak dapat diaktifkan (status: ${currentStatus})`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user