update WS admin loket dan checkin
This commit is contained in:
+24
-12
@@ -135,24 +135,36 @@ export const useQueue = (adminType = "loket", specificId = null) => {
|
||||
|
||||
// Filtered lists
|
||||
const filteredKliniks = computed(() => {
|
||||
if (!klinikSearch.value) return queueStore.kliniks;
|
||||
return queueStore.kliniks.filter((k) =>
|
||||
k.name.toLowerCase().includes(klinikSearch.value.toLowerCase())
|
||||
);
|
||||
let result = queueStore.kliniks;
|
||||
if (klinikSearch.value) {
|
||||
result = result.filter((k) =>
|
||||
k.name.toLowerCase().includes(klinikSearch.value.toLowerCase())
|
||||
);
|
||||
}
|
||||
// Sort alphabetically by name
|
||||
return [...result].sort((a, b) => (a.name || "").localeCompare(b.name || ""));
|
||||
});
|
||||
|
||||
const filteredPenunjangs = computed(() => {
|
||||
if (!penunjangSearch.value) return queueStore.penunjangs;
|
||||
return queueStore.penunjangs.filter((p) =>
|
||||
p.name.toLowerCase().includes(penunjangSearch.value.toLowerCase())
|
||||
);
|
||||
let result = queueStore.penunjangs;
|
||||
if (penunjangSearch.value) {
|
||||
result = result.filter((p) =>
|
||||
p.name.toLowerCase().includes(penunjangSearch.value.toLowerCase())
|
||||
);
|
||||
}
|
||||
// Sort alphabetically by name
|
||||
return [...result].sort((a, b) => (a.name || "").localeCompare(b.name || ""));
|
||||
});
|
||||
|
||||
const filteredChangeKliniks = computed(() => {
|
||||
if (!changeKlinikSearch.value) return queueStore.kliniks;
|
||||
return queueStore.kliniks.filter((k) =>
|
||||
k.name.toLowerCase().includes(changeKlinikSearch.value.toLowerCase())
|
||||
);
|
||||
let result = queueStore.kliniks;
|
||||
if (changeKlinikSearch.value) {
|
||||
result = result.filter((k) =>
|
||||
k.name.toLowerCase().includes(changeKlinikSearch.value.toLowerCase())
|
||||
);
|
||||
}
|
||||
// Sort alphabetically by name
|
||||
return [...result].sort((a, b) => (a.name || "").localeCompare(b.name || ""));
|
||||
});
|
||||
|
||||
// Methods
|
||||
|
||||
@@ -177,10 +177,11 @@
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<v-expansion-panels class="expansion-panels">
|
||||
<v-expansion-panels v-model="activePanel" class="expansion-panels">
|
||||
<v-expansion-panel
|
||||
v-for="klinikRuang in filteredKlinikRuang"
|
||||
:key="klinikRuang.id"
|
||||
:value="klinikRuang.id"
|
||||
class="expansion-panel"
|
||||
>
|
||||
<v-expansion-panel-title class="expansion-title">
|
||||
@@ -383,8 +384,13 @@ onMounted(async () => {
|
||||
// Initialize and connect WebSocket (Centralized)
|
||||
queueStore.initWebSocket(anjunganClientId.value);
|
||||
|
||||
// Register interest in this specific loket for scoped WebSocket refreshes
|
||||
queueStore.registerInterest(loketId.value);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(pollingInterval);
|
||||
// Unregister interest when leaving the page
|
||||
queueStore.unregisterInterest(loketId.value);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -464,6 +470,7 @@ const selectedFastTrack = ref(null);
|
||||
// Dialog Klinik Ruang
|
||||
const showKlinikRuangDialog = ref(false);
|
||||
const klinikRuangSearch = ref("");
|
||||
const activePanel = ref(null); // Tracks the open Klinik Ruang panel
|
||||
|
||||
// Watch dialog state to reset search when closed
|
||||
watch(showKlinikRuangDialog, (newValue) => {
|
||||
@@ -483,9 +490,22 @@ const klinikRuangList = computed(() => {
|
||||
const targetLayanan = isLoketEksekutif ? "Eksekutif" : "Reguler";
|
||||
|
||||
// Filter rooms by service type to match Loket's service level
|
||||
return (ruangStore.ruangData || []).filter(
|
||||
const baseList = (ruangStore.ruangData || []).filter(
|
||||
(k) => k.jenisLayanan === targetLayanan,
|
||||
);
|
||||
|
||||
// Sort clinics alphabetically by namaKlinik
|
||||
const sortedClinics = [...baseList].sort((a, b) =>
|
||||
(a.namaKlinik || "").localeCompare(b.namaKlinik || ""),
|
||||
);
|
||||
|
||||
// Sort each clinical room list alphabetically by namaRuang
|
||||
return sortedClinics.map((clinic) => ({
|
||||
...clinic,
|
||||
ruangList: [...(clinic.ruangList || [])].sort((a, b) =>
|
||||
(a.namaRuang || "").localeCompare(b.namaRuang || ""),
|
||||
),
|
||||
}));
|
||||
});
|
||||
|
||||
const filteredKlinikRuang = computed(() => {
|
||||
|
||||
@@ -1001,8 +1001,13 @@ onMounted(async () => {
|
||||
// Initialize and connect WebSocket (Centralized)
|
||||
queueStore.initWebSocket(anjunganClientId.value);
|
||||
|
||||
// Register interest in this specific loket for scoped WebSocket refreshes
|
||||
queueStore.registerInterest(loketId.value);
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(pollingInterval);
|
||||
// Unregister interest when leaving the page
|
||||
queueStore.unregisterInterest(loketId.value);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2729,6 +2729,9 @@ onMounted(async () => {
|
||||
|
||||
// Initialize WebSocket (Centralized)
|
||||
queueStore.initWebSocket(checkInClientId.value);
|
||||
|
||||
// Register global interest to receive staggered bulk refreshes on generic WS messages
|
||||
queueStore.registerGlobalInterest();
|
||||
|
||||
// Set interval to check every minute for reset time
|
||||
const resetCheckInterval = setInterval(() => {
|
||||
@@ -2738,6 +2741,8 @@ onMounted(async () => {
|
||||
onUnmounted(() => {
|
||||
clearInterval(pollingInterval);
|
||||
clearInterval(resetCheckInterval);
|
||||
// Unregister global interest
|
||||
queueStore.unregisterGlobalInterest();
|
||||
});
|
||||
} else {
|
||||
hasCamera.value = false;
|
||||
|
||||
+97
-17
@@ -20,8 +20,42 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const isLoadingPatients = ref(false);
|
||||
const apiPatientsError = ref(null);
|
||||
|
||||
// Scoped Refresh Logic: track which lokets are currently being viewed
|
||||
const activeLoketInterest = ref({}); // { [loketId]: count }
|
||||
const globalInterestCount = ref(0); // Tracks pages that need ALL loket data (e.g. CheckInPasien)
|
||||
|
||||
const registerInterest = (loketId) => {
|
||||
if (!loketId) return;
|
||||
const id = String(loketId);
|
||||
activeLoketInterest.value[id] = (activeLoketInterest.value[id] || 0) + 1;
|
||||
console.log(`🔌 [queueStore] Registered interest in Loket ${id}. Active:`, activeLoketInterest.value);
|
||||
};
|
||||
|
||||
const unregisterInterest = (loketId) => {
|
||||
if (!loketId) return;
|
||||
const id = String(loketId);
|
||||
if (activeLoketInterest.value[id]) {
|
||||
activeLoketInterest.value[id] = Math.max(0, activeLoketInterest.value[id] - 1);
|
||||
if (activeLoketInterest.value[id] === 0) {
|
||||
delete activeLoketInterest.value[id];
|
||||
}
|
||||
}
|
||||
console.log(`🔌 [queueStore] Unregistered interest in Loket ${id}. Active:`, activeLoketInterest.value);
|
||||
};
|
||||
|
||||
const registerGlobalInterest = () => {
|
||||
globalInterestCount.value++;
|
||||
console.log(`🌐 [queueStore] Global interest registered. Total: ${globalInterestCount.value}`);
|
||||
};
|
||||
|
||||
const unregisterGlobalInterest = () => {
|
||||
globalInterestCount.value = Math.max(0, globalInterestCount.value - 1);
|
||||
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());
|
||||
@@ -68,18 +102,36 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
isWsConnected.value = false;
|
||||
},
|
||||
onMessage: (data) => {
|
||||
console.log('📨 [queueStore] Global WS Message:', data);
|
||||
console.log('📨 [queueStore] Global WS Message received:', data);
|
||||
|
||||
// TRIGGER STRATEGIC REFRESHES
|
||||
// 1. Refetch patients for all active lokets in the store
|
||||
Object.keys(apiPatientsPerLoket.value).forEach(loketId => {
|
||||
fetchPatientsForLoket(loketId);
|
||||
});
|
||||
const messageData = data?.data || data;
|
||||
const targetLoketId = messageData?.loketId || messageData?.idloket;
|
||||
|
||||
// 2. Refetch clinics to update quotas/availability
|
||||
if (targetLoketId) {
|
||||
// 1. If message specifies a loket, refresh that one specifically
|
||||
console.log(`🎯 [queueStore] WS targeting Loket ${targetLoketId}: Refreshing...`);
|
||||
fetchPatientsForLoket(targetLoketId);
|
||||
} else if (globalInterestCount.value > 0) {
|
||||
// 2. 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)
|
||||
const interestingLokets = Object.keys(activeLoketInterest.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.`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Base data sync
|
||||
clinicStore.fetchRegulerClinics();
|
||||
|
||||
// 3. Ensure base data is synced
|
||||
ensureInitialData();
|
||||
}
|
||||
});
|
||||
@@ -450,17 +502,40 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
/**
|
||||
* Global fetcher for all patients across all available lokets
|
||||
*/
|
||||
const fetchAllPatients = async () => {
|
||||
console.log('🔄 [queueStore] Fetching all patients for all lokets...');
|
||||
/**
|
||||
* Global fetcher for all patients across all available lokets
|
||||
* Uses staggered fetching to prevent 429 Too Many Requests errors.
|
||||
*/
|
||||
const fetchAllPatients = async (force = false) => {
|
||||
// 1. Cooldown Check: Prevent global refresh spam (max once every 5 seconds)
|
||||
const now = Date.now();
|
||||
if (!force && now - lastGlobalFetchTime.value < 5000) {
|
||||
console.log(`⏭️ [queueStore] fetchAllPatients: Global refresh on cooldown (${Math.round((now - lastGlobalFetchTime.value)/1000)}s ago)`);
|
||||
return;
|
||||
}
|
||||
lastGlobalFetchTime.value = now;
|
||||
|
||||
console.log('🔄 [queueStore] Fetching all patients for all lokets (Staggered)...');
|
||||
const allLokets = loketStore.lokets || [];
|
||||
if (allLokets.length === 0) {
|
||||
console.warn('⚠️ [queueStore] No lokets available for fetchAllPatients');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use Promise.all for faster fetching
|
||||
await Promise.all(allLokets.map(l => fetchPatientsForLoket(l.id)));
|
||||
console.log(`✅ [queueStore] All patients fetched. Total: ${allPatients.value.length}`);
|
||||
// 2. Staggered Fetch: Instead of Promise.all, we fetch one by one with a small delay
|
||||
// This spreads the load and stays under the 429 threshold
|
||||
for (const loket of allLokets) {
|
||||
const id = loket.id || loket.no;
|
||||
if (id) {
|
||||
// We use await to wait for the fetch to finish, then wait a bit more
|
||||
await fetchPatientsForLoket(id);
|
||||
|
||||
// Wait 150ms between requests (not too long, but enough to breathe)
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ [queueStore] Staggered bulk fetch completed. Total: ${allPatients.value.length} patients in memory.`);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -810,11 +885,11 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
// For example: { 'loket-1': patient, 'loket-2': patient, 'klinik-3': patient }
|
||||
const currentProcessingPatient = ref({});
|
||||
|
||||
// Daftar klinik untuk dropdown diambil 1 pintu dari clinicStore
|
||||
const kliniks = ref(clinicStore.clinics || []);
|
||||
// Daftar klinik untuk dropdown diambil 1 pintu dari clinicStore (Computed for stability)
|
||||
const kliniks = computed(() => clinicStore.clinics || []);
|
||||
|
||||
// Penunjang data - reference dari penunjangStore
|
||||
const penunjangs = ref(penunjangStore.penunjangs || []);
|
||||
// Penunjang data - reference dari penunjangStore (Computed for stability)
|
||||
const penunjangs = computed(() => penunjangStore.penunjangList || []);
|
||||
|
||||
const RESET_HOUR = 2; // 2 AM reset threshold
|
||||
|
||||
@@ -2582,6 +2657,11 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
initWebSocket,
|
||||
disconnectWebSocket,
|
||||
isWsConnected,
|
||||
registerInterest,
|
||||
unregisterInterest,
|
||||
activeLoketInterest,
|
||||
registerGlobalInterest,
|
||||
unregisterGlobalInterest,
|
||||
};
|
||||
|
||||
}, {
|
||||
|
||||
Reference in New Issue
Block a user