PUSH UP WOKE UPDATE Api ANTREAN MASUK DAN MINOR CHANGE
This commit is contained in:
No files matched your search
+157
-107
@@ -1,6 +1,6 @@
|
||||
// stores/queueStore.js
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { useClinicStore } from './clinicStore';
|
||||
import { usePenunjangStore } from './penunjangStore';
|
||||
import { useLoketStore } from './loketStore';
|
||||
@@ -21,6 +21,10 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
|
||||
// Throttle mechanism: track last fetch time per loket
|
||||
const lastFetchTime = ref({});
|
||||
|
||||
// Synchronization Guard: track last update time to break loops across tabs
|
||||
const lastUpdated = ref(Date.now());
|
||||
// synchronization guard (moved lower)
|
||||
|
||||
/**
|
||||
* Sync patient status to apiPatientsPerLoket for reactivity
|
||||
@@ -563,90 +567,90 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
// SEED DATA: ONLY EKSEKUTIF PATIENTS
|
||||
// All REGULER patients will come from API
|
||||
const seedPatients = [
|
||||
{
|
||||
no: 1,
|
||||
jamPanggil: "11:20",
|
||||
barcode: seedBarcodeE1,
|
||||
noAntrian: `EA001 | Online - ${seedBarcodeE1}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "KANDUNGAN",
|
||||
kodeKlinik: "KD",
|
||||
fastTrack: "TIDAK",
|
||||
pembayaran: "Eksekutif",
|
||||
status: "waiting",
|
||||
processStage: "loket",
|
||||
createdAt: new Date().toISOString(),
|
||||
registrationType: 'online',
|
||||
visitType: 'SEKARANG',
|
||||
visitDate: new Date().toISOString().substring(0, 10),
|
||||
namaDokter: "Dr. Ahmad Wijaya, Sp.OG",
|
||||
noRM: "RM-E001",
|
||||
penanggungJawab: null,
|
||||
alasanFastTrack: null,
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
jamPanggil: "13:45",
|
||||
barcode: seedBarcodeE2,
|
||||
noAntrian: `EA002 | Online - ${seedBarcodeE2}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "IPD",
|
||||
kodeKlinik: "IP",
|
||||
fastTrack: "TIDAK",
|
||||
pembayaran: "Eksekutif",
|
||||
status: "waiting",
|
||||
processStage: "loket",
|
||||
createdAt: new Date().toISOString(),
|
||||
registrationType: 'online',
|
||||
visitType: 'SEKARANG',
|
||||
visitDate: new Date().toISOString().substring(0, 10),
|
||||
namaDokter: "Dr. Budi Santoso, Sp.PD",
|
||||
noRM: "RM-E002",
|
||||
penanggungJawab: null,
|
||||
alasanFastTrack: null,
|
||||
},
|
||||
{
|
||||
no: 3,
|
||||
jamPanggil: "15:10",
|
||||
barcode: seedBarcodeE3,
|
||||
noAntrian: `F-EA003 | Online - ${seedBarcodeE3}`,
|
||||
shift: "Shift 2",
|
||||
klinik: "SARAF",
|
||||
kodeKlinik: "SR",
|
||||
fastTrack: "YA",
|
||||
pembayaran: "Eksekutif",
|
||||
status: "waiting",
|
||||
processStage: "loket",
|
||||
createdAt: new Date().toISOString(),
|
||||
registrationType: 'online',
|
||||
visitType: 'SEKARANG',
|
||||
visitDate: new Date().toISOString().substring(0, 10),
|
||||
namaDokter: "Dr. Citra Dewi, Sp.S",
|
||||
noRM: "RM-E003",
|
||||
penanggungJawab: "Dr. Citra Dewi",
|
||||
alasanFastTrack: "Pasien Eksekutif prioritas",
|
||||
},
|
||||
{
|
||||
no: 4,
|
||||
jamPanggil: "16:30",
|
||||
barcode: seedBarcodeE4,
|
||||
noAntrian: `EA004 | Online - ${seedBarcodeE4}`,
|
||||
shift: "Shift 2",
|
||||
klinik: "THT",
|
||||
kodeKlinik: "TH",
|
||||
fastTrack: "TIDAK",
|
||||
pembayaran: "VIP",
|
||||
status: "waiting",
|
||||
processStage: "loket",
|
||||
createdAt: new Date().toISOString(),
|
||||
registrationType: 'online',
|
||||
visitType: 'SEKARANG',
|
||||
visitDate: new Date().toISOString().substring(0, 10),
|
||||
namaDokter: "Dr. Eka Putri, Sp.THT",
|
||||
noRM: "RM-E004",
|
||||
penanggungJawab: null,
|
||||
alasanFastTrack: null,
|
||||
},
|
||||
// {
|
||||
// no: 1,
|
||||
// jamPanggil: "11:20",
|
||||
// barcode: seedBarcodeE1,
|
||||
// noAntrian: `EA001 | Online - ${seedBarcodeE1}`,
|
||||
// shift: "Shift 1",
|
||||
// klinik: "KANDUNGAN",
|
||||
// kodeKlinik: "KD",
|
||||
// fastTrack: "TIDAK",
|
||||
// pembayaran: "Eksekutif",
|
||||
// status: "waiting",
|
||||
// processStage: "loket",
|
||||
// createdAt: new Date().toISOString(),
|
||||
// registrationType: 'online',
|
||||
// visitType: 'SEKARANG',
|
||||
// visitDate: new Date().toISOString().substring(0, 10),
|
||||
// namaDokter: "Dr. Ahmad Wijaya, Sp.OG",
|
||||
// noRM: "RM-E001",
|
||||
// penanggungJawab: null,
|
||||
// alasanFastTrack: null,
|
||||
// },
|
||||
// {
|
||||
// no: 2,
|
||||
// jamPanggil: "13:45",
|
||||
// barcode: seedBarcodeE2,
|
||||
// noAntrian: `EA002 | Online - ${seedBarcodeE2}`,
|
||||
// shift: "Shift 1",
|
||||
// klinik: "IPD",
|
||||
// kodeKlinik: "IP",
|
||||
// fastTrack: "TIDAK",
|
||||
// pembayaran: "Eksekutif",
|
||||
// status: "waiting",
|
||||
// processStage: "loket",
|
||||
// createdAt: new Date().toISOString(),
|
||||
// registrationType: 'online',
|
||||
// visitType: 'SEKARANG',
|
||||
// visitDate: new Date().toISOString().substring(0, 10),
|
||||
// namaDokter: "Dr. Budi Santoso, Sp.PD",
|
||||
// noRM: "RM-E002",
|
||||
// penanggungJawab: null,
|
||||
// alasanFastTrack: null,
|
||||
// },
|
||||
// {
|
||||
// no: 3,
|
||||
// jamPanggil: "15:10",
|
||||
// barcode: seedBarcodeE3,
|
||||
// noAntrian: `F-EA003 | Online - ${seedBarcodeE3}`,
|
||||
// shift: "Shift 2",
|
||||
// klinik: "SARAF",
|
||||
// kodeKlinik: "SR",
|
||||
// fastTrack: "YA",
|
||||
// pembayaran: "Eksekutif",
|
||||
// status: "waiting",
|
||||
// processStage: "loket",
|
||||
// createdAt: new Date().toISOString(),
|
||||
// registrationType: 'online',
|
||||
// visitType: 'SEKARANG',
|
||||
// visitDate: new Date().toISOString().substring(0, 10),
|
||||
// namaDokter: "Dr. Citra Dewi, Sp.S",
|
||||
// noRM: "RM-E003",
|
||||
// penanggungJawab: "Dr. Citra Dewi",
|
||||
// alasanFastTrack: "Pasien Eksekutif prioritas",
|
||||
// },
|
||||
// {
|
||||
// no: 4,
|
||||
// jamPanggil: "16:30",
|
||||
// barcode: seedBarcodeE4,
|
||||
// noAntrian: `EA004 | Online - ${seedBarcodeE4}`,
|
||||
// shift: "Shift 2",
|
||||
// klinik: "THT",
|
||||
// kodeKlinik: "TH",
|
||||
// fastTrack: "TIDAK",
|
||||
// pembayaran: "VIP",
|
||||
// status: "waiting",
|
||||
// processStage: "loket",
|
||||
// createdAt: new Date().toISOString(),
|
||||
// registrationType: 'online',
|
||||
// visitType: 'SEKARANG',
|
||||
// visitDate: new Date().toISOString().substring(0, 10),
|
||||
// namaDokter: "Dr. Eka Putri, Sp.THT",
|
||||
// noRM: "RM-E004",
|
||||
// penanggungJawab: null,
|
||||
// alasanFastTrack: null,
|
||||
// },
|
||||
];
|
||||
|
||||
|
||||
@@ -771,21 +775,26 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const isProcessing = Object.values(currentProcessingPatient.value).some(p => p && p.no === patient.no);
|
||||
if (isProcessing) return true;
|
||||
|
||||
const createdAt = patient.createdAt ? new Date(patient.createdAt) : null;
|
||||
const visitDateStr = patient.visitDate; // YYYY-MM-DD
|
||||
|
||||
const threshold = getResetThreshold();
|
||||
|
||||
// Check by createdAt first
|
||||
if (createdAt && createdAt < threshold) {
|
||||
return false;
|
||||
// Get YYYY-MM-DD for the logical "today" (based on 2 AM threshold)
|
||||
const year = threshold.getFullYear();
|
||||
const month = String(threshold.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(threshold.getDate()).padStart(2, '0');
|
||||
const logicalTodayStr = `${year}-${month}-${day}`;
|
||||
|
||||
// 1. If visitDate (YYYY-MM-DD) matches logical today's date, it's a "today" patient
|
||||
// This correctly includes API patients with midnight timestamps (00:00:00)
|
||||
if (patient.visitDate) {
|
||||
if (patient.visitDate === logicalTodayStr) return true;
|
||||
if (patient.visitDate < logicalTodayStr) return false;
|
||||
}
|
||||
|
||||
// Check by visitDate if available
|
||||
if (visitDateStr) {
|
||||
const vDate = new Date(visitDateStr);
|
||||
vDate.setHours(23, 59, 59, 999); // End of visit date
|
||||
if (vDate < threshold) return false;
|
||||
// 2. Fallback: check createdAt timestamp (critical for seed/manual tickets)
|
||||
// Tickets created before today's 2 AM are stale
|
||||
const createdAt = patient.createdAt ? new Date(patient.createdAt) : null;
|
||||
if (createdAt && createdAt < threshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -807,37 +816,77 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
syncCountersWithState();
|
||||
};
|
||||
|
||||
// Synchronization Guard: track if we are currently hydrating from another tab
|
||||
let isSyncing = false;
|
||||
|
||||
// CROSS-TAB SYNC: Listen for storage events to update store across tabs
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('storage', (event) => {
|
||||
if (event.key === 'queue-store-state') {
|
||||
console.log('🔄 queue-store-state changed in another tab, re-hydrating...');
|
||||
if (!event.newValue) return;
|
||||
|
||||
try {
|
||||
const newState = JSON.parse(event.newValue);
|
||||
|
||||
// BREAK THE LOOP: Only re-hydrate if the incoming data is strictly newer than ours
|
||||
// Defensive check: Handle cases where lastUpdated might be undefined or missing
|
||||
const incomingVersion = Number(newState?.lastUpdated || 0);
|
||||
const currentVersion = Number(lastUpdated.value || 0);
|
||||
|
||||
if (incomingVersion === 0 || incomingVersion <= currentVersion) {
|
||||
// Already up to date, older, or corrupted data. Skip hydration.
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🔄 Syncing state to version: ${incomingVersion}`);
|
||||
|
||||
if (newState) {
|
||||
if (newState.allPatients) allPatients.value = newState.allPatients;
|
||||
if (newState.quotaUsed !== undefined) quotaUsed.value = newState.quotaUsed;
|
||||
if (newState.currentProcessingPatient) currentProcessingPatient.value = newState.currentProcessingPatient;
|
||||
isSyncing = true; // Block local watch from updating timestamp
|
||||
|
||||
// FIX: Hydrate apiPatientsPerLoket as well since it is persisted.
|
||||
// Failure to hydrate this causes "ping-pong" writes between tabs if they have different local values.
|
||||
if (newState.apiPatientsPerLoket) apiPatientsPerLoket.value = newState.apiPatientsPerLoket;
|
||||
try {
|
||||
if (newState.allPatients) allPatients.value = newState.allPatients;
|
||||
if (newState.quotaUsed !== undefined) quotaUsed.value = newState.quotaUsed;
|
||||
if (newState.currentProcessingPatient) currentProcessingPatient.value = newState.currentProcessingPatient;
|
||||
if (newState.apiPatientsPerLoket) apiPatientsPerLoket.value = newState.apiPatientsPerLoket;
|
||||
|
||||
// Set local timestamp to match the source
|
||||
lastUpdated.value = incomingVersion;
|
||||
|
||||
syncCountersWithState();
|
||||
} finally {
|
||||
// Ensure we release the lock
|
||||
setTimeout(() => { isSyncing = false; }, 50);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error hydrating from storage event:', e);
|
||||
isSyncing = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Automatic timestamp update for local changes
|
||||
// This ensures lastUpdated changes whenever state changes LOCALLY
|
||||
// but skips updates coming from isSyncing = true
|
||||
watch([allPatients, quotaUsed, currentProcessingPatient, apiPatientsPerLoket], () => {
|
||||
if (!isSyncing) {
|
||||
lastUpdated.value = Date.now();
|
||||
// console.log('✨ Local state updated, version:', lastUpdated.value);
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
// Computed - Filter berdasarkan process stage dan status
|
||||
const getPatientsByStage = (stage) => {
|
||||
return computed(() => {
|
||||
// Filter by stage AND date (today only)
|
||||
const patients = allPatients.value.filter(p => p.processStage === stage && isTodayPatient(p));
|
||||
// optimization: filter all once, then sub-filter
|
||||
const patients = allPatients.value.filter(p => p && p.processStage === stage && isTodayPatient(p));
|
||||
|
||||
// Debug log
|
||||
console.log(`getPatientsByStage(${stage}):`, patients.length, 'patients (Today)');
|
||||
// Debug log (limited to avoid spam)
|
||||
if (patients.length > 0) {
|
||||
// console.log(`getPatientsByStage(${stage}):`, patients.length, 'patients (Today)');
|
||||
}
|
||||
|
||||
return {
|
||||
all: patients,
|
||||
@@ -936,6 +985,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
...allPatients.value[index],
|
||||
status: "waiting",
|
||||
// Assign to this specific loket if it was unassigned
|
||||
loketId: (adminType === 'loket' && targetId) ? parseInt(targetId) : allPatients.value[index].loketId,
|
||||
lastCalledAt: callTimestamp
|
||||
};
|
||||
|
||||
@@ -2227,7 +2277,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
persist: {
|
||||
key: 'queue-store-state',
|
||||
storage: typeof window !== 'undefined' ? localStorage : undefined,
|
||||
paths: ['allPatients', 'quotaUsed', 'currentProcessingPatient', 'apiPatientsPerLoket'],
|
||||
paths: ['allPatients', 'quotaUsed', 'currentProcessingPatient', 'apiPatientsPerLoket', 'lastUpdated'],
|
||||
serializer: {
|
||||
deserialize: JSON.parse,
|
||||
serialize: JSON.stringify,
|
||||
|
||||
Reference in New Issue
Block a user