Files
web-antrean/pages/AdminLoket.vue
T
2025-10-21 08:31:00 +07:00

749 lines
20 KiB
Vue

<!-- pages/LoketAdmin.vue -->
<template>
<div class="loket-container">
<!-- Header Section -->
<div class="page-header">
<div class="header-content">
<div class="header-left">
<div class="header-icon">
<v-icon size="32" color="white">mdi-view-dashboard</v-icon>
</div>
<div class="header-text">
<h1 class="page-title">Loket Admin</h1>
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
</div>
</div>
<div class="header-right">
<v-chip color="success" variant="flat" class="mr-2">
Total {{ totalPasien }} Pasien
</v-chip>
<v-chip color="white" variant="flat" class="text-primary">
Max 150 Pasien
</v-chip>
</div>
</div>
</div>
<!-- Combined Control Section -->
<v-row class="mb-4">
<!-- Left Side: Current Processing -->
<v-col cols="12" lg="6">
<!-- Current Patient Processing Card -->
<v-card
v-if="currentProcessingPatient"
class="current-processing-card"
elevation="2"
>
<v-card-text class="pa-4">
<div class="d-flex align-center justify-space-between">
<div class="patient-info">
<v-chip color="primary" variant="flat" class="mb-2">
<v-icon start>mdi-account-clock</v-icon>
SEDANG DIPROSES
</v-chip>
<div class="text-h5 font-weight-bold mb-1">
{{ currentProcessingPatient.noAntrian.split(" |")[0] }}
</div>
<div class="text-subtitle-1 text-grey-darken-1">
{{ currentProcessingPatient.barcode }} |
{{ currentProcessingPatient.klinik }}
</div>
<div class="patient-meta">
Jenis Pembayaran: {{ currentProcessingPatient.pembayaran }}
</div>
</div>
<div class="action-buttons d-flex flex-column">
<v-btn
color="success"
variant="flat"
class="mb-2"
width="120"
@click="processPatient(currentProcessingPatient, 'check-in')"
>
<v-icon start>mdi-check-circle</v-icon>
Check In
</v-btn>
<v-btn
color="warning"
variant="flat"
class="mb-2"
width="120"
@click="processPatient(currentProcessingPatient, 'terlambat')"
>
<v-icon start>mdi-clock-alert</v-icon>
Terlambat
</v-btn>
<v-btn
color="error"
variant="flat"
class="mb-2"
width="120"
@click="processPatient(currentProcessingPatient, 'pending')"
>
<v-icon start>mdi-pause-circle</v-icon>
Pending
</v-btn>
<v-btn
color="info"
variant="flat"
width="120"
@click="showChangeKlinikDialog = true"
>
<v-icon start>mdi-swap-horizontal</v-icon>
Ubah Klinik
</v-btn>
</div>
</div>
</v-card-text>
</v-card>
<!-- Empty State -->
<v-card v-else class="current-processing-card" elevation="2">
<v-card-text class="pa-6 text-center">
<v-icon size="64" color="grey-lighten-1" class="mb-3">
mdi-account-off-outline
</v-icon>
<div class="text-h6 text-grey-darken-1 mb-2">
Tidak Ada Pasien yang Diproses
</div>
<div class="text-body-2 text-grey">
Pilih pasien dari tabel "Di Loket" untuk memproses
</div>
</v-card-text>
</v-card>
</v-col>
<!-- Right Side: Queue Info and Call Controls -->
<v-col cols="12" lg="6">
<v-card class="quota-call-card" elevation="2">
<v-card-text class="pa-4">
<div class="d-flex align-center justify-center mb-3">
<v-chip color="success" variant="flat" size="large">
<v-icon start>mdi-account-arrow-right</v-icon>
PANGGIL ANTREAN ANJUNGAN
</v-chip>
</div>
<v-row class="mb-3">
<v-col cols="6" class="text-center">
<div class="quota-label">Kuota</div>
<div class="quota-number">150</div>
</v-col>
<v-col cols="6" class="text-center">
<div class="quota-label">Tersedia</div>
<div class="quota-number quota-available">
{{ 150 - quotaUsed }}
</div>
</v-col>
</v-row>
<div class="text-center mb-3">
<div class="quota-used-text">
Total Quota Terpakai: {{ quotaUsed }}
</div>
<v-progress-linear
:model-value="(quotaUsed / 150) * 100"
color="success"
height="6"
rounded
class="mt-2"
></v-progress-linear>
</div>
<v-divider class="my-3"></v-divider>
<div class="call-controls">
<div class="call-label mb-2">Kumulatif Panggil Antrean</div>
<div class="call-buttons">
<v-btn
color="success"
variant="flat"
class="call-btn"
@click="callNext"
:disabled="!nextPatient"
>
<span class="call-btn-text">1</span>
</v-btn>
<v-btn
color="info"
variant="flat"
class="call-btn"
@click="callMultiplePatients(5)"
>
<span class="call-btn-text">5</span>
</v-btn>
<v-btn
color="warning"
variant="flat"
class="call-btn"
@click="callMultiplePatients(10)"
>
<span class="call-btn-text">10</span>
</v-btn>
<v-btn
color="error"
variant="flat"
class="call-btn"
@click="callMultiplePatients(20)"
>
<span class="call-btn-text">20</span>
</v-btn>
</div>
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-row class="mb-4">
<v-col cols="12">
<div class="d-flex justify-center gap-2">
<v-btn
color="primary"
variant="flat"
size="large"
@click="showKlinikDialog = true"
>
<v-icon start>mdi-hospital-building</v-icon>
Buat Antrean Klinik Ruang
</v-btn>
<v-btn
color="secondary"
variant="flat"
size="large"
@click="openPenunjangDialog()"
>
<v-icon start>mdi-clipboard-pulse</v-icon>
Buat Antrean Penunjang
</v-btn>
</div>
</v-col>
</v-row>
<!-- Di Loket Patients Table -->
<v-card class="main-table-card mb-4" elevation="2">
<TabelData
:headers="diLoketHeaders"
:items="diLoketPatients"
title="DATA PASIEN - DI LOKET"
>
<template #actions="{ item }">
<div class="d-flex gap-1">
<v-btn
size="small"
color="info"
variant="flat"
@click="processPatient(item, 'aktifkan')"
>
Aktifkan
</v-btn>
<v-btn
size="small"
color="success"
variant="flat"
@click="processPatient(item, 'proses')"
>
Proses
</v-btn>
</div>
</template>
</TabelData>
</v-card>
<!-- Terlambat Patients Table -->
<v-card
class="late-table-card mb-4"
elevation="2"
v-if="terlambatPatients.length > 0"
>
<TabelData
:headers="terlambatHeaders"
:items="terlambatPatients"
title="INFO PASIEN LAPOR TERLAMBAT"
>
<template #actions="{ item }">
<div class="d-flex gap-1">
<v-btn
size="small"
color="success"
variant="flat"
@click="processPatient(item, 'aktifkan')"
>
Aktifkan
</v-btn>
</div>
</template>
</TabelData>
</v-card>
<!-- Pending Patients Table -->
<v-card
class="pending-table-card mb-4"
elevation="2"
v-if="pendingPatients.length > 0"
>
<TabelData
:headers="pendingHeaders"
:items="pendingPatients"
title="INFO PASIEN PENDING"
>
<template #actions="{ item }">
<div class="d-flex gap-1">
<v-btn
size="small"
color="success"
variant="flat"
@click="processPatient(item, 'proses')"
>
Proses
</v-btn>
</div>
</template>
</TabelData>
</v-card>
<!-- Klinik Dialog -->
<v-dialog v-model="showKlinikDialog" max-width="900px">
<v-card>
<v-card-title class="d-flex align-center pa-4 bg-primary text-white">
<v-btn icon @click="showKlinikDialog = false" class="mr-2">
<v-icon color="white">mdi-arrow-left</v-icon>
</v-btn>
<span class="text-h6">Buat Antrean Klinik Ruang</span>
</v-card-title>
<v-card-subtitle class="pa-4 text-center">
<h3 class="text-h6 mb-3">Pilih Klinik</h3>
<div class="d-flex justify-end gap-2">
<v-text-field
v-model="klinikSearch"
placeholder="Pencarian"
density="compact"
hide-details
style="max-width: 200px"
></v-text-field>
</div>
</v-card-subtitle>
<v-card-text class="pa-4">
<v-row>
<v-col
v-for="klinik in filteredKliniks"
:key="klinik.id"
cols="12"
sm="6"
md="4"
>
<v-card
class="klinik-card"
elevation="1"
@click="selectKlinik(klinik)"
>
<v-card-text class="text-center pa-4">
<div class="text-h6 font-weight-bold">{{ klinik.name }}</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
<!-- Penunjang Dialog -->
<v-dialog v-model="showPenunjangDialog" max-width="900px">
<v-card>
<v-card-title class="d-flex align-center pa-4 bg-secondary text-white">
<v-btn icon @click="showPenunjangDialog = false" class="mr-2">
<v-icon color="white">mdi-arrow-left</v-icon>
</v-btn>
<span class="text-h6">Buat Antrean Penunjang</span>
</v-card-title>
<v-card-subtitle class="pa-4">
<h3 class="text-h6 mb-3 text-center">Pilih Ruang</h3>
<div class="d-flex justify-end gap-2">
<v-text-field
v-model="penunjangSearch"
placeholder="Pencarian"
density="compact"
hide-details
style="max-width: 200px"
></v-text-field>
</div>
</v-card-subtitle>
<v-card-text class="pa-4">
<v-row>
<v-col
v-for="penunjang in filteredPenunjangs"
:key="penunjang.id"
cols="12"
sm="6"
md="4"
>
<v-card
class="penunjang-card"
elevation="1"
@click="selectPenunjang(penunjang)"
>
<v-card-text class="text-center pa-4">
<div class="text-h6 font-weight-bold">
{{ penunjang.name }}
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
<!-- Change Klinik Dialog -->
<v-dialog v-model="showChangeKlinikDialog" max-width="900px">
<v-card>
<v-card-title class="d-flex align-center pa-4 bg-info text-white">
<v-btn icon @click="showChangeKlinikDialog = false" class="mr-2">
<v-icon color="white">mdi-arrow-left</v-icon>
</v-btn>
<span class="text-h6">Ubah Klinik</span>
</v-card-title>
<v-card-subtitle class="pa-4">
<h3 class="text-h6 mb-3 text-center">Pilih Klinik Baru</h3>
<div class="d-flex justify-end gap-2">
<v-text-field
v-model="changeKlinikSearch"
placeholder="Pencarian"
density="compact"
hide-details
style="max-width: 200px"
></v-text-field>
</div>
</v-card-subtitle>
<v-card-text class="pa-4">
<v-row>
<v-col
v-for="klinik in filteredChangeKliniks"
:key="klinik.id"
cols="12"
sm="6"
md="4"
>
<v-card
class="klinik-card"
elevation="1"
@click="changeKlinik(klinik)"
>
<v-card-text class="text-center pa-4">
<div class="text-h6 font-weight-bold">{{ klinik.name }}</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
<!-- Snackbar -->
<v-snackbar
v-model="snackbar"
:color="snackbarColor"
:timeout="3000"
location="top right"
>
{{ snackbarText }}
<template v-slot:actions>
<v-btn icon @click="snackbar = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</template>
</v-snackbar>
</div>
</template>
<script setup>
import { ref } from "vue";
import TabelData from "../components/TabelData.vue";
import { useQueue } from "../composables/useQueue";
// Use queue composable with 'loket' admin type
const {
snackbar,
snackbarText,
snackbarColor,
showKlinikDialog,
showPenunjangDialog,
showChangeKlinikDialog,
klinikSearch,
penunjangSearch,
changeKlinikSearch,
currentProcessingPatient,
diLoketPatients,
terlambatPatients,
pendingPatients,
nextPatient,
totalPasien,
quotaUsed,
filteredKliniks,
filteredPenunjangs,
filteredChangeKliniks,
callNext,
callMultiplePatients,
processPatient,
selectKlinik,
selectPenunjang,
openPenunjangDialog,
changeKlinik,
} = useQueue('loket');
// Headers for different tables
const diLoketHeaders = ref([
{ title: "No", value: "no", sortable: false, width: "60px" },
{ title: "Jam Panggil", value: "jamPanggil", sortable: true, width: "100px" },
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
{ title: "Aksi", value: "aksi", sortable: false, width: "200px" },
]);
const terlambatHeaders = ref([
{ title: "No", value: "no", sortable: false, width: "60px" },
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
]);
const pendingHeaders = ref([
{ title: "#", value: "no", sortable: false, width: "60px" },
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
]);
</script>
<style scoped>
.loket-container {
background: #f5f7fa;
min-height: 100vh;
padding: 20px;
}
.page-header {
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
border-radius: 16px;
margin-bottom: 24px;
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32px;
color: white;
}
.header-left {
display: flex;
align-items: center;
}
.header-icon {
background: rgba(255, 255, 255, 0.2);
border-radius: 16px;
padding: 16px;
margin-right: 20px;
backdrop-filter: blur(10px);
}
.page-title {
font-size: 32px;
font-weight: 700;
margin: 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.page-subtitle {
margin: 4px 0 0 0;
opacity: 0.9;
font-size: 16px;
}
.header-right {
display: flex;
align-items: center;
}
.current-processing-card,
.quota-call-card,
.main-table-card,
.late-table-card,
.pending-table-card {
border-radius: 16px;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.current-processing-card {
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
border: 2px solid rgba(255, 152, 0, 0.2);
}
.quota-call-card {
background: linear-gradient(135deg, #e3f2fd 0%, #f5f5f5 100%);
border: 2px solid rgba(33, 150, 243, 0.2);
}
.patient-info .text-h5 {
color: #1976d2;
}
.action-buttons {
display: flex;
align-items: center;
}
.quota-label {
font-size: 13px;
color: #6b7280;
font-weight: 500;
margin-bottom: 4px;
}
.quota-number {
font-size: 32px;
font-weight: 700;
color: #1f2937;
line-height: 1;
}
.quota-available {
color: #4caf50;
}
.quota-used-text {
font-size: 13px;
color: #6b7280;
font-weight: 500;
}
.call-controls {
text-align: center;
}
.call-label {
font-size: 14px;
font-weight: 600;
color: #374151;
text-align: center;
}
.call-buttons {
display: flex;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
}
.call-btn {
min-width: 80px;
height: 48px;
border-radius: 8px;
}
.call-btn-text {
font-size: 18px;
font-weight: 700;
}
.klinik-card,
.penunjang-card {
cursor: pointer;
transition: all 0.3s ease;
}
.klinik-card:hover,
.penunjang-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.v-btn {
text-transform: none !important;
}
.v-btn--size-small {
height: 32px;
padding: 0 12px;
}
.text-success {
color: #4caf50 !important;
}
@media (max-width: 1024px) {
.header-content {
flex-direction: column;
gap: 20px;
text-align: center;
}
.header-left {
flex-direction: column;
gap: 12px;
}
.page-title {
font-size: 28px;
}
.action-buttons {
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
width: 100%;
}
.action-buttons .v-btn {
flex: 1;
min-width: 120px;
}
}
@media (max-width: 768px) {
.loket-container {
padding: 16px;
}
.header-content {
padding: 24px 20px;
}
.page-title {
font-size: 24px;
}
.action-buttons {
flex-direction: column;
width: 100%;
}
.action-buttons .v-btn {
width: 100%;
margin: 4px 0;
}
.quota-number {
font-size: 28px;
}
}
</style>