feat: implement AdminKlinikRuang page for managing patient queues, including processing, calling, filtering, and a global search, alongside a new queueStore.

This commit is contained in:
Fanrouver
2026-02-13 14:26:49 +07:00
parent 4df13cbe70
commit 67a5514654
2 changed files with 134 additions and 87 deletions

No files matched your search

+105 -64
View File
@@ -131,8 +131,18 @@ export const useQueueStore = defineStore('queue', () => {
const latestStatus = visitStatuses[visitStatuses.length - 1];
let patientStatus = 'di-loket';
if (latestStatus?.desc) {
const desc = latestStatus.desc.toLowerCase();
// Map status from database ID if available
if (latestStatus?.fk_ref_visit_status_id) {
const statusId = String(latestStatus.fk_ref_visit_status_id);
if (PATIENT_STATUS_MAP[statusId]) {
patientStatus = PATIENT_STATUS_MAP[statusId];
}
}
// Fallback to description search
if (patientStatus === 'di-loket' && (latestStatus?.visit_status_description || latestStatus?.desc)) {
const desc = (latestStatus.visit_status_description || latestStatus.desc).toLowerCase();
if (desc.includes('pemeriksaan') || desc.includes('sedang diproses')) {
patientStatus = 'pemeriksaan';
}
@@ -155,7 +165,7 @@ export const useQueueStore = defineStore('queue', () => {
createdAt: visit.registration_datetime || new Date().toISOString(),
visitType: visit.visit_type_name || 'ONSITE',
noRM: visit.norm || '',
fastTrack: 'TIDAK',
fastTrack: (service.ticket && String(service.ticket).startsWith('F-')) ? "YA" : "TIDAK",
registrationType: 'api',
visitId: visit.visit_id || visit.id,
visitCode: visit.visit_code,
@@ -202,32 +212,44 @@ export const useQueueStore = defineStore('queue', () => {
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) {
if (apiP) {
newPatientMap.delete(key); // Mark as handled
return {
// Preserve fastTrack and F- prefix if existing patient has it
const isFastTrack = (p.fastTrack === "YA" || (p.noAntrian && p.noAntrian.startsWith('F-')));
let finalNoAntrian = apiP.noAntrian;
if (isFastTrack && !finalNoAntrian.startsWith('F-')) {
finalNoAntrian = `F-${finalNoAntrian}`;
}
const mergedP = {
...apiP,
status: p.status,
calledPemeriksaanAwal: p.calledPemeriksaanAwal,
calledTindakan: p.calledTindakan,
tipeLayanan: p.tipeLayanan,
lastCalledAt: p.lastCalledAt,
lastCalledTipeLayanan: p.lastCalledTipeLayanan
fastTrack: isFastTrack ? "YA" : apiP.fastTrack,
noAntrian: finalNoAntrian
};
} else {
newPatientMap.delete(key); // Handled
return apiP;
// If we have local updates, preserve them
const hasLocalUpdates = (
p.status !== 'di-loket' ||
p.calledPemeriksaanAwal ||
p.calledTindakan ||
p.lastCalledAt
);
if (hasLocalUpdates) {
return {
...mergedP,
status: p.status,
calledPemeriksaanAwal: p.calledPemeriksaanAwal,
calledTindakan: p.calledTindakan,
tipeLayanan: p.tipeLayanan,
lastCalledAt: p.lastCalledAt,
lastCalledTipeLayanan: p.lastCalledTipeLayanan
};
} else {
return mergedP;
}
}
}
// 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;
@@ -413,22 +435,45 @@ export const useQueueStore = defineStore('queue', () => {
* idvisit 5, 6 = di-loket (PS CHECK-IN, TP LOKET)
*/
const PATIENT_STATUS_MAP = {
1: 'menunggu',
2: 'menunggu',
3: 'anjungan',
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',
"28": 'pending', // PE PENDAFTARAN
"29": 'terlambat' // TR PENDAFTARAN
// Stage: ANJUNGAN (1-5, 25-27)
1: 'menunggu', // CT ANJUNGAN
2: 'menunggu', // RT ANJUNGAN
3: 'anjungan', // PG ANJUNGAN
4: 'anjungan', // RT CHECK-IN
5: 'di-loket', // PS CHECK-IN
25: 'menunggu', // BT ANJUNGAN
26: 'pending', // PE ANJUNGAN
27: 'terlambat', // TE ANJUNGAN
// Stage: LOKET (6-9, 28-29)
6: 'di-loket', // RT PENDAFTARAN
7: 'anjungan', // PG PENDAFTARAN
8: 'di-loket', // PO PENDAFTARAN
9: 'di-loket', // PS PENDAFTARAN
28: 'pending', // PE PENDAFTARAN
29: 'terlambat', // TR PENDAFTARAN
// Stage: VERIFIKASI (10-13, 30-31)
10: 'di-loket',
11: 'anjungan',
12: 'di-loket',
13: 'di-loket',
30: 'pending',
31: 'terlambat',
// Stage: PELAYANAN (14-19, 32-33)
14: 'pemeriksaan', // CT PEMERIKSAAN
15: 'pemeriksaan', // RT PEMERIKSAAN
16: 'pemeriksaan', // PG PEMERIKSAAN AWAL
17: 'pemeriksaan', // PG PEMERIKSAAN
18: 'pemeriksaan', // PO PEMERIKSAAN
19: 'pemeriksaan', // PS PEMERIKSAAN
32: 'pending', // PE PEMERIKSAAN
33: 'terlambat', // TR PEMERIKSAAN
// String versions for robustness
"1": 'menunggu', "2": 'menunggu', "3": 'anjungan', "4": 'anjungan', "5": 'di-loket',
"6": 'di-loket', "14": 'pemeriksaan', "15": 'pemeriksaan', "28": 'pending', "29": 'terlambat'
};
/**
@@ -517,7 +562,7 @@ export const useQueueStore = defineStore('queue', () => {
shift: apiPatient.shift ? `Shift ${apiPatient.shift}` : '',
klinik: apiPatient.klinik || '',
kodeKlinik: apiPatient.klinik || '', // Will be mapped later
fastTrack: "TIDAK",
fastTrack: (apiPatient.ticket && String(apiPatient.ticket).startsWith('F-')) ? "YA" : "TIDAK",
pembayaran: apiPatient.pembayaran || '',
status: status,
processStage: 'loket',
@@ -678,7 +723,7 @@ export const useQueueStore = defineStore('queue', () => {
}
});
// 3. Update new patients with preserved status
// 3. Update new patients with preserved status and fasttrack
patientsWithLoketId.forEach(p => {
const key = p.idtiket ? `id-${p.idtiket}` : `bc-${p.barcode}`;
if (preservedStatuses.has(key)) {
@@ -725,25 +770,11 @@ export const useQueueStore = defineStore('queue', () => {
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;
// 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
if (p.registrationType === 'api' && String(p.loketId) === String(loketId)) return false;
if (p.registrationType === 'api' && String(p.loketId) === String(loketId)) {
// But only if it's NOT in the new batch (stale)
if (!newPatientMap.has(key)) return false;
}
return true;
});
@@ -767,15 +798,25 @@ export const useQueueStore = defineStore('queue', () => {
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)
// Only update if new status has higher or equal priority
// This prevents API data from overwriting LocalStorage terlambat/pending status
if (newPrio > existingPrio) {
if (newPrio >= existingPrio) {
console.log(`🔄 [queueStore] Updating patient ${newPatient.barcode} from ${existing.status} to ${newPatient.status}`);
// Preserve fastTrack and F- prefix if existing patient has it
const isFastTrack = (existing.fastTrack === "YA" || (existing.noAntrian && existing.noAntrian.startsWith('F-')));
let finalNoAntrian = newPatient.noAntrian;
if (isFastTrack && !finalNoAntrian.startsWith('F-')) {
finalNoAntrian = `F-${finalNoAntrian}`;
}
allPatients.value[existingIndex] = {
...newPatient,
// Preserve some local state
calledByAdmin: existing.calledByAdmin,
lastCalledAt: existing.lastCalledAt
lastCalledAt: existing.lastCalledAt,
fastTrack: isFastTrack ? "YA" : newPatient.fastTrack,
noAntrian: finalNoAntrian
};
} else {
console.log(`🛡️ [queueStore] Protecting patient ${existing.barcode} with status ${existing.status} from API overwrite (${newPatient.status})`);