update logika pemanggilan, sticky konten, tampilan card

This commit is contained in:
bagus-arie05
2026-01-15 14:53:17 +07:00
parent 8aa5ab2ded
commit 96c637618b
6 changed files with 121 additions and 46 deletions
@@ -254,7 +254,7 @@ defineEmits(['action', 'change-klinik', 'process-next', 'call', 'open-klinik-rua
}
.patient-number {
font-size: 28px;
font-size: 32px;
line-height: 36px;
font-weight: 700;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+24 -1
View File
@@ -10,7 +10,8 @@
elevation="2"
:class="{
'clickable-card': isClickable,
'fast-track-card': isFastTrack
'fast-track-card': isFastTrack && !isCurrentlyProcessing,
'processing-card': isCurrentlyProcessing
}"
@click="handleCardClick"
v-bind="isClickable ? tooltipProps : {}"
@@ -92,6 +93,9 @@
<script setup>
import { computed } from 'vue';
import { useQueueStore } from '~/stores/queueStore';
const queueStore = useQueueStore();
const props = defineProps({
patient: {
@@ -111,6 +115,13 @@ const isFastTrack = computed(() => {
return fastTrack === 'YA';
});
const isCurrentlyProcessing = computed(() => {
const currentPatient = queueStore.currentProcessingPatient?.loket;
if (!currentPatient || !props.patient) return false;
return currentPatient.no === props.patient.no ||
currentPatient.barcode === props.patient.barcode;
});
const isEksekutif = computed(() => {
const pembayaran = (props.patient.pembayaran ?? "").toString().trim().toUpperCase();
return pembayaran === 'EKSEKUTIF';
@@ -222,6 +233,18 @@ const getStatusLabel = (status) => {
border-color: rgba(255, 132, 65, 0.6);
}
}
&.processing-card {
background: var(--color-primary-100) !important;
border-color: var(--color-primary-400);
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.2) !important;
&:hover {
background: var(--color-primary-200) !important;
border-color: var(--color-primary-500);
box-shadow: 0 6px 16px rgba(25, 118, 210, 0.25) !important;
}
}
}
.card-header {
+13 -32
View File
@@ -10,7 +10,7 @@
/>
<!-- Global Search and Actions -->
<div class="global-search-section mb-4">
<div class="global-search-section sticky-search-section">
<div class="search-actions-row">
<v-text-field
v-model="globalSearchQuery"
@@ -41,7 +41,7 @@
</div>
<!-- Rooms Grid -->
<div class="rooms-grid-wrapper">
<div class="rooms-grid-wrapper mt-4">
<div class="rooms-grid">
<v-card
v-for="ruang in ruangList"
@@ -1601,13 +1601,8 @@ const getPatientCardClass = (patient) => {
const baseClass = 'patient-queue-item';
const isFastTrack = (patient.fastTrack ?? '').toString().trim().toUpperCase() === 'YA';
// Pasien yang sudah digenerate/diproses: warna berbeda
if (patient.tipeLayanan || patient.calledPemeriksaanAwal || patient.calledTindakan) {
return isFastTrack
? `${baseClass} patient-queue-item-processed patient-queue-item-fast-track`
: `${baseClass} patient-queue-item-processed`;
}
// Pasien yang perlu digenerate: warna default atau fast track
// Jika pasien fast track, tambahkan class fast track
// Tidak perlu mengubah warna card jika sudah pemeriksaan awal/tindakan
return isFastTrack
? `${baseClass} patient-queue-item-default patient-queue-item-fast-track`
: `${baseClass} patient-queue-item-default`;
@@ -1743,6 +1738,14 @@ onMounted(() => {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.sticky-search-section {
position: -webkit-sticky; /* Safari */
position: sticky;
top: 16px;
z-index: 100;
margin-bottom: 0;
}
.search-actions-row {
display: flex;
gap: 12px;
@@ -1821,7 +1824,7 @@ onMounted(() => {
}
.current-patient-section {
background: linear-gradient(135deg, var(--color-primary-200) 0%, var(--color-primary-300) 100%);
background: linear-gradient(135deg, var(--color-primary-50) 0%, var(--color-primary-100) 100%);
border-radius: 8px;
padding: 16px;
border: 1px solid var(--color-primary-400);
@@ -2142,17 +2145,6 @@ onMounted(() => {
}
/* Pasien yang sudah digenerate/diproses */
.patient-queue-item-processed {
background: linear-gradient(135deg, var(--color-primary-200) 0%, var(--color-primary-300) 100%);
border: 1px solid var(--color-primary-400);
}
.patient-queue-item-processed:hover {
background: linear-gradient(135deg, var(--color-primary-300) 0%, var(--color-primary-400) 100%);
border-color: var(--color-primary-500);
}
/* Pasien default */
.patient-queue-item-default {
background: var(--color-neutral-100);
@@ -2175,17 +2167,6 @@ onMounted(() => {
border-color: rgba(255, 132, 65, 0.6) !important;
}
/* Fast track dengan processed - override processed styling */
.patient-queue-item-processed.patient-queue-item-fast-track {
background: #FBE9E8 !important;
border-color: rgba(255, 132, 65, 0.5) !important;
}
.patient-queue-item-processed.patient-queue-item-fast-track:hover {
background: #F1B3AE !important;
border-color: rgba(255, 132, 65, 0.6) !important;
}
/* Fast track dengan default - override default styling */
.patient-queue-item-default.patient-queue-item-fast-track {
background: #FBE9E8 !important;
+37 -7
View File
@@ -12,7 +12,7 @@
<!-- Main Content Grid -->
<v-row class="content-grid" dense>
<!-- Left Column: Current Patient & Queue Actions -->
<v-col cols="12" md="5">
<v-col cols="12" md="5" class="sticky-column">
<div class="sticky-wrapper">
<!-- Current Patient Card -->
<CurrentPatientCard
@@ -396,11 +396,18 @@ const handleProcessNext = () => {
};
const handleCallPatient = () => {
// TODO: Integrate text-to-speech library here
// Example: speak(`Nomor antrian ${currentProcessingPatient.value?.noAntrian.split(" |")[0]}, silakan menuju ke loket`)
// Panggil pasien yang sedang diproses
if (currentProcessingPatient.value) {
console.log('Calling patient:', currentProcessingPatient.value);
// Placeholder for text-to-speech integration
const result = queueStore.callProcessingPatient('loket');
if (result.success) {
snackbarText.value = result.message;
snackbarColor.value = "success";
snackbar.value = true;
} else {
snackbarText.value = result.message;
snackbarColor.value = "error";
snackbar.value = true;
}
}
};
@@ -408,6 +415,29 @@ const openKlinikRuangDialog = () => {
showKlinikRuangDialog.value = true;
};
// Calculate sticky top offset based on header height
const stickyTopOffset = ref(100);
const updateStickyOffset = () => {
const header = document.querySelector('.page-header');
if (header) {
const headerHeight = header.offsetHeight;
const containerPadding = 16;
stickyTopOffset.value = headerHeight + containerPadding + 8; // header + padding + minimal space
}
};
onMounted(() => {
updateStickyOffset();
window.addEventListener('resize', updateStickyOffset);
// Use nextTick to ensure DOM is fully rendered
setTimeout(updateStickyOffset, 100);
});
onUnmounted(() => {
window.removeEventListener('resize', updateStickyOffset);
});
const closeKlinikRuangDialog = () => {
showKlinikRuangDialog.value = false;
klinikRuangSearch.value = "";
@@ -485,7 +515,6 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
background: var(--color-neutral-300);
min-height: 100vh;
padding: 16px;
overflow-x: hidden; /* Prevent horizontal scroll */
width: 100%;
max-width: 100vw;
box-sizing: border-box;
@@ -508,9 +537,10 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
}
.sticky-wrapper {
position: -webkit-sticky; /* Safari */
position: sticky;
top: 16px;
align-self: flex-start;
z-index: 10;
}
.filters {
+8 -2
View File
@@ -614,12 +614,18 @@ const isInTTSWindow = (queue) => {
}
// Current called queue - tiket yang sedang diproses di admin loket
// Nomor antrian menjadi "dipanggil" jika diproses pada AdminLoket
// Nomor antrian menjadi "dipanggil" jika diproses pada AdminLoket DAN sudah dipanggil oleh admin
const currentCalledQueue = computed(() => {
// Prioritas 1: Pasien yang sedang diproses di admin loket (currentProcessingPatient)
// Ini adalah pasien yang sedang dipanggil/diproses di AdminLoket
// Hanya tampilkan jika sudah dipanggil eksplisit oleh admin (calledByAdmin === true)
if (currentProcessingPatient.value) {
const processingPatient = currentProcessingPatient.value
// Cek apakah sudah dipanggil oleh admin
if (!processingPatient.calledByAdmin) {
return null
}
// Pastikan pasien memiliki noAntrian dan status 'di-loket' (sudah check-in)
if (processingPatient.noAntrian && processingPatient.status === 'di-loket') {
// Dapatkan nama klinik dari nomor antrian
+38 -3
View File
@@ -754,7 +754,8 @@ export const useQueueStore = defineStore('queue', () => {
allPatients.value[patientIndex] = {
...allPatients.value[patientIndex],
status: "di-loket",
processStage: "klinik"
processStage: "klinik",
calledByAdmin: false // Reset flag saat pindah stage
};
message = `Pasien ${patientCode} berhasil check in dan masuk ke Tabel Loket Klinik`;
}
@@ -784,7 +785,8 @@ export const useQueueStore = defineStore('queue', () => {
case "terlambat":
allPatients.value[patientIndex] = {
...allPatients.value[patientIndex],
status: "terlambat"
status: "terlambat",
calledByAdmin: false // Reset flag
};
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
currentProcessingPatient.value[adminType] = null;
@@ -795,7 +797,8 @@ export const useQueueStore = defineStore('queue', () => {
case "pending":
allPatients.value[patientIndex] = {
...allPatients.value[patientIndex],
status: "pending"
status: "pending",
calledByAdmin: false // Reset flag
};
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
currentProcessingPatient.value[adminType] = null;
@@ -891,6 +894,35 @@ export const useQueueStore = defineStore('queue', () => {
};
};
const callProcessingPatient = (adminType = 'loket') => {
// Panggil pasien yang sedang diproses untuk ditampilkan di layar anjungan
const processingPatient = currentProcessingPatient.value[adminType];
if (!processingPatient) {
return { success: false, message: "Tidak ada pasien yang sedang diproses" };
}
// Update flag calledByAdmin
const patientIndex = allPatients.value.findIndex(p => p.no === processingPatient.no);
if (patientIndex !== -1) {
allPatients.value[patientIndex] = {
...allPatients.value[patientIndex],
calledByAdmin: true,
lastCalledAt: new Date().toISOString()
};
// Update currentProcessingPatient with the new data
currentProcessingPatient.value[adminType] = allPatients.value[patientIndex];
return {
success: true,
message: `Memanggil pasien ${processingPatient.noAntrian.split(" |")[0]}`
};
}
return { success: false, message: "Gagal memanggil pasien" };
};
const createAntreanKlinik = (klinik, patient = null, adminType = 'loket') => {
const newNo = allPatients.value.length + 1;
const timestamp = new Date();
@@ -1016,6 +1048,7 @@ export const useQueueStore = defineStore('queue', () => {
calledTindakan: false,
lastCalledAt: null,
lastCalledTipeLayanan: null,
calledByAdmin: false, // Flag untuk tracking apakah sudah dipanggil oleh admin
};
allPatients.value.push(newPatient);
@@ -1535,6 +1568,7 @@ export const useQueueStore = defineStore('queue', () => {
visitType: visitType,
visitDate: visitDate || timestamp.toISOString().substring(0, 10),
namaDokter: namaDokter || null, // Nama dokter hanya untuk pasien Eksekutif
calledByAdmin: false, // Flag untuk tracking apakah sudah dipanggil oleh admin
// Fast Track data
penanggungJawab: (isFastTrack && fastTrackData) ? fastTrackData.penanggungJawab : null,
alasanFastTrack: (isFastTrack && fastTrackData) ? fastTrackData.alasanFastTrack : null,
@@ -1664,6 +1698,7 @@ export const useQueueStore = defineStore('queue', () => {
callNext,
callMultiplePatients,
processPatient,
callProcessingPatient,
createAntreanKlinik,
createAntreanPenunjang,
createAntreanKlinikRuang,