feat: Add admin counter queue page with patient and current patient cards.
This commit is contained in:
@@ -86,23 +86,6 @@
|
||||
<div class="patient-klinik-pembayaran">{{ patient.klinik }} | {{ patient.pembayaran }}</div>
|
||||
</div>
|
||||
|
||||
<!-- NEW: Option to process next even if current is active -->
|
||||
<div class="next-action-shortcut mt-2" v-if="hasNextQueue">
|
||||
<v-btn
|
||||
block
|
||||
variant="tonal"
|
||||
color="primary-600"
|
||||
size="small"
|
||||
class="text-none font-weight-bold"
|
||||
@click="$emit('process-next')"
|
||||
>
|
||||
<v-icon start size="16">mdi-skip-next</v-icon>
|
||||
Panggil Berikutnya
|
||||
</v-btn>
|
||||
<div class="info-text text-center mt-1" style="font-size: 10px; color: var(--color-neutral-600);">
|
||||
(Gunakan jika lupa klik Selesai pada pasien ini)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="create-queue-buttons mt-4">
|
||||
<div class="create-buttons-container">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
:class="{
|
||||
'clickable-card': isClickable,
|
||||
'fast-track-card': isFastTrack && !isCurrentlyProcessing,
|
||||
'processing-card': isCurrentlyProcessing
|
||||
'processing-card': isCurrentlyProcessing || patient.status === 'diproses'
|
||||
}"
|
||||
@click="handleCardClick"
|
||||
v-bind="isClickable ? tooltipProps : {}"
|
||||
|
||||
+211
-2
@@ -221,6 +221,78 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Confirmation Replace Dialog -->
|
||||
<v-dialog v-model="showConfirmReplaceDialog" max-width="500px">
|
||||
<v-card class="dialog-card overflow-hidden rounded-xl">
|
||||
<!-- Solid Blue Header (Matching Detail Pasien Style) -->
|
||||
<v-card-title class="bg-primary-600 text-white py-4 px-6 d-flex justify-between align-center">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon start icon="mdi-alert-circle-outline" class="mr-2"></v-icon>
|
||||
<span class="text-h6 font-weight-bold">Konfirmasi Ganti Pasien</span>
|
||||
</div>
|
||||
<v-btn icon="mdi-close" variant="text" size="small" @click="showConfirmReplaceDialog = false" class="text-white"></v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="dialog-content-premium pa-6">
|
||||
<div class="text-center mb-6 text-uppercase font-weight-bold text-caption text-neutral-600" style="letter-spacing: 1px;">
|
||||
Ganti Antrean Aktif
|
||||
</div>
|
||||
|
||||
<div class="comparison-container mb-6">
|
||||
<!-- Current Patient -->
|
||||
<div class="comparison-item current">
|
||||
<div class="item-label">DARI (AKTIF)</div>
|
||||
<div class="item-card">
|
||||
<div class="item-number text-danger-700">{{ currentProcessingPatient?.noAntrian?.split(" |")[0] || '-' }}</div>
|
||||
<div class="item-status">Sedang Diproses</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-arrow">
|
||||
<v-icon size="24" color="neutral-600">mdi-chevron-double-right</v-icon>
|
||||
</div>
|
||||
|
||||
<!-- New Patient -->
|
||||
<div class="comparison-item target">
|
||||
<div class="item-label">KE (BARU)</div>
|
||||
<div class="item-card">
|
||||
<div class="item-number text-primary-600">{{ pendingReplaceItem ? pendingReplaceItem.noAntrian.split(" |")[0] : (nextPatient ? nextPatient.noAntrian.split(" |")[0] : 'Berikutnya') }}</div>
|
||||
<div class="item-status">Siap Diproses</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="confirm-text text-center px-4">
|
||||
Apakah anda yakin ingin mengganti antrian yang aktif saat ini?
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions class="pa-4 bg-light justify-center">
|
||||
<v-btn
|
||||
variant="flat"
|
||||
color="neutral-200"
|
||||
@click="showConfirmReplaceDialog = false"
|
||||
class="px-8 font-weight-bold rounded-lg"
|
||||
size="large"
|
||||
>
|
||||
Batal
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary-600"
|
||||
@click="handleConfirmReplace"
|
||||
class="px-8 text-white font-weight-bold ml-4 rounded-lg"
|
||||
variant="flat"
|
||||
size="large"
|
||||
elevation="2"
|
||||
>
|
||||
Ya, Ganti Pasien
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<AppSnackbar
|
||||
v-model="snackbar"
|
||||
@@ -468,6 +540,11 @@ const showKlinikRuangDialog = ref(false);
|
||||
const klinikRuangSearch = ref("");
|
||||
const activePanel = ref(null); // Tracks the open Klinik Ruang panel
|
||||
|
||||
// Confirmation Replace Dialog
|
||||
const showConfirmReplaceDialog = ref(false);
|
||||
const pendingReplaceItem = ref(null);
|
||||
const pendingReplaceAction = ref(null);
|
||||
|
||||
// Watch dialog state to reset search when closed
|
||||
watch(showKlinikRuangDialog, (newValue) => {
|
||||
if (!newValue) {
|
||||
@@ -790,6 +867,11 @@ const handleCall = async (count) => {
|
||||
};
|
||||
|
||||
const handleTableAction = async (item, action) => {
|
||||
if (action === "proses") {
|
||||
confirmAndProcess(item, action);
|
||||
return;
|
||||
}
|
||||
|
||||
await processPatient(item, action);
|
||||
|
||||
// Real-time broadcast after process
|
||||
@@ -805,11 +887,43 @@ const handleTableAction = async (item, action) => {
|
||||
};
|
||||
|
||||
const handleProcessNext = () => {
|
||||
processNextQueue();
|
||||
// Real-time broadcast after process next
|
||||
confirmAndProcess(null, "next");
|
||||
};
|
||||
|
||||
const confirmAndProcess = (item, action) => {
|
||||
if (currentProcessingPatient.value) {
|
||||
pendingReplaceItem.value = item;
|
||||
pendingReplaceAction.value = action;
|
||||
showConfirmReplaceDialog.value = true;
|
||||
} else {
|
||||
executeProcess(item, action);
|
||||
}
|
||||
};
|
||||
|
||||
const executeProcess = async (item, action) => {
|
||||
if (action === "next") {
|
||||
processNextQueue();
|
||||
} else {
|
||||
await processPatient(item, action);
|
||||
// If action is process, auto-call the patient after a short delay
|
||||
if (action === "proses") {
|
||||
setTimeout(() => {
|
||||
handleCallPatient();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// Real-time broadcast after process
|
||||
broadcastUpdate();
|
||||
};
|
||||
|
||||
const handleConfirmReplace = () => {
|
||||
executeProcess(pendingReplaceItem.value, pendingReplaceAction.value);
|
||||
showConfirmReplaceDialog.value = false;
|
||||
pendingReplaceItem.value = null;
|
||||
pendingReplaceAction.value = null;
|
||||
};
|
||||
|
||||
const { sendViaPost } = useWebSocket();
|
||||
|
||||
const broadcastUpdate = async (callData = null) => {
|
||||
@@ -1332,6 +1446,101 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.dialog-header-blue {
|
||||
background-color: var(--color-primary-600) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.header-icon-wrapper {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.header-subtitle {
|
||||
font-size: 13px;
|
||||
opacity: 0.9;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dialog-content-premium {
|
||||
background: var(--color-neutral-100);
|
||||
}
|
||||
|
||||
.comparison-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: var(--color-neutral-200);
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--color-neutral-400);
|
||||
}
|
||||
|
||||
.comparison-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
color: var(--color-neutral-600);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.item-card {
|
||||
background: white;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--color-neutral-400);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item-number {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&.text-danger-700 {
|
||||
color: var(--color-danger-700) !important;
|
||||
}
|
||||
|
||||
&.text-primary-600 {
|
||||
color: var(--color-primary-600) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.item-status {
|
||||
font-size: 11px;
|
||||
color: var(--color-neutral-700);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.comparison-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.confirm-text {
|
||||
font-size: 15px;
|
||||
color: var(--color-neutral-800);
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.headline-5 {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
|
||||
Reference in New Issue
Block a user