push update

This commit is contained in:
Fanrouver
2026-02-12 08:06:20 +07:00
parent 23164bcf2d
commit 7fd99343bd
9 changed files with 498 additions and 125 deletions
+71 -23
View File
@@ -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
View File
@@ -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);
+1 -4
View File
@@ -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
});
});
+11 -11
View File
@@ -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);
})
})
+16 -5
View File
@@ -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(() => {
-4
View File
@@ -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);
});
-4
View File
@@ -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
View File
@@ -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})`;
}