push error missing function
This commit is contained in:
No files matched your search
+234
-23
@@ -16,12 +16,20 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
// ============================================
|
||||
|
||||
// State untuk API patient data per loket
|
||||
const allPatients = ref([]);
|
||||
const apiPatientsPerLoket = ref({});
|
||||
const isLoadingPatients = ref(false);
|
||||
const apiPatientsError = ref(null);
|
||||
const quotaUsed = ref(5);
|
||||
const currentProcessingPatient = ref({});
|
||||
|
||||
const lastUpdated = ref(Date.now());
|
||||
const lastFetchTime = ref({});
|
||||
const lastGlobalFetchTime = ref(0); // Cooldown for bulk refreshes
|
||||
|
||||
// Scoped Refresh Logic: track which lokets are currently being viewed
|
||||
const activeLoketInterest = ref({}); // { [loketId]: count }
|
||||
const activeClinicInterest = ref({}); // { [kodeKlinik]: count }
|
||||
const globalInterestCount = ref(0); // Tracks pages that need ALL loket data (e.g. CheckInPasien)
|
||||
|
||||
const registerInterest = (loketId) => {
|
||||
@@ -40,7 +48,26 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
delete activeLoketInterest.value[id];
|
||||
}
|
||||
}
|
||||
console.log(`🔌 [queueStore] Unregistered interest in Loket ${id}. Active:`, activeLoketInterest.value);
|
||||
console.log(`🔌 [queueStore] Unregistered interest in Loket ${id}. Active lokets:`, activeLoketInterest.value);
|
||||
};
|
||||
|
||||
const registerClinicInterest = (kodeKlinik) => {
|
||||
if (!kodeKlinik) return;
|
||||
const code = String(kodeKlinik);
|
||||
activeClinicInterest.value[code] = (activeClinicInterest.value[code] || 0) + 1;
|
||||
console.log(`🔌 [queueStore] Registered interest in Clinic ${code}. Active clinics:`, activeClinicInterest.value);
|
||||
};
|
||||
|
||||
const unregisterClinicInterest = (kodeKlinik) => {
|
||||
if (!kodeKlinik) return;
|
||||
const code = String(kodeKlinik);
|
||||
if (activeClinicInterest.value[code]) {
|
||||
activeClinicInterest.value[code] = Math.max(0, activeClinicInterest.value[code] - 1);
|
||||
if (activeClinicInterest.value[code] === 0) {
|
||||
delete activeClinicInterest.value[code];
|
||||
}
|
||||
}
|
||||
console.log(`🔌 [queueStore] Unregistered interest in Clinic ${code}. Active clinics:`, activeClinicInterest.value);
|
||||
};
|
||||
|
||||
const registerGlobalInterest = () => {
|
||||
@@ -53,13 +80,151 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
console.log(`🌐 [queueStore] Global interest unregistered. Total: ${globalInterestCount.value}`);
|
||||
};
|
||||
|
||||
// Throttle mechanism: track last fetch time per loket
|
||||
const lastFetchTime = ref({});
|
||||
const lastGlobalFetchTime = ref(0); // Cooldown for bulk refreshes
|
||||
|
||||
// Synchronization Guard: track last update time to break loops across tabs
|
||||
const lastUpdated = ref(Date.now());
|
||||
// synchronization guard (moved lower)
|
||||
const fetchPatientsForClinic = async (kodeKlinik) => {
|
||||
if (!kodeKlinik) return { success: false, message: 'Kode Klinik diperlukan' };
|
||||
|
||||
isLoadingPatients.value = true;
|
||||
apiPatientsError.value = null;
|
||||
|
||||
// THROTTLE: Check if we recently fetched (within last 5 seconds)
|
||||
const now = Date.now();
|
||||
const lastFetch = lastFetchTime.value[`clinic-${kodeKlinik}`] || 0;
|
||||
const timeSinceLastFetch = now - lastFetch;
|
||||
|
||||
if (timeSinceLastFetch < 5000) {
|
||||
console.log(`⏭️ [queueStore] Skipping fetch for clinic ${kodeKlinik} (last fetched ${Math.round(timeSinceLastFetch/1000)}s ago)`);
|
||||
isLoadingPatients.value = false;
|
||||
return { success: true, message: 'Using cache' };
|
||||
}
|
||||
|
||||
lastFetchTime.value[`clinic-${kodeKlinik}`] = now;
|
||||
|
||||
try {
|
||||
// Find clinic ID from clinicStore
|
||||
const clinic = clinicStore.clinics.find(c => c.kode === kodeKlinik);
|
||||
if (!clinic || !clinic.id) {
|
||||
throw new Error(`Klinik ID tidak ditemukan untuk kode: ${kodeKlinik}`);
|
||||
}
|
||||
|
||||
const url = `/visit-api/visit?klinik_id=${clinic.id}&limit=500`;
|
||||
console.log(`🔄 [queueStore] Fetching patients for clinic ${kodeKlinik} (ID: ${clinic.id})...`);
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
|
||||
const rawResponse = await response.json();
|
||||
const data = rawResponse?.data || [];
|
||||
|
||||
const mappedClinicPatients = [];
|
||||
data.forEach((visit, index) => {
|
||||
const healthcareServices = visit.healthcare_services || [];
|
||||
|
||||
// Filter for KLINIK healthcare_type_name with active=true
|
||||
const services = healthcareServices.filter(s =>
|
||||
s.healthcare_type_name === 'KLINIK' && s.active === true
|
||||
);
|
||||
|
||||
if (services.length > 0) {
|
||||
services.forEach(service => {
|
||||
const roomId = service.fk_ms_sub_healthcare_service_id;
|
||||
const visitStatuses = visit.visit_statuses || [];
|
||||
const latestStatus = visitStatuses[visitStatuses.length - 1];
|
||||
|
||||
let patientStatus = 'di-loket';
|
||||
if (latestStatus?.desc) {
|
||||
const desc = latestStatus.desc.toLowerCase();
|
||||
if (desc.includes('pemeriksaan') || desc.includes('sedang diproses')) {
|
||||
patientStatus = 'pemeriksaan';
|
||||
}
|
||||
}
|
||||
|
||||
const p = {
|
||||
no: visit.id || (20000 + index),
|
||||
barcode: visit.visit_code || service.ticket || '',
|
||||
noAntrian: service.ticket || visit.visit_code || '',
|
||||
jamPanggil: service.check_in_datetime || visit.registration_datetime || '',
|
||||
klinik: service.healthcare_service_name || clinic.name,
|
||||
kodeKlinik: kodeKlinik,
|
||||
klinikId: clinic.id,
|
||||
ruang: service.sub_healthcare_service_name || '',
|
||||
nomorRuang: roomId ? String(roomId) : String(clinic.id),
|
||||
pembayaran: service.payment_type_name || visit.payment_type_name || '',
|
||||
status: patientStatus,
|
||||
processStage: 'klinik-ruang',
|
||||
createdAt: visit.registration_datetime || new Date().toISOString(),
|
||||
visitType: visit.visit_type_name || 'ONSITE',
|
||||
noRM: visit.norm || '',
|
||||
fastTrack: 'TIDAK',
|
||||
registrationType: 'api',
|
||||
visitId: visit.visit_id || visit.id,
|
||||
visitCode: visit.visit_code
|
||||
};
|
||||
|
||||
if (isTodayPatient(p)) mappedClinicPatients.push(p);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// MERGE LOGIC
|
||||
const newPatientMap = new Map();
|
||||
mappedClinicPatients.forEach(p => {
|
||||
const key = p.visitId ? `vid-${p.visitId}` : (p.barcode ? `bc-${p.barcode}` : `no-${p.no}`);
|
||||
newPatientMap.set(key, p);
|
||||
});
|
||||
|
||||
// Update existing allPatients while preserving local UI state
|
||||
allPatients.value = allPatients.value.map(p => {
|
||||
if (p.processStage !== 'klinik-ruang' || p.kodeKlinik !== kodeKlinik) return p;
|
||||
|
||||
const key = p.visitId ? `vid-${p.visitId}` : (p.barcode ? `bc-${p.barcode}` : `no-${p.no}`);
|
||||
const apiP = newPatientMap.get(key);
|
||||
|
||||
if (apiP) {
|
||||
// If we have local updates, preserve them
|
||||
const hasLocalUpdates = (
|
||||
p.status !== 'di-loket' ||
|
||||
p.calledPemeriksaanAwal ||
|
||||
p.calledTindakan ||
|
||||
p.lastCalledAt
|
||||
);
|
||||
|
||||
if (hasLocalUpdates) {
|
||||
newPatientMap.delete(key); // Mark as handled
|
||||
return {
|
||||
...apiP,
|
||||
status: p.status,
|
||||
calledPemeriksaanAwal: p.calledPemeriksaanAwal,
|
||||
calledTindakan: p.calledTindakan,
|
||||
tipeLayanan: p.tipeLayanan,
|
||||
lastCalledAt: p.lastCalledAt,
|
||||
lastCalledTipeLayanan: p.lastCalledTipeLayanan
|
||||
};
|
||||
} else {
|
||||
newPatientMap.delete(key); // Handled
|
||||
return apiP;
|
||||
}
|
||||
}
|
||||
|
||||
// If it was an API patient that disappeared from the list, it's either stale or from another room
|
||||
if (p.registrationType === 'api') return null;
|
||||
|
||||
return p;
|
||||
}).filter(Boolean);
|
||||
|
||||
// Add remaining new patients
|
||||
allPatients.value.push(...newPatientMap.values());
|
||||
|
||||
console.log(`✅ [queueStore] Successfully fetched ${mappedClinicPatients.length} patients for clinic ${kodeKlinik}`);
|
||||
return { success: true, message: `${mappedClinicPatients.length} pasien dimuat` };
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ [queueStore] Error fetching clinic patients (${kodeKlinik}):`, error);
|
||||
apiPatientsError.value = error.message;
|
||||
return { success: false, message: error.message };
|
||||
} finally {
|
||||
isLoadingPatients.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// WEBSOCKET INTEGRATION (CENTRALIZED)
|
||||
@@ -107,26 +272,47 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
// TRIGGER STRATEGIC REFRESHES
|
||||
const messageData = data?.data || data;
|
||||
const targetLoketId = messageData?.loketId || messageData?.idloket;
|
||||
const targetKlinikId = messageData?.klinikId || messageData?.idklinik;
|
||||
|
||||
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) {
|
||||
// 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) {
|
||||
// 2. If it's a generic message and we have global interest (Check-in page open)
|
||||
// 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...`);
|
||||
fetchAllPatients();
|
||||
} else {
|
||||
// 3. Otherwise, only refresh lokets that currently have active interest (Admin tabs open)
|
||||
// 4. Otherwise, only refresh lokets/clinics that currently have active interest
|
||||
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);
|
||||
interestingLokets.forEach(loketId => {
|
||||
fetchPatientsForLoket(loketId);
|
||||
});
|
||||
} else {
|
||||
console.log(`🔕 [queueStore] WS generic message: No active interest, skipping bulk refresh.`);
|
||||
}
|
||||
|
||||
if (interestingClinics.length > 0) {
|
||||
console.log(`🌐 [queueStore] WS generic message: 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.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,17 +640,35 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
allPatients.value = allPatients.value.filter(p => {
|
||||
const key = p.idtiket ? `id-${p.idtiket}` : `bc-${p.barcode}`;
|
||||
|
||||
// Remove if it's being replaced by new API batch (matches by barcode or idtiket)
|
||||
// CRITICAL FIX: If patient has already moved to a later stage (e.g. klinik-ruang),
|
||||
// do NOT remove it or overwrite it with a 'loket' stage API patient.
|
||||
if (p.processStage && p.processStage !== 'loket') {
|
||||
// Check if this advanced patient matches the incoming batch
|
||||
if (newPatientMap.has(key)) {
|
||||
// Signal to skip adding the 'loket' version from API
|
||||
newPatientMap.delete(key);
|
||||
}
|
||||
return true; // Keep the advanced 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)) {
|
||||
// Skip replacing it with 'menunggu'/'anjungan' status
|
||||
newPatientMap.delete(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove if it's being replaced by new API batch
|
||||
if (newPatientMap.has(key)) return false;
|
||||
|
||||
// Also check if barcode matches any new API patient (for cases where local has no idtiket)
|
||||
// This prevents duplicates like barcode "2602050017" appearing as both BPJS (local) and JKN (API)
|
||||
// Also check if barcode matches any new API patient
|
||||
if (p.barcode) {
|
||||
const barcodeMatches = patientsWithLoketId.some(newP => newP.barcode === p.barcode);
|
||||
if (barcodeMatches) return false;
|
||||
}
|
||||
|
||||
// Remove if it's a stale 'api' patient for this loket (that wasn't in the new batch)
|
||||
// Remove if it's a stale 'api' patient for this loket
|
||||
if (p.registrationType === 'api' && String(p.loketId) === String(loketId)) return false;
|
||||
|
||||
return true;
|
||||
@@ -499,6 +703,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Global fetcher for all patients across all available lokets
|
||||
*/
|
||||
@@ -879,11 +1084,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
};
|
||||
|
||||
// Initial state - START EMPTY to avoid clashing with hydration
|
||||
const allPatients = ref([]);
|
||||
const quotaUsed = ref(5);
|
||||
// State - currentProcessingPatient is now an object mapping 'stage-id' to patient
|
||||
// For example: { 'loket-1': patient, 'loket-2': patient, 'klinik-3': patient }
|
||||
const currentProcessingPatient = ref({});
|
||||
// Placeholder lines for moved state references (already handled at top)
|
||||
|
||||
// Daftar klinik untuk dropdown diambil 1 pintu dari clinicStore (Computed for stability)
|
||||
const kliniks = computed(() => clinicStore.clinics || []);
|
||||
@@ -1721,12 +1922,15 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
nomorScreen: ruang.nomorScreen,
|
||||
fastTrack: patient ? (patient.fastTrack || "TIDAK") : "TIDAK",
|
||||
pembayaran: patient ? patient.pembayaran : "UMUM",
|
||||
noRM: patient ? patient.noRM : null,
|
||||
noRM: patient ? (patient.noRM || patient.norm) : null,
|
||||
status: "pemeriksaan", // Patients from loket to klinik ruang start as "pemeriksaan"
|
||||
processStage: "klinik-ruang", // Set ke klinik-ruang langsung
|
||||
createdAt: timestamp.toISOString(),
|
||||
referencePatient: patient ? patient.noAntrian : null,
|
||||
sourcePatientNo: patient ? patient.no : null,
|
||||
visitId: patient ? (patient.visitId || patient.id) : null,
|
||||
visitCode: patient ? (patient.visitCode || patient.visit_code) : null,
|
||||
registrationType: patient ? (patient.registrationType || 'onsite') : 'onsite',
|
||||
// Tracking panggilan
|
||||
calledPemeriksaanAwal: false,
|
||||
calledTindakan: false,
|
||||
@@ -2459,7 +2663,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
|
||||
// PRIORITAS: Hanya cari dengan EXACT barcode match (case-insensitive, whitespace-insensitive)
|
||||
// Format barcode: YYMMDD + 5 digit (contoh: 26011500001)
|
||||
// Jangan gunakan fallback ke noAntrian atau no karena bisa menyebabkan false positive
|
||||
// Ini adalah satu-satunya cara yang aman untuk match pasien
|
||||
const patientIndex = allPatients.value.findIndex(p => {
|
||||
// Normalize barcode untuk comparison
|
||||
const patientBarcode = String(p.barcode || '').trim();
|
||||
@@ -2605,6 +2809,9 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
ensureInitialData,
|
||||
quotaUsed,
|
||||
currentProcessingPatient,
|
||||
lastUpdated,
|
||||
lastFetchTime,
|
||||
lastGlobalFetchTime,
|
||||
kliniks,
|
||||
penunjangs,
|
||||
|
||||
@@ -2645,6 +2852,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
|
||||
// API Patient Actions
|
||||
fetchPatientsForLoket,
|
||||
fetchPatientsForClinic,
|
||||
fetchAllPatients,
|
||||
getPatientsForLoket,
|
||||
mapStatusFromDeskripsi,
|
||||
@@ -2660,6 +2868,9 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
registerInterest,
|
||||
unregisterInterest,
|
||||
activeLoketInterest,
|
||||
registerClinicInterest,
|
||||
unregisterClinicInterest,
|
||||
activeClinicInterest,
|
||||
registerGlobalInterest,
|
||||
unregisterGlobalInterest,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user