555 lines
16 KiB
JavaScript
555 lines
16 KiB
JavaScript
// stores/queueStore.js
|
|
import { defineStore } from 'pinia';
|
|
import { ref, computed } from 'vue';
|
|
import { useClinicStore } from './clinicStore';
|
|
|
|
export const useQueueStore = defineStore('queue', () => {
|
|
const clinicStore = useClinicStore();
|
|
// Seed data for easy reset during dev
|
|
const seedPatients = [
|
|
{
|
|
no: 1,
|
|
jamPanggil: "12:49",
|
|
barcode: "250811100163",
|
|
noAntrian: "UM1001 | Online - 250811100163",
|
|
shift: "Shift 1",
|
|
klinik: "KANDUNGAN",
|
|
fastTrack: "YA", // Fast Track Patient
|
|
pembayaran: "BPJS",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 2,
|
|
jamPanggil: "10:52",
|
|
barcode: "250811100155",
|
|
noAntrian: "UM1002 | Online - 250811100155",
|
|
shift: "Shift 1",
|
|
klinik: "IPD",
|
|
fastTrack: "TIDAK",
|
|
pembayaran: "UMUM",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 3,
|
|
jamPanggil: "09:30",
|
|
barcode: "250811100200",
|
|
noAntrian: "UM1003 | Online - 250811100200",
|
|
shift: "Shift 1",
|
|
klinik: "SARAF",
|
|
fastTrack: "YA", // Fast Track Patient
|
|
pembayaran: "BPJS",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 4,
|
|
jamPanggil: "14:15",
|
|
barcode: "250811100210",
|
|
noAntrian: "UM1004 | Online - 250811100210",
|
|
shift: "Shift 1",
|
|
klinik: "THT",
|
|
fastTrack: "TIDAK",
|
|
pembayaran: "UMUM",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 5,
|
|
jamPanggil: "12:49",
|
|
barcode: "250811100163",
|
|
noAntrian: "UM1005 | Online - 250811100163",
|
|
shift: "Shift 2",
|
|
klinik: "KANDUNGAN",
|
|
fastTrack: "TIDAK",
|
|
pembayaran: "UMUM",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 6,
|
|
jamPanggil: "10:52",
|
|
barcode: "250811100155",
|
|
noAntrian: "UM1006 | Online - 250811100155",
|
|
shift: "Shift 1",
|
|
klinik: "IPD",
|
|
fastTrack: "YA", // Fast Track Patient
|
|
pembayaran: "BPJS",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 7,
|
|
jamPanggil: "09:30",
|
|
barcode: "250811100200",
|
|
noAntrian: "UM1007 | Online - 250811100200",
|
|
shift: "Shift 1",
|
|
klinik: "SARAF",
|
|
fastTrack: "TIDAK",
|
|
pembayaran: "UMUM",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 8,
|
|
jamPanggil: "14:15",
|
|
barcode: "250811100210",
|
|
noAntrian: "UM1008 | Online - 250811100210",
|
|
shift: "Shift 1",
|
|
klinik: "THT",
|
|
fastTrack: "TIDAK",
|
|
pembayaran: "UMUM",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 9,
|
|
jamPanggil: "12:49",
|
|
barcode: "250811100163",
|
|
noAntrian: "UM1009 | Online - 250811100163",
|
|
shift: "Shift 2",
|
|
klinik: "KANDUNGAN",
|
|
fastTrack: "YA", // Fast Track Patient
|
|
pembayaran: "BPJS",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 10,
|
|
jamPanggil: "10:52",
|
|
barcode: "250811100155",
|
|
noAntrian: "UM1010 | Online - 250811100155",
|
|
shift: "Shift 1",
|
|
klinik: "IPD",
|
|
fastTrack: "TIDAK",
|
|
pembayaran: "UMUM",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 11,
|
|
jamPanggil: "09:30",
|
|
barcode: "250811100200",
|
|
noAntrian: "UM1011 | Online - 250811100200",
|
|
shift: "Shift 1",
|
|
klinik: "SARAF",
|
|
fastTrack: "TIDAK",
|
|
pembayaran: "UMUM",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
{
|
|
no: 12,
|
|
jamPanggil: "14:15",
|
|
barcode: "250811100210",
|
|
noAntrian: "UM1012 | Online - 250811100210",
|
|
shift: "Shift 2",
|
|
klinik: "THT",
|
|
fastTrack: "YA", // Fast Track Patient
|
|
pembayaran: "BPJS",
|
|
status: "waiting",
|
|
processStage: "loket",
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
];
|
|
|
|
const cloneSeed = () => seedPatients.map(p => ({ ...p }));
|
|
|
|
// State
|
|
const allPatients = ref(cloneSeed());
|
|
|
|
const quotaUsed = ref(5);
|
|
const currentProcessingPatient = ref({
|
|
loket: null,
|
|
klinik: null,
|
|
penunjang: null,
|
|
});
|
|
|
|
// Daftar klinik untuk dropdown diambil 1 pintu dari clinicStore
|
|
const kliniks = computed(() => {
|
|
const baseList = typeof clinicStore.getClinicsForDropdown === 'function'
|
|
? clinicStore.getClinicsForDropdown()
|
|
: [];
|
|
|
|
// Bentuk objek disesuaikan dengan yang dipakai di useQueue (id, name, kode)
|
|
return baseList.map((c) => ({
|
|
id: c.id,
|
|
name: c.name,
|
|
kode: c.kode,
|
|
icon: c.icon,
|
|
available: c.available,
|
|
}));
|
|
});
|
|
|
|
const penunjangs = ref([
|
|
{ id: 1, name: "LABORATORIUM" },
|
|
{ id: 2, name: "RADIOLOGI" },
|
|
{ id: 3, name: "USG" },
|
|
{ id: 4, name: "CT SCAN" },
|
|
{ id: 5, name: "MRI" },
|
|
{ id: 6, name: "EKG" },
|
|
{ id: 7, name: "ENDOSKOPI" },
|
|
{ id: 8, name: "FISIOTERAPI" },
|
|
{ id: 9, name: "FARMASI" },
|
|
]);
|
|
|
|
// Computed - Filter berdasarkan process stage dan status
|
|
const getPatientsByStage = (stage) => {
|
|
return computed(() => {
|
|
const patients = allPatients.value.filter(p => p.processStage === stage);
|
|
|
|
// Debug log
|
|
console.log(`getPatientsByStage(${stage}):`, patients.length, 'patients');
|
|
if (patients.length > 0) {
|
|
console.log('Sample patient properties:', Object.keys(patients[0]));
|
|
console.log('Sample fastTrack values:', patients.map(p => p.fastTrack));
|
|
}
|
|
|
|
return {
|
|
all: patients,
|
|
waiting: patients.filter(p => p.status === 'waiting'),
|
|
diLoket: patients.filter(p => p.status === 'di-loket'),
|
|
terlambat: patients.filter(p => p.status === 'terlambat'),
|
|
pending: patients.filter(p => p.status === 'pending'),
|
|
};
|
|
});
|
|
};
|
|
|
|
// Total pasien per stage
|
|
const getTotalPasienByStage = (stage) => {
|
|
return computed(() =>
|
|
allPatients.value.filter(p => p.processStage === stage).length
|
|
);
|
|
};
|
|
|
|
const totalPasien = computed(() => allPatients.value.length);
|
|
|
|
const resetPatients = () => {
|
|
allPatients.value = cloneSeed();
|
|
quotaUsed.value = 5;
|
|
currentProcessingPatient.value = { loket: null, klinik: null, penunjang: null };
|
|
};
|
|
|
|
// Actions
|
|
const callNext = (adminType = 'loket') => {
|
|
const stageMap = {
|
|
'loket': 'loket',
|
|
'klinik': 'klinik',
|
|
'penunjang': 'penunjang'
|
|
};
|
|
|
|
const targetStage = stageMap[adminType];
|
|
const nextPatient = allPatients.value.find(p =>
|
|
p.status === 'waiting' && p.processStage === targetStage
|
|
);
|
|
|
|
if (!nextPatient) {
|
|
return { success: false, message: "Tidak ada pasien selanjutnya" };
|
|
}
|
|
|
|
if (quotaUsed.value >= 150) {
|
|
return { success: false, message: "Quota sudah penuh" };
|
|
}
|
|
|
|
// Update dengan cara yang Vue reactive
|
|
const index = allPatients.value.findIndex(p => p.no === nextPatient.no);
|
|
if (index !== -1) {
|
|
allPatients.value[index] = { ...allPatients.value[index], status: "di-loket" };
|
|
}
|
|
|
|
quotaUsed.value++;
|
|
|
|
return {
|
|
success: true,
|
|
message: `Memanggil pasien ${nextPatient.noAntrian.split(" |")[0]}`,
|
|
};
|
|
};
|
|
|
|
const callMultiplePatients = (count, adminType = 'loket') => {
|
|
const stageMap = {
|
|
'loket': 'loket',
|
|
'klinik': 'klinik',
|
|
'penunjang': 'penunjang'
|
|
};
|
|
|
|
const targetStage = stageMap[adminType];
|
|
const waitingList = allPatients.value.filter(p =>
|
|
p.status === 'waiting' && p.processStage === targetStage
|
|
);
|
|
|
|
const patientsToCall = waitingList.slice(0, count);
|
|
|
|
if (patientsToCall.length === 0) {
|
|
return { success: false, message: "Tidak ada pasien yang menunggu" };
|
|
}
|
|
|
|
if (quotaUsed.value + patientsToCall.length > 150) {
|
|
return { success: false, message: "Quota tidak mencukupi" };
|
|
}
|
|
|
|
// Update dengan cara yang Vue reactive
|
|
patientsToCall.forEach((patient) => {
|
|
const index = allPatients.value.findIndex(p => p.no === patient.no);
|
|
if (index !== -1) {
|
|
allPatients.value[index] = { ...allPatients.value[index], status: "di-loket" };
|
|
}
|
|
});
|
|
|
|
quotaUsed.value += patientsToCall.length;
|
|
|
|
return {
|
|
success: true,
|
|
message: `Memanggil ${patientsToCall.length} pasien ke loket`,
|
|
};
|
|
};
|
|
|
|
const processPatient = (patient, action, adminType = 'loket') => {
|
|
const patientCode = patient.noAntrian.split(" |")[0];
|
|
let message = "";
|
|
|
|
// Find patient index for reactive update
|
|
const patientIndex = allPatients.value.findIndex(p => p.no === patient.no);
|
|
|
|
if (patientIndex === -1) {
|
|
return { success: false, message: "Pasien tidak ditemukan" };
|
|
}
|
|
|
|
switch (action) {
|
|
case "check-in":
|
|
// Jika check-in di loket, pindahkan ke klinik dengan status di-loket
|
|
if (adminType === 'loket') {
|
|
allPatients.value[patientIndex] = {
|
|
...allPatients.value[patientIndex],
|
|
status: "di-loket",
|
|
processStage: "klinik"
|
|
};
|
|
message = `Pasien ${patientCode} berhasil check in dan masuk ke Tabel Loket Klinik`;
|
|
}
|
|
// Jika check-in di klinik, selesai
|
|
else if (adminType === 'klinik') {
|
|
allPatients.value[patientIndex] = {
|
|
...allPatients.value[patientIndex],
|
|
status: "processed"
|
|
};
|
|
message = `Pasien ${patientCode} berhasil check in di Klinik`;
|
|
}
|
|
// Jika check-in di penunjang, selesai
|
|
else if (adminType === 'penunjang') {
|
|
allPatients.value[patientIndex] = {
|
|
...allPatients.value[patientIndex],
|
|
status: "processed"
|
|
};
|
|
message = `Pasien ${patientCode} berhasil check in di Penunjang`;
|
|
}
|
|
|
|
// Clear current processing di admin yang melakukan check-in
|
|
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
|
|
currentProcessingPatient.value[adminType] = null;
|
|
}
|
|
break;
|
|
|
|
case "terlambat":
|
|
allPatients.value[patientIndex] = {
|
|
...allPatients.value[patientIndex],
|
|
status: "terlambat"
|
|
};
|
|
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
|
|
currentProcessingPatient.value[adminType] = null;
|
|
}
|
|
message = `Pasien ${patientCode} ditandai terlambat`;
|
|
break;
|
|
|
|
case "pending":
|
|
allPatients.value[patientIndex] = {
|
|
...allPatients.value[patientIndex],
|
|
status: "pending"
|
|
};
|
|
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
|
|
currentProcessingPatient.value[adminType] = null;
|
|
}
|
|
message = `Pasien ${patientCode} di-pending`;
|
|
break;
|
|
|
|
case "aktifkan":
|
|
const currentStatus = allPatients.value[patientIndex].status;
|
|
if (currentStatus === "terlambat" || currentStatus === "pending") {
|
|
// PERBAIKAN: Update dengan cara yang Vue reactive
|
|
allPatients.value[patientIndex] = {
|
|
...allPatients.value[patientIndex],
|
|
status: "di-loket"
|
|
};
|
|
message = `Pasien ${patientCode} diaktifkan kembali dan masuk ke tabel Di Loket`;
|
|
} else {
|
|
message = `Pasien ${patientCode} tidak dapat diaktifkan (status: ${currentStatus})`;
|
|
}
|
|
break;
|
|
|
|
case "proses":
|
|
// Ambil data terbaru dari array
|
|
currentProcessingPatient.value[adminType] = allPatients.value[patientIndex];
|
|
message = `Memproses pasien ${patientCode}`;
|
|
break;
|
|
}
|
|
|
|
return { success: true, message };
|
|
};
|
|
|
|
const processNextQueue = (adminType = 'loket') => {
|
|
const stageMap = {
|
|
'loket': 'loket',
|
|
'klinik': 'klinik',
|
|
'penunjang': 'penunjang'
|
|
};
|
|
|
|
const targetStage = stageMap[adminType];
|
|
const nextPatient = allPatients.value.find(p =>
|
|
p.status === 'di-loket' && p.processStage === targetStage
|
|
);
|
|
|
|
if (!nextPatient) {
|
|
return { success: false, message: "Tidak ada pasien di loket yang dapat diproses" };
|
|
}
|
|
|
|
// Set sebagai current processing patient
|
|
currentProcessingPatient.value[adminType] = nextPatient;
|
|
|
|
return {
|
|
success: true,
|
|
message: `Memproses pasien ${nextPatient.noAntrian.split(" |")[0]}`,
|
|
};
|
|
};
|
|
|
|
const createAntreanKlinik = (klinik, patient = null, adminType = 'loket') => {
|
|
const newNo = allPatients.value.length + 1;
|
|
const timestamp = new Date();
|
|
const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`;
|
|
|
|
const newPatient = {
|
|
no: newNo,
|
|
jamPanggil: `${String(timestamp.getHours()).padStart(2, "0")}:${String(
|
|
timestamp.getMinutes()
|
|
).padStart(2, "0")}`,
|
|
barcode: barcode,
|
|
noAntrian: `KL${String(newNo).padStart(4, "0")} | Klinik - ${barcode}`,
|
|
shift: "Shift 1",
|
|
klinik: klinik.name,
|
|
fastTrack: "TIDAK",
|
|
pembayaran: patient ? patient.pembayaran : "UMUM",
|
|
status: "di-loket",
|
|
processStage: "klinik",
|
|
createdAt: timestamp.toISOString(),
|
|
referencePatient: patient ? patient.noAntrian : null,
|
|
};
|
|
|
|
allPatients.value.push(newPatient);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Antrean klinik ${klinik.name} berhasil dibuat dan masuk ke Tabel Loket Klinik`,
|
|
patient: newPatient,
|
|
};
|
|
};
|
|
|
|
const createAntreanPenunjang = (penunjang, patient = null, adminType = 'loket') => {
|
|
const newNo = allPatients.value.length + 1;
|
|
const timestamp = new Date();
|
|
const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`;
|
|
|
|
const newPatient = {
|
|
no: newNo,
|
|
jamPanggil: `${String(timestamp.getHours()).padStart(2, "0")}:${String(
|
|
timestamp.getMinutes()
|
|
).padStart(2, "0")}`,
|
|
barcode: barcode,
|
|
noAntrian: `PN${String(newNo).padStart(4, "0")} | Penunjang - ${barcode}`,
|
|
shift: "Shift 1",
|
|
klinik: penunjang.name,
|
|
fastTrack: "TIDAK",
|
|
pembayaran: patient ? patient.pembayaran : "UMUM",
|
|
status: "di-loket",
|
|
processStage: "penunjang",
|
|
createdAt: timestamp.toISOString(),
|
|
referencePatient: patient ? patient.noAntrian : null,
|
|
};
|
|
|
|
allPatients.value.push(newPatient);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Antrean penunjang ${penunjang.name} berhasil dibuat dan masuk ke Tabel Loket Penunjang`,
|
|
patient: newPatient,
|
|
};
|
|
};
|
|
|
|
const changeKlinik = (patient, newKlinik, adminType = 'loket') => {
|
|
const patientIndex = allPatients.value.findIndex((p) => p.no === patient.no);
|
|
|
|
if (patientIndex !== -1) {
|
|
// Update dengan cara yang Vue reactive
|
|
allPatients.value[patientIndex] = {
|
|
...allPatients.value[patientIndex],
|
|
klinik: newKlinik.name
|
|
};
|
|
|
|
// Update current processing if it's the same patient
|
|
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
|
|
currentProcessingPatient.value[adminType] = {
|
|
...currentProcessingPatient.value[adminType],
|
|
klinik: newKlinik.name
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
message: `Klinik berhasil diubah ke ${newKlinik.name}`,
|
|
};
|
|
}
|
|
|
|
return { success: false, message: "Pasien tidak ditemukan" };
|
|
};
|
|
|
|
const getCurrentProcessing = (adminType) => {
|
|
return computed(() => currentProcessingPatient.value[adminType]);
|
|
};
|
|
|
|
const setCurrentProcessing = (patient, adminType) => {
|
|
currentProcessingPatient.value[adminType] = patient;
|
|
};
|
|
|
|
return {
|
|
// State
|
|
allPatients,
|
|
quotaUsed,
|
|
currentProcessingPatient,
|
|
kliniks,
|
|
penunjangs,
|
|
|
|
// Computed
|
|
totalPasien,
|
|
|
|
// Actions
|
|
callNext,
|
|
callMultiplePatients,
|
|
processPatient,
|
|
createAntreanKlinik,
|
|
createAntreanPenunjang,
|
|
changeKlinik,
|
|
processNextQueue,
|
|
getPatientsByStage,
|
|
getTotalPasienByStage,
|
|
getCurrentProcessing,
|
|
setCurrentProcessing,
|
|
};
|
|
}); |