348 lines
12 KiB
JavaScript
348 lines
12 KiB
JavaScript
// composables/useQueue.js
|
|
import { ref, computed } from "vue";
|
|
import { useQueueStore } from "../stores/queueStore";
|
|
import { useLoketStore } from "../stores/loketStore";
|
|
|
|
export const useQueue = (adminType = "loket", specificId = null) => {
|
|
const queueStore = useQueueStore();
|
|
const loketStore = useLoketStore();
|
|
|
|
// Normalize specificId to a reactive value
|
|
const idValue = computed(() => {
|
|
if (typeof specificId === 'function') return specificId();
|
|
if (specificId && typeof specificId === 'object' && 'value' in specificId) return specificId.value;
|
|
return specificId;
|
|
});
|
|
|
|
// Local state
|
|
const snackbar = ref(false);
|
|
const snackbarText = ref("");
|
|
const snackbarColor = ref("success");
|
|
|
|
// Dialog states
|
|
const showKlinikDialog = ref(false);
|
|
const showPenunjangDialog = ref(false);
|
|
const showChangeKlinikDialog = ref(false);
|
|
const klinikSearch = ref("");
|
|
const penunjangSearch = ref("");
|
|
const changeKlinikSearch = ref("");
|
|
const selectedPatientForPenunjang = ref(null);
|
|
|
|
// FIXED: Get stage patients once, then derive others from it
|
|
const stagePatients = computed(() => {
|
|
// Don't use .value here - getPatientsByStage returns a computed already
|
|
const patients = queueStore.getPatientsByStage(adminType);
|
|
return patients.value;
|
|
});
|
|
|
|
// Computed from store - isolated by specificId if provided
|
|
const currentProcessingPatient = computed(() => {
|
|
const key = idValue.value ? `${adminType}-${idValue.value}` : adminType;
|
|
return queueStore.currentProcessingPatient[key];
|
|
});
|
|
|
|
// Derive from stagePatients - ADD DEBUG LOGS
|
|
const diLoketPatients = computed(() => {
|
|
const patients = stagePatients.value.diLoket || [];
|
|
console.log('🔍 useQueue - diLoketPatients:', patients.length);
|
|
if (patients.length > 0) {
|
|
console.log('🔍 First diLoket patient:', patients[0]);
|
|
console.log('🔍 First diLoket patient fastTrack:', patients[0].fastTrack);
|
|
}
|
|
return patients;
|
|
});
|
|
|
|
const terlambatPatients = computed(() => {
|
|
const patients = stagePatients.value.terlambat || [];
|
|
console.log('🔍 useQueue - terlambatPatients:', patients.length);
|
|
return patients;
|
|
});
|
|
|
|
const pendingPatients = computed(() => {
|
|
const patients = stagePatients.value.pending || [];
|
|
console.log('🔍 useQueue - pendingPatients:', patients.length);
|
|
return patients;
|
|
});
|
|
|
|
const anjunganPatients = computed(() => stagePatients.value.anjungan || []);
|
|
|
|
// Pasien yang belum dipanggil (status "menunggu")
|
|
const menungguPatients = computed(() => stagePatients.value.menunggu || []);
|
|
|
|
const nextPatient = computed(() => {
|
|
// Prioritaskan pasien menunggu, baru anjungan
|
|
return menungguPatients.value[0] || anjunganPatients.value[0] || null;
|
|
});
|
|
|
|
// Total pasien hanya untuk stage admin ini
|
|
const totalPasien = computed(() => {
|
|
const total = queueStore.getTotalPasienByStage(adminType);
|
|
return total.value;
|
|
});
|
|
|
|
// Helper to check if a patient is relevant to the current admin view
|
|
const isPatientRelevant = (p) => {
|
|
if (p.processStage !== adminType) return false;
|
|
const targetId = idValue.value;
|
|
if (!targetId) return true; // No specific ID, show all for this stage
|
|
|
|
if (adminType === 'loket') {
|
|
const thisLoket = loketStore.getLoketById(parseInt(targetId));
|
|
if (!thisLoket) return String(p.loketId) === String(targetId);
|
|
|
|
// Must be assigned to this loket OR unassigned but clinic is served by this loket
|
|
const isAssignedToThis = p.loketId && String(p.loketId) === String(targetId);
|
|
if (isAssignedToThis) return true;
|
|
|
|
const isServedByThis = !p.loketId && thisLoket.pelayanan && Array.isArray(thisLoket.pelayanan) && thisLoket.pelayanan.includes(p.kodeKlinik);
|
|
|
|
return isServedByThis;
|
|
}
|
|
|
|
if (adminType === 'klinik') {
|
|
// Filter by clinic code or clinic name
|
|
return String(p.kodeKlinik) === String(targetId) || String(p.klinik) === String(targetId);
|
|
}
|
|
|
|
if (adminType === 'penunjang') {
|
|
// Penunjang usually uses clinic name as the identifier in seeds
|
|
return String(p.klinik) === String(targetId) || String(p.kodeKlinik) === String(targetId);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// 1. Menunggu Count (Backlog ONLY - Strictly status 'menunggu')
|
|
const menungguCount = computed(() => {
|
|
let list = menungguPatients.value || [];
|
|
return list.filter(isPatientRelevant).length;
|
|
});
|
|
|
|
// 2. Calculated Quota Used (Dynamic - any ticket NOT 'menunggu' counts as used)
|
|
const calculatedQuotaUsed = computed(() => {
|
|
const list = queueStore.allPatients || [];
|
|
return list.filter(p => {
|
|
if (!isPatientRelevant(p)) return false;
|
|
// Status is NOT 'menunggu' (meaning it's being served or finished)
|
|
return p.status !== 'menunggu';
|
|
}).length;
|
|
});
|
|
|
|
const quotaUsed = computed(() => calculatedQuotaUsed.value);
|
|
|
|
// Expose dev helper to refresh seed data
|
|
const resetPatients = () => queueStore.resetPatients();
|
|
|
|
// Filtered lists
|
|
const filteredKliniks = computed(() => {
|
|
let result = (queueStore.kliniks.value || queueStore.kliniks || []).filter(k => k.jenisLayanan === 'Reguler');
|
|
if (klinikSearch.value) {
|
|
result = result.filter((k) =>
|
|
k.name.toLowerCase().includes(klinikSearch.value.toLowerCase())
|
|
);
|
|
}
|
|
// Sort alphabetically by name
|
|
return [...result].sort((a, b) => (a.name || "").localeCompare(b.name || ""));
|
|
});
|
|
|
|
const filteredPenunjangs = computed(() => {
|
|
let result = queueStore.penunjangs;
|
|
if (penunjangSearch.value) {
|
|
result = result.filter((p) =>
|
|
p.name.toLowerCase().includes(penunjangSearch.value.toLowerCase())
|
|
);
|
|
}
|
|
// Sort alphabetically by name
|
|
return [...result].sort((a, b) => (a.name || "").localeCompare(b.name || ""));
|
|
});
|
|
|
|
const filteredChangeKliniks = computed(() => {
|
|
let result = (queueStore.kliniks.value || queueStore.kliniks || []).filter(k => k.jenisLayanan === 'Reguler');
|
|
if (changeKlinikSearch.value) {
|
|
result = result.filter((k) =>
|
|
k.name.toLowerCase().includes(changeKlinikSearch.value.toLowerCase())
|
|
);
|
|
}
|
|
// Sort alphabetically by name
|
|
return [...result].sort((a, b) => (a.name || "").localeCompare(b.name || ""));
|
|
});
|
|
|
|
// Methods
|
|
const showSnackbar = (text, color = "success") => {
|
|
snackbarText.value = text;
|
|
snackbarColor.value = color;
|
|
snackbar.value = true;
|
|
};
|
|
|
|
const callNext = (specificIdOverride = null) => {
|
|
const targetId = specificIdOverride || idValue.value;
|
|
const result = queueStore.callNext(adminType, targetId);
|
|
showSnackbar(result.message, result.success ? "success" : "warning");
|
|
};
|
|
|
|
const callMultiplePatients = (count, specificIdOverride = null) => {
|
|
const targetId = specificIdOverride || idValue.value;
|
|
const result = queueStore.callMultiplePatients(count, adminType, targetId);
|
|
showSnackbar(result.message, result.success ? "success" : "warning");
|
|
};
|
|
|
|
const processPatient = (patient, action) => {
|
|
const result = queueStore.processPatient(patient, action, adminType, idValue.value);
|
|
|
|
let color = "success";
|
|
if (action === "terlambat") color = "warning";
|
|
else if (action === "pending") color = "info";
|
|
|
|
showSnackbar(result.message, color);
|
|
};
|
|
|
|
// Helper function untuk mendapatkan pasien yang sedang diproses dari store
|
|
const getCurrentProcessingPatientFromStore = () => {
|
|
try {
|
|
const key = idValue.value ? `${adminType}-${idValue.value}` : adminType;
|
|
const processingPatient = queueStore.currentProcessingPatient?.[key];
|
|
if (!processingPatient) return null;
|
|
|
|
// Dapatkan data terbaru dari allPatients langsung (bukan dari getPatientsByStage yang sudah difilter)
|
|
// allPatients adalah ref yang di-export dari Pinia store, bisa diakses langsung atau dengan .value
|
|
// Coba akses langsung dulu, jika undefined baru coba dengan .value
|
|
let allPatients = [];
|
|
if (queueStore.allPatients) {
|
|
// Jika allPatients adalah ref (punya .value)
|
|
allPatients = Array.isArray(queueStore.allPatients)
|
|
? queueStore.allPatients
|
|
: (queueStore.allPatients.value || []);
|
|
}
|
|
|
|
// Cari pasien berdasarkan no, barcode, atau noAntrian
|
|
const latestPatient = allPatients.find(
|
|
p => (p && p.no === processingPatient.no) ||
|
|
(p && p.barcode && p.barcode === processingPatient.barcode) ||
|
|
(p && p.noAntrian && p.noAntrian === processingPatient.noAntrian)
|
|
);
|
|
|
|
// Jika ditemukan, return data terbaru, jika tidak return data dari currentProcessingPatient
|
|
return latestPatient || processingPatient;
|
|
} catch (error) {
|
|
console.error('Error in getCurrentProcessingPatientFromStore:', error);
|
|
// Fallback: return processingPatient dari store jika ada
|
|
return queueStore.currentProcessingPatient?.[adminType] || null;
|
|
}
|
|
};
|
|
|
|
const selectKlinik = (klinik) => {
|
|
// Pastikan currentProcessingPatient valid, jika tidak, coba dapatkan dari store
|
|
let patient = currentProcessingPatient.value || getCurrentProcessingPatientFromStore();
|
|
|
|
if (!patient) {
|
|
showSnackbar("Tidak ada pasien yang sedang diproses", "error");
|
|
showKlinikDialog.value = false;
|
|
return;
|
|
}
|
|
|
|
const result = queueStore.createAntreanKlinik(klinik, patient, adminType);
|
|
showSnackbar(result.message, "success");
|
|
showKlinikDialog.value = false;
|
|
};
|
|
|
|
const selectPenunjang = (penunjang) => {
|
|
// Pastikan currentProcessingPatient valid, jika tidak, coba dapatkan dari store
|
|
let patient = currentProcessingPatient.value || getCurrentProcessingPatientFromStore();
|
|
|
|
if (!patient) {
|
|
showSnackbar("Tidak ada pasien yang sedang diproses", "error");
|
|
showPenunjangDialog.value = false;
|
|
selectedPatientForPenunjang.value = null;
|
|
return;
|
|
}
|
|
|
|
const result = queueStore.createAntreanPenunjang(
|
|
penunjang,
|
|
patient,
|
|
adminType
|
|
);
|
|
showSnackbar(result.message, "success");
|
|
showPenunjangDialog.value = false;
|
|
selectedPatientForPenunjang.value = null;
|
|
};
|
|
|
|
const openPenunjangDialog = (patient = null) => {
|
|
selectedPatientForPenunjang.value = patient;
|
|
showPenunjangDialog.value = true;
|
|
};
|
|
|
|
const changeKlinik = (klinik) => {
|
|
// Pastikan currentProcessingPatient valid, jika tidak, coba dapatkan dari store
|
|
let patient = currentProcessingPatient.value || getCurrentProcessingPatientFromStore();
|
|
|
|
if (!patient) {
|
|
showSnackbar("Tidak ada pasien yang sedang diproses", "error");
|
|
showChangeKlinikDialog.value = false;
|
|
return { success: false };
|
|
}
|
|
|
|
const result = queueStore.changeKlinik(
|
|
patient,
|
|
klinik,
|
|
adminType,
|
|
idValue.value
|
|
);
|
|
showSnackbar(result.message, result.success ? "success" : "error");
|
|
showChangeKlinikDialog.value = false;
|
|
return result;
|
|
};
|
|
|
|
|
|
const processNextQueue = () => {
|
|
const result = queueStore.processNextQueue(adminType, idValue.value);
|
|
showSnackbar(result.message, result.success ? "success" : "warning");
|
|
};
|
|
|
|
const getRowClass = (item) => {
|
|
if (item.status === "current") {
|
|
return "text-success font-weight-bold";
|
|
}
|
|
return "";
|
|
};
|
|
|
|
return {
|
|
// State
|
|
snackbar,
|
|
snackbarText,
|
|
snackbarColor,
|
|
showKlinikDialog,
|
|
showPenunjangDialog,
|
|
showChangeKlinikDialog,
|
|
klinikSearch,
|
|
penunjangSearch,
|
|
changeKlinikSearch,
|
|
selectedPatientForPenunjang,
|
|
|
|
// Computed
|
|
currentProcessingPatient,
|
|
diLoketPatients,
|
|
terlambatPatients,
|
|
pendingPatients,
|
|
anjunganPatients,
|
|
menungguPatients,
|
|
nextPatient,
|
|
totalPasien,
|
|
quotaUsed,
|
|
filteredKliniks,
|
|
filteredPenunjangs,
|
|
filteredChangeKliniks,
|
|
stagePatients,
|
|
|
|
// Methods
|
|
showSnackbar,
|
|
callNext,
|
|
callMultiplePatients,
|
|
processPatient,
|
|
selectKlinik,
|
|
selectPenunjang,
|
|
openPenunjangDialog,
|
|
changeKlinik,
|
|
processNextQueue,
|
|
getRowClass,
|
|
};
|
|
}; |