feat: implement Grand Paviliun dashboard with specialized room monitoring, queue management, and modular components
This commit is contained in:
No files matched your search
@@ -0,0 +1,211 @@
|
||||
import { ref, computed } from 'vue';
|
||||
import { useQueueStore } from '@/stores/queueStore';
|
||||
import { useMasterStore } from '@/stores/masterStore';
|
||||
import { useClinicStore } from '@/stores/clinicStore';
|
||||
|
||||
export const useGrandPaviliun = () => {
|
||||
const queueStore = useQueueStore();
|
||||
const masterStore = useMasterStore();
|
||||
const clinicStore = useClinicStore();
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
// Expose methods for UI
|
||||
const fetchEksekutifData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// Pastikan data master ruangan sudah dimuat
|
||||
if (!masterStore.ruangData || masterStore.ruangData.length === 0) {
|
||||
// Assume it's loaded in index or master layouts, but fallback if not
|
||||
}
|
||||
|
||||
const eksekutifClinics = masterStore.ruangData.filter(r => r.jenisLayanan === 'Eksekutif');
|
||||
|
||||
// Ambil data antrean untuk semua klinik eksekutif
|
||||
const promises = eksekutifClinics.map(clinic => {
|
||||
queueStore.registerClinicInterest(clinic.kodeKlinik);
|
||||
return queueStore.fetchPatientsForClinic(clinic.kodeKlinik);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
// Connect WebSocket if not connected
|
||||
if (!queueStore.isWsConnected) {
|
||||
queueStore.initWebSocket('admin-klinik-ruang-eksekutif');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error fetching eksekutif data:', e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Computed data for UI
|
||||
const clinics = computed(() => {
|
||||
const eksekutifClinics = (masterStore.ruangData || []).filter(r => r.jenisLayanan === 'Eksekutif');
|
||||
|
||||
return eksekutifClinics.map(clinic => {
|
||||
// Find all patients for this clinic
|
||||
const clinicPatients = queueStore.allPatients.filter(p => p.kodeKlinik === clinic.kodeKlinik && p.processStage === 'klinik-ruang' && p.status !== 'processed');
|
||||
|
||||
// Find current processing patient (we take the first room's current patient if multiple, or global)
|
||||
// Since design shows 1 column per clinic, we find the first patient marked as processing.
|
||||
// Usually, queueStore.currentProcessingPatient is keyed by `klinik-ruang-${kodeKlinik}-${nomorRuang}`
|
||||
let currentPatientObj = null;
|
||||
|
||||
// We look through clinic rooms to find a processing patient
|
||||
for (const ruang of clinic.ruangList) {
|
||||
const key = `klinik-ruang-${clinic.kodeKlinik}-${ruang.nomorRuang}`;
|
||||
const processing = queueStore.currentProcessingPatient[key];
|
||||
if (processing) {
|
||||
currentPatientObj = {
|
||||
...processing,
|
||||
roomIndicator: `RUANG ${ruang.nomorRuang}`,
|
||||
nomorRuang: ruang.nomorRuang
|
||||
};
|
||||
break; // Stop at first one for the column view
|
||||
}
|
||||
}
|
||||
|
||||
// Get queue patients (those not currently processing)
|
||||
const currentPatientNo = currentPatientObj ? currentPatientObj.no : null;
|
||||
const queuePatients = clinicPatients
|
||||
.filter(p => p.no !== currentPatientNo)
|
||||
.map(p => ({
|
||||
...p,
|
||||
isActive: false // Could be based on selection later
|
||||
}));
|
||||
|
||||
return {
|
||||
kodeKlinik: clinic.kodeKlinik,
|
||||
namaKlinik: clinic.namaKlinik,
|
||||
currentPatient: currentPatientObj,
|
||||
queuePatients: queuePatients,
|
||||
roomOptions: clinic.ruangList
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const monitorRooms = computed(() => {
|
||||
const rooms = [];
|
||||
let idCounter = 1;
|
||||
|
||||
const eksekutifClinics = (masterStore.ruangData || []).filter(r => r.jenisLayanan === 'Eksekutif');
|
||||
|
||||
eksekutifClinics.forEach(clinic => {
|
||||
clinic.ruangList.forEach(ruang => {
|
||||
const key = `klinik-ruang-${clinic.kodeKlinik}-${ruang.nomorRuang}`;
|
||||
const processing = queueStore.currentProcessingPatient[key];
|
||||
|
||||
// As user specified: Monitoring only shows data when 'Panggil Pemeriksaan' (Tindakan) is true
|
||||
// Assuming calledTindakan flag exists on processing patient
|
||||
const isActiveInMonitoring = processing && processing.calledTindakan;
|
||||
|
||||
rooms.push({
|
||||
id: idCounter++,
|
||||
name: `RUANG ${ruang.nomorRuang}`,
|
||||
activePatient: isActiveInMonitoring ? (processing.noAntrian?.split(" |")[0] || processing.barcode) : null,
|
||||
clinicName: clinic.namaKlinik
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return rooms;
|
||||
});
|
||||
|
||||
// Actions
|
||||
const handlePemeriksaanAwal = (patient, kodeKlinik, nomorRuang) => {
|
||||
if (!patient || !kodeKlinik || !nomorRuang) return;
|
||||
|
||||
const roomInfo = masterStore.ruangData
|
||||
.find(c => c.kodeKlinik === kodeKlinik)
|
||||
?.ruangList.find(r => String(r.nomorRuang) === String(nomorRuang));
|
||||
|
||||
if (!roomInfo) return;
|
||||
|
||||
const wsData = {
|
||||
action: 'callPemeriksaanAwal',
|
||||
patientId: patient.barcode || patient.visitId,
|
||||
patientCode: patient.noAntrian?.split(" |")[0] || patient.barcode,
|
||||
patientName: patient.name || '',
|
||||
roomName: roomInfo.namaRuang,
|
||||
clinicCode: kodeKlinik,
|
||||
tipePanggilan: 'Pemeriksaan Awal'
|
||||
};
|
||||
|
||||
queueStore.sendViaPost(wsData);
|
||||
|
||||
// Update local state to mark as called
|
||||
if (patient.no) {
|
||||
const idx = queueStore.allPatients.findIndex(p => p.no === patient.no);
|
||||
if (idx !== -1) {
|
||||
queueStore.allPatients[idx].calledPemeriksaanAwal = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handlePanggilPemeriksaan = (patient, kodeKlinik, nomorRuang) => {
|
||||
if (!patient || !kodeKlinik || !nomorRuang) return;
|
||||
|
||||
const roomInfo = masterStore.ruangData
|
||||
.find(c => c.kodeKlinik === kodeKlinik)
|
||||
?.ruangList.find(r => String(r.nomorRuang) === String(nomorRuang));
|
||||
|
||||
if (!roomInfo) return;
|
||||
|
||||
const wsData = {
|
||||
action: 'callTindakan',
|
||||
patientId: patient.barcode || patient.visitId,
|
||||
patientCode: patient.noAntrian?.split(" |")[0] || patient.barcode,
|
||||
patientName: patient.name || '',
|
||||
roomName: roomInfo.namaRuang,
|
||||
clinicCode: kodeKlinik,
|
||||
tipePanggilan: 'Tindakan'
|
||||
};
|
||||
|
||||
queueStore.sendViaPost(wsData);
|
||||
|
||||
// Update local state to mark as called
|
||||
if (patient.no) {
|
||||
const idx = queueStore.allPatients.findIndex(p => p.no === patient.no);
|
||||
if (idx !== -1) {
|
||||
queueStore.allPatients[idx].calledTindakan = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const processPatientAction = async (patient, action, kodeKlinik, nomorRuang) => {
|
||||
if (!patient || !kodeKlinik || !nomorRuang) return { success: false, message: 'Data tidak lengkap' };
|
||||
|
||||
const result = await queueStore.processPatientKlinikRuang(
|
||||
patient,
|
||||
action,
|
||||
kodeKlinik,
|
||||
nomorRuang
|
||||
);
|
||||
|
||||
if (action === 'pending' && result.success) {
|
||||
const patientIndex = queueStore.allPatients.findIndex(p => p.no === patient.no);
|
||||
if (patientIndex !== -1) {
|
||||
queueStore.allPatients[patientIndex] = {
|
||||
...queueStore.allPatients[patientIndex],
|
||||
status: 'pending'
|
||||
};
|
||||
const key = `klinik-ruang-${kodeKlinik}-${nomorRuang}`;
|
||||
queueStore.currentProcessingPatient[key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return {
|
||||
clinics,
|
||||
monitorRooms,
|
||||
loading,
|
||||
fetchEksekutifData,
|
||||
handlePemeriksaanAwal,
|
||||
handlePanggilPemeriksaan,
|
||||
processPatientAction
|
||||
};
|
||||
};
|
||||
@@ -94,6 +94,14 @@ export const useQueue = (adminType = "loket", specificId = null) => {
|
||||
const isAssignedToThis = p.loketId && String(p.loketId) === String(targetId);
|
||||
if (isAssignedToThis) return true;
|
||||
|
||||
// Add strict isolation between Eksekutif and Reguler for unassigned tickets
|
||||
const isPatientEksekutif = p.noAntrian && (String(p.noAntrian).startsWith('E') || String(p.noAntrian).startsWith('F-E'));
|
||||
const isLoketEksekutif = thisLoket.tipeLoket === 'EKSEKUTIF' || thisLoket.id >= 1000;
|
||||
|
||||
if (isPatientEksekutif !== isLoketEksekutif) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isServedByThis = !p.loketId && thisLoket.pelayanan && Array.isArray(thisLoket.pelayanan) && thisLoket.pelayanan.includes(p.kodeKlinik);
|
||||
|
||||
return isServedByThis;
|
||||
|
||||
Reference in New Issue
Block a user