PUSH UP WOKE UPDATE Api ANTREAN MASUK DAN MINOR CHANGE

This commit is contained in:
Fanrouver
2026-01-30 13:40:43 +07:00
parent 7c3fc4bd50
commit 507f415710
13 changed files with 808 additions and 217 deletions

No files matched your search

+157 -107
View File
@@ -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,