update print adminloket, logika klinik ruang, numbering antrian baru klinik ruang
This commit is contained in:
@@ -5,6 +5,8 @@ export interface ThermalPrintData {
|
||||
noAntrian: string;
|
||||
barcode: string;
|
||||
klinik: string;
|
||||
ruang?: string;
|
||||
nomorRuang?: string;
|
||||
shift: string;
|
||||
pembayaran: string;
|
||||
tanggal: string;
|
||||
@@ -103,6 +105,21 @@ export const useThermalPrint = () => {
|
||||
|
||||
// Format nomor antrian (hilangkan bagian "| Onsite - barcode")
|
||||
const noAntrianDisplay = data.noAntrian.split(' |')[0];
|
||||
|
||||
// Format informasi ruang: "Poli Anak - Ruang A"
|
||||
let ruangInfo = '';
|
||||
if (data.klinik && data.nomorRuang) {
|
||||
// Konversi nomor ruang ke abjad (1 = A, 2 = B, 3 = C, dst)
|
||||
const ruangNumber = parseInt(data.nomorRuang) || 1;
|
||||
const ruangLetter = String.fromCharCode(64 + ruangNumber); // 64 = '@', 65 = 'A', 66 = 'B', dst
|
||||
ruangInfo = `${data.klinik} - Ruang ${ruangLetter}`;
|
||||
} else if (data.klinik && data.ruang) {
|
||||
// Fallback jika hanya ada nama ruang
|
||||
ruangInfo = `${data.klinik} - ${data.ruang}`;
|
||||
} else if (data.klinik) {
|
||||
// Hanya klinik jika tidak ada info ruang
|
||||
ruangInfo = data.klinik;
|
||||
}
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
@@ -333,7 +350,7 @@ export const useThermalPrint = () => {
|
||||
|
||||
<div class="no-antrian">${noAntrianDisplay}</div>
|
||||
|
||||
<div class="klinik-name">${data.klinik}</div>
|
||||
${ruangInfo ? `<div class="klinik-name">${ruangInfo}</div>` : `<div class="klinik-name">${data.klinik}</div>`}
|
||||
|
||||
<div class="qr-code-section">
|
||||
<div class="qr-code">
|
||||
@@ -502,6 +519,8 @@ export const useThermalPrint = () => {
|
||||
noAntrian: patient.noAntrian || '',
|
||||
barcode: patient.barcode || '',
|
||||
klinik: patient.klinik || '',
|
||||
ruang: patient.ruang || undefined,
|
||||
nomorRuang: patient.nomorRuang || undefined,
|
||||
shift: patient.shift || 'Shift 1',
|
||||
pembayaran: patient.pembayaran || '',
|
||||
tanggal: tanggalKunjungan,
|
||||
|
||||
@@ -9,31 +9,6 @@
|
||||
theme="warning"
|
||||
/>
|
||||
|
||||
<!-- Generate Ticket Section -->
|
||||
<v-card class="generate-ticket-card mb-4" elevation="0">
|
||||
<v-card-text class="pa-4">
|
||||
<div class="section-label mb-3">GENERATE TIKET</div>
|
||||
<v-row dense align="center">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="barcodeInput"
|
||||
placeholder="Masukan Barcode"
|
||||
density="compact"
|
||||
hide-details
|
||||
prepend-inner-icon="mdi-barcode-scan"
|
||||
@keyup.enter="handleScanBarcode"
|
||||
autofocus
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<div class="instruction-text">
|
||||
Tekan Enter. (Apabila barcode depan nomor ada huruf lain, Ex: J200730100005 "Hiraukan huruf 'j' nya")
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Rooms Grid -->
|
||||
<div class="rooms-grid">
|
||||
<v-card
|
||||
@@ -58,7 +33,7 @@
|
||||
<div class="section-label-small mb-2">SEDANG DIPROSES</div>
|
||||
<div class="current-patient-info">
|
||||
<div class="patient-number-large">
|
||||
{{ getCurrentProcessingForRoom(ruang)?.noAntrian.split(" |")[0] }}
|
||||
{{ getCurrentProcessingForRoom(ruang)?.noAntrian?.split(" |")[0] || '-' }}
|
||||
</div>
|
||||
<div class="patient-details">
|
||||
<div class="action-buttons mt-3">
|
||||
@@ -113,24 +88,56 @@
|
||||
<div class="empty-text">Tidak ada pasien yang diproses</div>
|
||||
</div>
|
||||
|
||||
<!-- Pending Patients (Belum Generate Tiket) -->
|
||||
<div v-if="getPendingPatientsForRoom(ruang).length > 0" class="pending-section">
|
||||
<!-- Daftar Pasien -->
|
||||
<div v-if="getAllPatientsForRoom(ruang).length > 0" class="patient-list-section">
|
||||
<div class="section-label-small mb-2">
|
||||
DAFTAR PASIEN PERLU GENERATE TIKET
|
||||
DAFTAR PASIEN
|
||||
<v-chip size="x-small" color="warning" class="ml-2">
|
||||
{{ getPendingPatientsForRoom(ruang).length }}
|
||||
{{ getAllPatientsForRoom(ruang).length }}
|
||||
</v-chip>
|
||||
</div>
|
||||
<div class="pending-queue-list">
|
||||
<div class="patient-queue-list">
|
||||
<div
|
||||
v-for="patient in paginatedPendingPatients(ruang)"
|
||||
v-for="patient in paginatedAllPatients(ruang)"
|
||||
:key="patient.no"
|
||||
class="pending-queue-item"
|
||||
:class="getPatientCardClass(patient)"
|
||||
>
|
||||
<div class="pending-queue-number">
|
||||
{{ patient.noAntrian.split(" |")[0] }}
|
||||
<div class="patient-queue-number">
|
||||
{{ patient.noAntrian?.split(" |")[0] || patient.barcode || '-' }}
|
||||
</div>
|
||||
<div class="pending-queue-actions">
|
||||
<div class="patient-queue-info">
|
||||
<!-- Penanda Panggilan Pemeriksaan Awal -->
|
||||
<v-chip
|
||||
v-if="patient.calledPemeriksaanAwal"
|
||||
size="x-small"
|
||||
color="primary"
|
||||
class="mr-1"
|
||||
>
|
||||
<v-icon start size="12">mdi-check-circle</v-icon>
|
||||
Pemeriksaan Awal
|
||||
</v-chip>
|
||||
<!-- Penanda Panggilan Tindakan -->
|
||||
<v-chip
|
||||
v-if="patient.calledTindakan"
|
||||
size="x-small"
|
||||
color="secondary"
|
||||
class="mr-1"
|
||||
>
|
||||
<v-icon start size="12">mdi-check-circle</v-icon>
|
||||
Tindakan
|
||||
</v-chip>
|
||||
<!-- Penanda Status Pending -->
|
||||
<v-chip
|
||||
v-if="patient.status === 'pending'"
|
||||
size="x-small"
|
||||
color="error"
|
||||
class="mr-1"
|
||||
>
|
||||
<v-icon start size="12">mdi-pause-circle</v-icon>
|
||||
Pending
|
||||
</v-chip>
|
||||
</div>
|
||||
<div class="patient-queue-actions">
|
||||
<v-btn
|
||||
icon
|
||||
size="x-small"
|
||||
@@ -151,35 +158,23 @@
|
||||
<v-tooltip activator="parent">Pindah Ruang</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="patient.status === 'pending'"
|
||||
size="x-small"
|
||||
variant="flat"
|
||||
color="primary"
|
||||
class="text-white"
|
||||
@click="handleProcessPendingPatient(ruang, patient)"
|
||||
@click="patient.status === 'pending' ? handleProcessPendingPatient(ruang, patient) : handleProcessPatient(ruang, patient)"
|
||||
>
|
||||
<v-icon start size="14">mdi-play</v-icon>
|
||||
Proses
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
size="x-small"
|
||||
variant="flat"
|
||||
color="primary"
|
||||
class="text-white"
|
||||
@click="handleGenerateTicket(patient, ruang)"
|
||||
>
|
||||
<v-icon start size="14">mdi-ticket</v-icon>
|
||||
Generate
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pagination -->
|
||||
<v-pagination
|
||||
v-if="getPendingPatientsForRoom(ruang).length > 10"
|
||||
v-if="getAllPatientsForRoom(ruang).length > 10"
|
||||
v-model="pendingPage[ruang.nomorRuang]"
|
||||
:length="Math.ceil(getPendingPatientsForRoom(ruang).length / 10)"
|
||||
:length="Math.ceil(getAllPatientsForRoom(ruang).length / 10)"
|
||||
:total-visible="5"
|
||||
size="small"
|
||||
class="mt-3"
|
||||
@@ -190,48 +185,6 @@
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<!-- Dialog Select Ruang -->
|
||||
<v-dialog v-model="showRuangDialog" max-width="500px" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="dialog-header">
|
||||
<span>Pilih Ruang</span>
|
||||
<v-btn icon variant="text" size="small" @click="showRuangDialog = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text class="pa-4">
|
||||
<div class="mb-3">
|
||||
<div class="body-2 text-semibold mb-2">Pasien: {{ scannedPatient?.noAntrian?.split(" |")[0] }}</div>
|
||||
<div class="caption-2 text-muted">{{ scannedPatient?.barcode }}</div>
|
||||
</div>
|
||||
<v-radio-group v-model="selectedRuang" class="mt-0">
|
||||
<v-radio
|
||||
v-for="ruang in ruangList"
|
||||
:key="ruang.nomorRuang"
|
||||
:value="ruang"
|
||||
:label="`${ruang.namaRuang} (R.${ruang.nomorRuang})`"
|
||||
/>
|
||||
</v-radio-group>
|
||||
<v-select
|
||||
v-model="selectedTipeLayanan"
|
||||
:items="['Pemeriksaan Awal', 'Tindakan']"
|
||||
label="Tipe Layanan"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
class="mt-4"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="showRuangDialog = false">Batal</v-btn>
|
||||
<v-btn color="primary" variant="flat" :disabled="!selectedRuang || !selectedTipeLayanan" @click="confirmCreateAntrean">
|
||||
Buat Antrean
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Dialog Pindah Ruang -->
|
||||
<v-dialog v-model="showPindahRuangDialog" max-width="500px" persistent>
|
||||
<v-card>
|
||||
@@ -403,14 +356,9 @@ const currentDate = ref(
|
||||
})
|
||||
);
|
||||
|
||||
const barcodeInput = ref('');
|
||||
const snackbar = ref(false);
|
||||
const snackbarText = ref('');
|
||||
const snackbarColor = ref('success');
|
||||
const showRuangDialog = ref(false);
|
||||
const scannedPatient = ref(null);
|
||||
const selectedRuang = ref(null);
|
||||
const selectedTipeLayanan = ref('Pemeriksaan Awal');
|
||||
const showDetailDialog = ref(false);
|
||||
const selectedPatientDetail = ref(null);
|
||||
const pendingPage = ref({});
|
||||
@@ -424,105 +372,29 @@ const showSnackbar = (message, color = 'success') => {
|
||||
snackbar.value = true;
|
||||
};
|
||||
|
||||
const handleScanBarcode = () => {
|
||||
if (!barcodeInput.value.trim()) {
|
||||
showSnackbar('Masukkan barcode/nomor antrian', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find patient from queueStore
|
||||
const cleanInput = String(barcodeInput.value).trim().toUpperCase();
|
||||
const numericInput = cleanInput.replace(/^[A-Z]+/, '');
|
||||
|
||||
const patient = queueStore.allPatients.find(p => {
|
||||
if (p.barcode === cleanInput || p.barcode === numericInput) return true;
|
||||
const noAntrianUpper = (p.noAntrian || '').toUpperCase();
|
||||
if (noAntrianUpper.includes(cleanInput) || noAntrianUpper.includes(numericInput)) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!patient) {
|
||||
showSnackbar('Pasien tidak ditemukan', 'error');
|
||||
barcodeInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if patient already has antrean klinik ruang
|
||||
const existingAntrean = queueStore.allPatients.find(p =>
|
||||
p.referencePatient === patient.noAntrian &&
|
||||
p.kodeKlinik === klinikData.value?.kodeKlinik &&
|
||||
p.processStage === 'klinik-ruang'
|
||||
);
|
||||
|
||||
if (existingAntrean) {
|
||||
showSnackbar(`Pasien sudah memiliki antrean di ${existingAntrean.ruang}`, 'warning');
|
||||
barcodeInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
scannedPatient.value = patient;
|
||||
selectedRuang.value = null;
|
||||
selectedTipeLayanan.value = 'Pemeriksaan Awal';
|
||||
showRuangDialog.value = true;
|
||||
barcodeInput.value = '';
|
||||
};
|
||||
|
||||
const confirmCreateAntrean = () => {
|
||||
if (!selectedRuang.value || !selectedTipeLayanan.value || !scannedPatient.value) {
|
||||
showSnackbar('Pilih ruang dan tipe layanan', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const klinikRuang = {
|
||||
kodeKlinik: klinikData.value.kodeKlinik,
|
||||
namaKlinik: klinikData.value.namaKlinik
|
||||
};
|
||||
|
||||
const result = queueStore.scanAndCreateAntreanKlinikRuang(
|
||||
scannedPatient.value.barcode,
|
||||
klinikRuang,
|
||||
selectedRuang.value,
|
||||
selectedTipeLayanan.value
|
||||
);
|
||||
|
||||
if (result.success && result.patient) {
|
||||
// Auto proses pasien yang baru digenerate tiket
|
||||
const processResult = queueStore.processPatientKlinikRuang(
|
||||
result.patient,
|
||||
'proses',
|
||||
klinikData.value.kodeKlinik,
|
||||
selectedRuang.value.nomorRuang
|
||||
);
|
||||
showSnackbar(processResult.message, processResult.success ? 'success' : 'error');
|
||||
} else {
|
||||
showSnackbar(result.message, result.success ? 'success' : 'error');
|
||||
}
|
||||
|
||||
showRuangDialog.value = false;
|
||||
scannedPatient.value = null;
|
||||
};
|
||||
|
||||
// Get pending patients (belum generate tiket - dari AdminLoket/AdminKlinik)
|
||||
// Juga include pasien yang sudah digenerate tiket tapi status pending
|
||||
const getPendingPatientsForRoom = (ruang) => {
|
||||
// Get all patients for room (hanya pasien dari processStage 'klinik-ruang')
|
||||
const getAllPatientsForRoom = (ruang) => {
|
||||
return queueStore.allPatients
|
||||
.filter(p =>
|
||||
p.kodeKlinik === klinikData.value?.kodeKlinik &&
|
||||
p.nomorRuang === ruang.nomorRuang &&
|
||||
p.ruang === ruang.namaRuang &&
|
||||
(
|
||||
// Belum generate tiket (belum punya tipeLayanan)
|
||||
(!p.tipeLayanan && (p.processStage === 'klinik' || p.processStage === 'klinik-ruang') &&
|
||||
(p.status === 'waiting' || p.status === 'di-loket' || p.status === 'terlambat')) ||
|
||||
// Sudah generate tiket tapi status pending
|
||||
(p.tipeLayanan && p.processStage === 'klinik-ruang' && p.status === 'pending')
|
||||
)
|
||||
p.processStage === 'klinik-ruang' &&
|
||||
// Exclude pasien yang sedang diproses (current processing)
|
||||
p.no !== getCurrentProcessingForRoom(ruang)?.no &&
|
||||
// Include semua pasien dengan status yang relevan
|
||||
(p.status === 'waiting' || p.status === 'di-loket' || p.status === 'terlambat' || p.status === 'pending')
|
||||
)
|
||||
.sort((a, b) => {
|
||||
// Prioritaskan yang belum digenerate (pending status di akhir)
|
||||
if (!a.tipeLayanan && b.tipeLayanan) return -1;
|
||||
if (a.tipeLayanan && !b.tipeLayanan) return 1;
|
||||
// Prioritaskan yang sudah digenerate/diproses & pending di atas
|
||||
const aHasTiket = !!a.tipeLayanan;
|
||||
const bHasTiket = !!b.tipeLayanan;
|
||||
|
||||
// Jika satu sudah punya tiket dan yang lain belum, yang sudah tiket di atas
|
||||
if (aHasTiket && !bHasTiket) return -1;
|
||||
if (!aHasTiket && bHasTiket) return 1;
|
||||
|
||||
// Jika keduanya sudah punya tiket atau keduanya belum, sort by status
|
||||
const statusPriority = {
|
||||
'di-loket': 1,
|
||||
'waiting': 2,
|
||||
@@ -532,12 +404,18 @@ const getPendingPatientsForRoom = (ruang) => {
|
||||
const priorityDiff = (statusPriority[a.status] || 99) - (statusPriority[b.status] || 99);
|
||||
if (priorityDiff !== 0) return priorityDiff;
|
||||
|
||||
// Sort by time
|
||||
const timeA = a.jamPanggil?.split(':').map(Number) || [0, 0];
|
||||
const timeB = b.jamPanggil?.split(':').map(Number) || [0, 0];
|
||||
return timeA[0] * 60 + timeA[1] - (timeB[0] * 60 + timeB[1]);
|
||||
});
|
||||
};
|
||||
|
||||
// Get pending patients (untuk backward compatibility jika masih digunakan)
|
||||
const getPendingPatientsForRoom = (ruang) => {
|
||||
return getAllPatientsForRoom(ruang).filter(p => !p.tipeLayanan || p.status === 'pending');
|
||||
};
|
||||
|
||||
|
||||
|
||||
const getCurrentProcessingForRoom = (ruang) => {
|
||||
@@ -610,7 +488,7 @@ const handleGenerateTicket = (patient, currentRuang) => {
|
||||
}
|
||||
} else {
|
||||
// Pasien belum punya ruang, isi field dan trigger scan (akan muncul dialog)
|
||||
barcodeInput.value = patient.noAntrian.split(" |")[0] || patient.barcode;
|
||||
barcodeInput.value = patient.noAntrian?.split(" |")[0] || patient.barcode || '';
|
||||
handleScanBarcode();
|
||||
}
|
||||
};
|
||||
@@ -638,16 +516,17 @@ const confirmPindahRuang = () => {
|
||||
|
||||
// Jika pasien sudah punya antrean klinik ruang (sudah digenerate tiket), update antreannya juga
|
||||
if (patient.processStage === 'klinik-ruang' && patient.tipeLayanan) {
|
||||
// Update antrean yang sudah ada
|
||||
queueStore.allPatients[patientIndex] = {
|
||||
...queueStore.allPatients[patientIndex],
|
||||
ruang: selectedRuangBaru.value.namaRuang,
|
||||
nomorRuang: selectedRuangBaru.value.nomorRuang,
|
||||
nomorScreen: selectedRuangBaru.value.nomorScreen,
|
||||
// Update noAntrian untuk reflect ruang baru
|
||||
noAntrian: `${patient.noAntrian.split(" |")[0]} | ${klinikData.value.namaKlinik} - ${selectedRuangBaru.value.namaRuang} - ${patient.tipeLayanan}`,
|
||||
noAntrianRuang: `${klinikData.value.namaKlinik} - ${selectedRuangBaru.value.namaRuang} | ${patient.noAntrian.split(" |")[0]}`
|
||||
};
|
||||
// Update antrean yang sudah ada
|
||||
const baseNoAntrian = patient.noAntrian?.split(" |")[0] || patient.barcode || '';
|
||||
queueStore.allPatients[patientIndex] = {
|
||||
...queueStore.allPatients[patientIndex],
|
||||
ruang: selectedRuangBaru.value.namaRuang,
|
||||
nomorRuang: selectedRuangBaru.value.nomorRuang,
|
||||
nomorScreen: selectedRuangBaru.value.nomorScreen,
|
||||
// Update noAntrian untuk reflect ruang baru
|
||||
noAntrian: `${baseNoAntrian} | ${klinikData.value.namaKlinik} - ${selectedRuangBaru.value.namaRuang} - ${patient.tipeLayanan}`,
|
||||
noAntrianRuang: `${klinikData.value.namaKlinik} - ${selectedRuangBaru.value.namaRuang} | ${baseNoAntrian}`
|
||||
};
|
||||
|
||||
// Clear current processing dari ruang lama jika ada
|
||||
const oldRuang = ruangList.value.find(r => r.nomorRuang === patient.nomorRuang);
|
||||
@@ -668,7 +547,7 @@ const confirmPindahRuang = () => {
|
||||
}
|
||||
|
||||
showSnackbar(
|
||||
`Pasien ${selectedPatientForPindah.value.noAntrian.split(" |")[0]} berhasil dipindah ke ${selectedRuangBaru.value.namaRuang}`,
|
||||
`Pasien ${selectedPatientForPindah.value.noAntrian?.split(" |")[0] || selectedPatientForPindah.value.barcode || '-'} berhasil dipindah ke ${selectedRuangBaru.value.namaRuang}`,
|
||||
'success'
|
||||
);
|
||||
|
||||
@@ -679,27 +558,43 @@ const confirmPindahRuang = () => {
|
||||
|
||||
const handleCallPatientByTipe = (ruang, tipeLayanan) => {
|
||||
const current = getCurrentProcessingForRoom(ruang);
|
||||
if (!current) return;
|
||||
if (!current || !current.no) {
|
||||
showSnackbar('Tidak ada pasien yang sedang diproses', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update tipeLayanan pasien untuk tracking panggilan terakhir
|
||||
const patientIndex = queueStore.allPatients.findIndex(p => p.no === current.no);
|
||||
if (patientIndex !== -1) {
|
||||
// Update status to di-loket untuk ditampilkan di anjungan
|
||||
// Simpan tipeLayanan yang dipanggil untuk display di anjungan
|
||||
queueStore.allPatients[patientIndex] = {
|
||||
...queueStore.allPatients[patientIndex],
|
||||
status: 'di-loket',
|
||||
tipeLayanan: tipeLayanan, // Update tipeLayanan untuk display di anjungan
|
||||
lastCalledAt: new Date().toISOString(),
|
||||
lastCalledTipeLayanan: tipeLayanan // Track tipe layanan terakhir yang dipanggil
|
||||
};
|
||||
|
||||
// Update current processing (tetap 1 pasien, tidak dipisah per tipe)
|
||||
const key = `klinik-ruang-${klinikData.value.kodeKlinik}-${ruang.nomorRuang}`;
|
||||
queueStore.currentProcessingPatient[key] = queueStore.allPatients[patientIndex];
|
||||
if (patientIndex === -1) {
|
||||
showSnackbar('Pasien tidak ditemukan', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showSnackbar(`Memanggil pasien ${current.noAntrian.split(" |")[0]} untuk ${tipeLayanan}`, 'success');
|
||||
// Update tracking panggilan berdasarkan tipeLayanan
|
||||
const updateData = {
|
||||
...queueStore.allPatients[patientIndex],
|
||||
status: 'di-loket',
|
||||
lastCalledAt: new Date().toISOString(),
|
||||
lastCalledTipeLayanan: tipeLayanan
|
||||
};
|
||||
|
||||
// Set penanda panggilan sesuai tipeLayanan
|
||||
if (tipeLayanan === 'Pemeriksaan Awal') {
|
||||
updateData.calledPemeriksaanAwal = true;
|
||||
} else if (tipeLayanan === 'Tindakan') {
|
||||
updateData.calledTindakan = true;
|
||||
}
|
||||
|
||||
queueStore.allPatients[patientIndex] = updateData;
|
||||
|
||||
// Update current processing (tetap 1 pasien, tidak dipisah per tipe)
|
||||
const key = `klinik-ruang-${klinikData.value.kodeKlinik}-${ruang.nomorRuang}`;
|
||||
queueStore.currentProcessingPatient[key] = queueStore.allPatients[patientIndex];
|
||||
|
||||
const patientCode = queueStore.allPatients[patientIndex].noAntrian?.split(" |")[0] ||
|
||||
queueStore.allPatients[patientIndex].barcode ||
|
||||
'-';
|
||||
showSnackbar(`Memanggil pasien ${patientCode} untuk ${tipeLayanan}`, 'success');
|
||||
};
|
||||
|
||||
const showPatientDetail = (patient) => {
|
||||
@@ -758,6 +653,25 @@ const handlePatientAction = (ruang, action) => {
|
||||
showSnackbar(result.message, result.success ? 'success' : 'error');
|
||||
};
|
||||
|
||||
// Handle proses pasien (untuk pasien yang belum diproses)
|
||||
const handleProcessPatient = (ruang, patient) => {
|
||||
const patientIndex = queueStore.allPatients.findIndex(p => p.no === patient.no);
|
||||
if (patientIndex === -1) {
|
||||
showSnackbar('Pasien tidak ditemukan', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set sebagai current processing
|
||||
const result = queueStore.processPatientKlinikRuang(
|
||||
queueStore.allPatients[patientIndex],
|
||||
'proses',
|
||||
klinikData.value.kodeKlinik,
|
||||
ruang.nomorRuang
|
||||
);
|
||||
|
||||
showSnackbar(result.message, result.success ? 'success' : 'error');
|
||||
};
|
||||
|
||||
const handleProcessPendingPatient = (ruang, patient) => {
|
||||
// Proses kembali pasien yang dipending
|
||||
const patientIndex = queueStore.allPatients.findIndex(p => p.no === patient.no);
|
||||
@@ -766,10 +680,10 @@ const handleProcessPendingPatient = (ruang, patient) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update status pasien dari pending menjadi di-loket
|
||||
// Update status pasien dari pending menjadi waiting
|
||||
queueStore.allPatients[patientIndex] = {
|
||||
...queueStore.allPatients[patientIndex],
|
||||
status: 'di-loket'
|
||||
status: 'waiting'
|
||||
};
|
||||
|
||||
// Set sebagai current processing
|
||||
@@ -783,12 +697,23 @@ const handleProcessPendingPatient = (ruang, patient) => {
|
||||
showSnackbar(result.message, result.success ? 'success' : 'error');
|
||||
};
|
||||
|
||||
const paginatedPendingPatients = (ruang) => {
|
||||
const allPending = getPendingPatientsForRoom(ruang);
|
||||
const paginatedAllPatients = (ruang) => {
|
||||
const allPatients = getAllPatientsForRoom(ruang);
|
||||
const page = pendingPage.value[ruang.nomorRuang] || 1;
|
||||
const startIndex = (page - 1) * 10;
|
||||
const endIndex = startIndex + 10;
|
||||
return allPending.slice(startIndex, endIndex);
|
||||
return allPatients.slice(startIndex, endIndex);
|
||||
};
|
||||
|
||||
// Helper untuk menentukan class card berdasarkan status pasien
|
||||
const getPatientCardClass = (patient) => {
|
||||
const baseClass = 'patient-queue-item';
|
||||
// Pasien yang sudah digenerate/diproses & pending: warna berbeda
|
||||
if (patient.tipeLayanan || patient.status === 'pending') {
|
||||
return `${baseClass} patient-queue-item-processed`;
|
||||
}
|
||||
// Pasien yang perlu digenerate: warna default
|
||||
return `${baseClass} patient-queue-item-pending`;
|
||||
};
|
||||
|
||||
|
||||
@@ -981,7 +906,7 @@ onMounted(() => {
|
||||
color: var(--color-neutral-700);
|
||||
}
|
||||
|
||||
.pending-section {
|
||||
.patient-list-section {
|
||||
background: var(--color-warning-50);
|
||||
border: 1px solid var(--color-warning-200);
|
||||
border-radius: 8px;
|
||||
@@ -989,7 +914,7 @@ onMounted(() => {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pending-queue-list {
|
||||
.patient-queue-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
@@ -997,35 +922,59 @@ onMounted(() => {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.pending-queue-item {
|
||||
.patient-queue-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pending-queue-item:hover {
|
||||
/* Pasien yang sudah digenerate/diproses & pending */
|
||||
.patient-queue-item-processed {
|
||||
background: var(--color-primary-50);
|
||||
border: 1px solid var(--color-primary-200);
|
||||
}
|
||||
|
||||
.patient-queue-item-processed:hover {
|
||||
background: var(--color-primary-100);
|
||||
border-color: var(--color-primary-400);
|
||||
}
|
||||
|
||||
/* Pasien yang perlu digenerate */
|
||||
.patient-queue-item-pending {
|
||||
background: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
}
|
||||
|
||||
.patient-queue-item-pending:hover {
|
||||
background: var(--color-warning-100);
|
||||
border-color: var(--color-warning-400);
|
||||
}
|
||||
|
||||
.pending-queue-number {
|
||||
.patient-queue-number {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--color-neutral-900);
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.pending-queue-actions {
|
||||
.patient-queue-info {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.patient-queue-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pending-queue-actions .v-btn {
|
||||
.patient-queue-actions .v-btn {
|
||||
text-transform: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ const klinikRuangList = computed(() => {
|
||||
});
|
||||
|
||||
const navigateToKlinik = (kodeKlinik) => {
|
||||
router.push(`/admin-klinik-ruang/${kodeKlinik}`);
|
||||
router.push(`/adminklinikruang/${kodeKlinik}`);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
+17
-1
@@ -255,9 +255,11 @@ import QueueActionsCard from "@/components/features/queue/QueueActionsCard.vue";
|
||||
import PatientDataTable from "@/components/features/queue/TabelPatientData.vue";
|
||||
import SelectionDialog from "@/components/common/SelectionDialog.vue";
|
||||
import AppSnackbar from "@/components/common/AppSnackbar.vue";
|
||||
import { useThermalPrint } from "@/composables/useThermalPrint";
|
||||
|
||||
const masterStore = useMasterStore();
|
||||
const queueStore = useQueueStore();
|
||||
const { printTicketFromPatient } = useThermalPrint();
|
||||
|
||||
const {
|
||||
snackbar,
|
||||
@@ -446,7 +448,7 @@ const closeKlinikRuangDialog = () => {
|
||||
klinikRuangSearch.value = "";
|
||||
};
|
||||
|
||||
const buatAntreanKlinikRuang = (klinikRuang, ruang) => {
|
||||
const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
|
||||
// Pastikan currentProcessingPatient valid, jika tidak, coba dapatkan dari store
|
||||
let patient = currentProcessingPatient.value;
|
||||
if (!patient) {
|
||||
@@ -491,6 +493,20 @@ const buatAntreanKlinikRuang = (klinikRuang, ruang) => {
|
||||
"loket"
|
||||
);
|
||||
|
||||
if (result.success && result.patient) {
|
||||
// Print ticket dengan nomor antrian baru
|
||||
try {
|
||||
await printTicketFromPatient(result.patient);
|
||||
} catch (error) {
|
||||
console.error('Error printing ticket:', error);
|
||||
snackbarText.value = `${result.message}. Gagal print tiket.`;
|
||||
snackbarColor.value = "warning";
|
||||
snackbar.value = true;
|
||||
closeKlinikRuangDialog();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
snackbarText.value = result.message;
|
||||
snackbarColor.value = result.success ? "success" : "error";
|
||||
snackbar.value = true;
|
||||
|
||||
+34
-5
@@ -570,32 +570,61 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const timestamp = new Date();
|
||||
const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`;
|
||||
|
||||
// Generate nomor antrian baru dengan format: [huruf pertama poli + urutan abjad ruang + nomor antrian ruang]
|
||||
// Contoh: "Anak" ruang 1 = "AA001" (A dari Anak, A dari ruang 1, 001 nomor antrian)
|
||||
|
||||
// 1. Ambil huruf pertama dari nama klinik/poli
|
||||
const firstLetter = klinikRuang.namaKlinik.charAt(0).toUpperCase();
|
||||
|
||||
// 2. Konversi nomor ruang ke abjad (1 = A, 2 = B, 3 = C, dst)
|
||||
const ruangNumber = parseInt(ruang.nomorRuang) || 1;
|
||||
const ruangLetter = String.fromCharCode(64 + ruangNumber); // 64 = '@', 65 = 'A', 66 = 'B', dst
|
||||
|
||||
// 3. Hitung nomor antrian ruang (dimulai dari 1, maksimal 3 digit)
|
||||
const roomQueues = allPatients.value.filter(p =>
|
||||
p.kodeKlinik === klinikRuang.kodeKlinik &&
|
||||
p.nomorRuang === ruang.nomorRuang &&
|
||||
p.processStage === 'klinik-ruang'
|
||||
);
|
||||
const queueNumber = roomQueues.length + 1;
|
||||
const queueNumberStr = String(queueNumber).padStart(3, "0");
|
||||
|
||||
// 4. Format nomor antrian: AA001, AB002, dst
|
||||
const newNoAntrian = `${firstLetter}${ruangLetter}${queueNumberStr}`;
|
||||
|
||||
const newPatient = {
|
||||
no: newNo,
|
||||
jamPanggil: `${String(timestamp.getHours()).padStart(2, "0")}:${String(
|
||||
timestamp.getMinutes()
|
||||
).padStart(2, "0")}`,
|
||||
barcode: barcode,
|
||||
noAntrian: `KR${String(newNo).padStart(4, "0")} | ${klinikRuang.namaKlinik} - Ruang ${ruang.nomorRuang} - ${barcode}`,
|
||||
shift: "Shift 1",
|
||||
noAntrian: `${newNoAntrian} | ${klinikRuang.namaKlinik} - ${ruang.namaRuang}`,
|
||||
noAntrianRuang: `${klinikRuang.namaKlinik} - ${ruang.namaRuang} | ${newNoAntrian}`,
|
||||
shift: patient ? (patient.shift || "Shift 1") : "Shift 1",
|
||||
klinik: klinikRuang.namaKlinik,
|
||||
ruang: ruang.namaRuang,
|
||||
kodeKlinik: klinikRuang.kodeKlinik,
|
||||
nomorRuang: ruang.nomorRuang,
|
||||
nomorScreen: ruang.nomorScreen,
|
||||
fastTrack: "TIDAK",
|
||||
fastTrack: patient ? (patient.fastTrack || "TIDAK") : "TIDAK",
|
||||
pembayaran: patient ? patient.pembayaran : "UMUM",
|
||||
status: "waiting",
|
||||
processStage: "klinik",
|
||||
processStage: "klinik-ruang", // Set ke klinik-ruang langsung
|
||||
createdAt: timestamp.toISOString(),
|
||||
referencePatient: patient ? patient.noAntrian : null,
|
||||
sourcePatientNo: patient ? patient.no : null,
|
||||
// Tracking panggilan
|
||||
calledPemeriksaanAwal: false,
|
||||
calledTindakan: false,
|
||||
lastCalledAt: null,
|
||||
lastCalledTipeLayanan: null,
|
||||
};
|
||||
|
||||
allPatients.value.push(newPatient);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Antrean ${klinikRuang.namaKlinik} Ruang ${ruang.nomorRuang} berhasil dibuat dan akan ditampilkan di layar antrian`,
|
||||
message: `Antrean ${klinikRuang.namaKlinik} Ruang ${ruang.nomorRuang} berhasil dibuat: ${newNoAntrian}`,
|
||||
patient: newPatient,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user