diff --git a/pages/AdminKlinikRuang/[kodeKlinik].vue b/pages/AdminKlinikRuang/[kodeKlinik].vue index a1b1872..d65d0c2 100644 --- a/pages/AdminKlinikRuang/[kodeKlinik].vue +++ b/pages/AdminKlinikRuang/[kodeKlinik].vue @@ -10,7 +10,8 @@ /> -
+
+
+
@@ -221,74 +223,118 @@ - - - - Detail Pasien - + + + + Detail Pasien + mdi-close - - + +
- Nomor Antrian - {{ selectedPatientDetail.noAntrian?.split(" |")[0] }} + Nomor Antrian + {{ selectedPatientDetail.noAntrian?.split(" |")[0] || '-' }}
- Barcode - {{ selectedPatientDetail.barcode }} + Barcode + {{ selectedPatientDetail.barcode || '-' }} +
+
+
+ + +
+ Klinik + {{ selectedPatientDetail.klinik || '-' }}
- Klinik - {{ selectedPatientDetail.klinik }} + Ruang + {{ selectedPatientDetail.ruang || '-' }} +
+
+
+ + +
+ Jam Panggil + {{ selectedPatientDetail.jamPanggil || '-' }}
- Ruang - {{ selectedPatientDetail.ruang || '-' }} + Pembayaran + {{ selectedPatientDetail.pembayaran || '-' }} +
+
+
+ + +
+ Layanan +
+ + Pemeriksaan Awal + + + Tindakan + +
- Jam Panggil - {{ selectedPatientDetail.jamPanggil }} -
-
- -
- Pembayaran - {{ selectedPatientDetail.pembayaran }} -
-
- -
- Tipe Layanan - - {{ selectedPatientDetail.tipeLayanan }} - -
-
- -
- Status - + Status + {{ getStatusLabel(selectedPatientDetail.status) }}
+ + +
+ Nomor Antrean Anjungan Sebelumnya + {{ selectedPatientDetail.referencePatient }} +
+
+
- + - Tutup + + TUTUP +
@@ -571,9 +617,11 @@ const handleCallPatientByTipe = (ruang, tipeLayanan) => { } // Update tracking panggilan berdasarkan tipeLayanan + // Update tipeLayanan pasien agar muncul di kolom yang sesuai di Anjungan const updateData = { ...queueStore.allPatients[patientIndex], status: 'di-loket', + tipeLayanan: tipeLayanan, // Update tipeLayanan untuk display di Anjungan lastCalledAt: new Date().toISOString(), lastCalledTipeLayanan: tipeLayanan }; @@ -598,10 +646,76 @@ const handleCallPatientByTipe = (ruang, tipeLayanan) => { }; const showPatientDetail = (patient) => { - selectedPatientDetail.value = patient; + // Get latest patient data from store + const patientIndex = queueStore.allPatients.findIndex(p => p.no === patient.no); + if (patientIndex !== -1) { + selectedPatientDetail.value = { ...queueStore.allPatients[patientIndex] }; + } else { + selectedPatientDetail.value = { ...patient }; + } showDetailDialog.value = true; }; +// Toggle layanan status (membatalkan panggilan jika sudah dipanggil) +const toggleLayananStatus = (tipeLayanan) => { + if (!selectedPatientDetail.value || !selectedPatientDetail.value.no) return; + + const patientIndex = queueStore.allPatients.findIndex(p => p.no === selectedPatientDetail.value.no); + if (patientIndex === -1) { + showSnackbar('Pasien tidak ditemukan', 'error'); + return; + } + + const patient = queueStore.allPatients[patientIndex]; + const updateData = { ...patient }; + + if (tipeLayanan === 'Pemeriksaan Awal') { + // Toggle status: jika sudah dipanggil, batalkan; jika belum, tidak bisa diaktifkan dari sini + if (patient.calledPemeriksaanAwal) { + updateData.calledPemeriksaanAwal = false; + // Jika tipeLayanan saat ini adalah Pemeriksaan Awal, reset tipeLayanan + if (patient.tipeLayanan === 'Pemeriksaan Awal') { + updateData.tipeLayanan = null; + } + showSnackbar('Status panggilan Pemeriksaan Awal dibatalkan', 'info'); + } else { + showSnackbar('Pemeriksaan Awal belum dipanggil', 'warning'); + return; + } + } else if (tipeLayanan === 'Tindakan') { + // Toggle status: jika sudah dipanggil, batalkan; jika belum, tidak bisa diaktifkan dari sini + if (patient.calledTindakan) { + updateData.calledTindakan = false; + // Jika tipeLayanan saat ini adalah Tindakan, reset tipeLayanan + if (patient.tipeLayanan === 'Tindakan') { + updateData.tipeLayanan = null; + } + showSnackbar('Status panggilan Tindakan dibatalkan', 'info'); + } else { + showSnackbar('Tindakan belum dipanggil', 'warning'); + return; + } + } + + // Update patient data + queueStore.allPatients[patientIndex] = updateData; + + // Update selectedPatientDetail untuk refresh dialog + selectedPatientDetail.value = { ...updateData }; + + // Update current processing jika pasien sedang diproses + const ruangList = masterStore.ruangData || []; + for (const klinikRuang of ruangList) { + if (klinikRuang.kodeKlinik === patient.kodeKlinik) { + const key = `klinik-ruang-${patient.kodeKlinik}-${patient.nomorRuang}`; + if (queueStore.currentProcessingPatient[key]?.no === patient.no) { + queueStore.currentProcessingPatient[key] = updateData; + } + break; + } + } +}; + const getStatusColor = (status) => { const colors = { 'di-loket': 'success', @@ -767,9 +881,13 @@ onMounted(() => { line-height: 1.4; } +.rooms-grid-wrapper { + margin-top: 24px; +} + .rooms-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); + grid-template-columns: repeat(3, 1fr); gap: 16px; } @@ -791,6 +909,7 @@ onMounted(() => { align-items: center; font-weight: 700; font-size: 16px; + gap: 8px; } .current-patient-section { @@ -888,6 +1007,149 @@ onMounted(() => { padding: 16px 20px; } +/* Detail Dialog Styles */ +.detail-dialog-card { + border-radius: 12px; + overflow: hidden; +} + +.detail-dialog-header { + display: flex; + justify-content: space-between; + align-items: center; + background: var(--color-neutral-100); + color: var(--color-neutral-900); + padding: 20px 24px; + border-bottom: 1px solid var(--color-neutral-300); +} + +.detail-dialog-title { + font-size: 20px; + font-weight: 700; + color: var(--color-neutral-900); +} + +.detail-close-btn { + color: var(--color-neutral-600) !important; +} + +.detail-close-btn:hover { + background: var(--color-neutral-200) !important; +} + +.detail-dialog-content { + padding: 24px !important; + background: var(--color-neutral-50); +} + +.detail-row { + margin-bottom: 16px; +} + +.detail-row:last-child { + margin-bottom: 0; +} + +.detail-item { + display: flex; + flex-direction: column; + gap: 8px; +} + +.detail-label { + font-size: 12px; + line-height: 16px; + color: var(--color-neutral-600); + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.detail-value { + font-size: 16px; + line-height: 24px; + font-weight: 700; + color: var(--color-neutral-900); +} + +.detail-value.reference-value { + font-size: 14px; + line-height: 20px; + word-break: break-word; +} + +.detail-chip { + font-weight: 600; + text-transform: none; +} + +.detail-chips-group { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.detail-chip-clickable { + cursor: pointer; + transition: all 0.2s ease; + font-weight: 600; + text-transform: none; +} + +.detail-chip-clickable:hover { + transform: scale(1.05); + opacity: 0.9; +} + +.detail-chip-clickable.chip-active { + cursor: pointer; +} + +.detail-chip-clickable:not(.chip-active) { + cursor: not-allowed; + opacity: 0.6; +} + +.detail-dialog-actions { + padding: 16px 24px; + background: var(--color-neutral-100); + border-top: 1px solid var(--color-neutral-300); +} + +.detail-dialog-actions .v-btn { + min-width: 120px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.detail-chips-group { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.detail-chip-clickable { + cursor: pointer; + transition: all 0.2s ease; + font-weight: 600; + text-transform: none; +} + +.detail-chip-clickable:hover { + transform: scale(1.05); + opacity: 0.9; +} + +.detail-chip-clickable.chip-active { + cursor: pointer; +} + +.detail-chip-clickable:not(.chip-active) { + cursor: not-allowed; + opacity: 0.6; +} + .body-2 { font-size: 16px; line-height: 24px; @@ -979,6 +1241,12 @@ onMounted(() => { font-weight: 600; } +@media (max-width: 1400px) { + .rooms-grid { + grid-template-columns: repeat(2, 1fr); + } +} + @media (max-width: 960px) { .rooms-grid { grid-template-columns: 1fr; diff --git a/pages/AdminLoket.vue b/pages/AdminLoket.vue index 7ca2cf5..0eeadd5 100644 --- a/pages/AdminLoket.vue +++ b/pages/AdminLoket.vue @@ -37,21 +37,8 @@
- - - - mdi-hospital-building - Buat Antrean Klinik - - - - + + - + { } .create-buttons :deep(.v-col) { - padding: 0 4px; box-sizing: border-box; - flex: 0 0 33.333333%; - max-width: 33.333333%; } .create-buttons :deep(.v-col:first-child) { diff --git a/pages/Anjungan/AntrianKlinikRuang/[kodeKlinik].vue b/pages/Anjungan/AntrianKlinikRuang/[kodeKlinik].vue index 0b08365..288d74d 100644 --- a/pages/Anjungan/AntrianKlinikRuang/[kodeKlinik].vue +++ b/pages/Anjungan/AntrianKlinikRuang/[kodeKlinik].vue @@ -58,19 +58,7 @@ {{ ruang.pemeriksaanAwal.currentQueue.noAntrian.split(' |')[0] }}
-
-
SELANJUTNYA
-
-
- {{ queue.noAntrian.split(' |')[0] }} -
-
-
-
+
mdi-clock-outline

Tidak Ada Antrian

@@ -88,19 +76,7 @@ {{ ruang.tindakan.currentQueue.noAntrian.split(' |')[0] }}
-
-
SELANJUTNYA
-
-
- {{ queue.noAntrian.split(' |')[0] }} -
-
-
-
+
mdi-clock-outline

Tidak Ada Antrian

@@ -280,28 +256,39 @@ const displayedRuang = computed(() => { return numA - numB }) - // Split by tipeLayanan - const pemeriksaanAwalQueues = allQueues.filter(q => q.tipeLayanan === 'Pemeriksaan Awal') - const tindakanQueues = allQueues.filter(q => q.tipeLayanan === 'Tindakan') + // Split by tipeLayanan berdasarkan tipeLayanan terakhir yang dipanggil + // Pasien yang dipanggil akan muncul di kolom sesuai tipeLayanan terakhir yang dipanggil + // Jika pasien dipanggil untuk Pemeriksaan Awal, muncul di kolom Pemeriksaan Awal + // Jika pasien dipanggil untuk Tindakan, muncul di kolom Tindakan (dan hilang dari Pemeriksaan Awal) + const pemeriksaanAwalQueues = allQueues.filter(q => { + // Gunakan tipeLayanan (yang diupdate saat dipanggil) untuk menentukan kolom + return q.tipeLayanan === 'Pemeriksaan Awal'; + }); + const tindakanQueues = allQueues.filter(q => { + // Gunakan tipeLayanan (yang diupdate saat dipanggil) untuk menentukan kolom + return q.tipeLayanan === 'Tindakan'; + }) // Get current and next queues for each tipeLayanan // Current queue is the one with status 'di-loket' and matching tipeLayanan + // Jika ada pasien baru yang dipanggil, akan menggantikan pasien sebelumnya di kolom tersebut const getCurrentAndNext = (queues, tipeLayanan) => { // Find current: pasien dengan status 'di-loket' dan tipeLayanan yang sesuai - // Sort by lastCalledAt untuk mendapatkan yang terakhir dipanggil + // Sort by lastCalledAt untuk mendapatkan yang terakhir dipanggil (yang baru dipanggil akan menggantikan yang lama) const currentCandidates = queues.filter(q => q.status === 'di-loket' && q.tipeLayanan === tipeLayanan ).sort((a, b) => { const timeA = a.lastCalledAt ? new Date(a.lastCalledAt) : new Date(a.createdAt || 0) const timeB = b.lastCalledAt ? new Date(b.lastCalledAt) : new Date(b.createdAt || 0) - return timeB - timeA + return timeB - timeA // Yang terakhir dipanggil di atas }) + // Ambil yang terakhir dipanggil sebagai current (menggantikan yang sebelumnya) const current = currentCandidates.length > 0 ? currentCandidates[0] : null - // Next queues: exclude current patient + // Next queues: exclude current patient dan pasien dengan status 'di-loket' (yang sudah dipanggil) const next = queues.filter(q => q.no !== current?.no && - (q.status === 'waiting' || q.status === 'di-loket') + q.status === 'waiting' // Hanya yang waiting (belum dipanggil) ).slice(0, 3) // Show max 3 next queues return { current, next } } diff --git a/stores/navItems1.ts b/stores/navItems1.ts index 45a6652..3c5d05e 100644 --- a/stores/navItems1.ts +++ b/stores/navItems1.ts @@ -24,7 +24,6 @@ const defaultNavItems: NavItem[] = [ // badge: "3", }, { id: 4, name: "Admin Loket", icon: "mdi-account-supervisor-outline", path: "/AdminLoket" }, - { id: 5, name: "Admin Klinik", icon: "mdi-text-box-plus-outline", path: "/AdminKlinik" }, { id: 6, name: "Admin Klinik Ruang", icon: "mdi-door-open", path: "/AdminKlinikRuang" }, { id: 7, name: "Admin Penunjang", icon: "mdi-plus-box-outline", path: "/AdminPenunjang" }, { id: 8, name: "Buat Antrean", icon: "mdi-account-multiple-plus-outline", path: "/BuatAntrean" },