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
+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})`;
}