update flow anjungan,checkin,admin,dan klinik ruang
This commit is contained in:
@@ -12,6 +12,10 @@
|
||||
<span class="quota-label">Tersedia</span>
|
||||
<span class="quota-value quota-available">{{ availableQuota }}</span>
|
||||
</div>
|
||||
<div class="quota-item">
|
||||
<span class="quota-label">Menunggu</span>
|
||||
<span class="quota-value quota-waiting">{{ menungguCount }}</span>
|
||||
</div>
|
||||
<div class="quota-item full-width">
|
||||
<v-progress-linear
|
||||
:model-value="quotaPercentage"
|
||||
@@ -21,6 +25,7 @@
|
||||
class="mt-1"
|
||||
/>
|
||||
<span class="quota-used">Terpakai: {{ usedQuota }}</span>
|
||||
<span class="quota-callable">Bisa dipanggil: {{ callableQuota }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -53,6 +58,10 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
menungguCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
hasNext: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -61,6 +70,11 @@ const props = defineProps({
|
||||
|
||||
const availableQuota = computed(() => props.totalQuota - props.usedQuota);
|
||||
const quotaPercentage = computed(() => (props.usedQuota / props.totalQuota) * 100);
|
||||
// Kuota yang bisa dipanggil = Tersedia - Menunggu
|
||||
const callableQuota = computed(() => {
|
||||
const callable = availableQuota.value - props.menungguCount;
|
||||
return Math.max(0, callable);
|
||||
});
|
||||
|
||||
defineEmits(['call']);
|
||||
</script>
|
||||
@@ -70,6 +84,18 @@ defineEmits(['call']);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--color-neutral-500);
|
||||
background: var(--color-neutral-100);
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.queue-actions-card :deep(.v-card-text) {
|
||||
padding: 16px !important;
|
||||
box-sizing: border-box !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
@@ -81,20 +107,37 @@ defineEmits(['call']);
|
||||
}
|
||||
|
||||
.quota-info {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
display: grid !important;
|
||||
grid-template-columns: 1fr 1fr 1fr !important;
|
||||
gap: 6px !important;
|
||||
margin-bottom: 12px !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
grid-auto-flow: row !important;
|
||||
grid-auto-rows: auto !important;
|
||||
}
|
||||
|
||||
.quota-item {
|
||||
background: var(--color-neutral-300);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
background: var(--color-neutral-300) !important;
|
||||
border-radius: 8px !important;
|
||||
padding: 10px 6px !important;
|
||||
text-align: center !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important;
|
||||
word-wrap: break-word !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
flex: 1 1 0% !important;
|
||||
}
|
||||
|
||||
.quota-item.full-width {
|
||||
grid-column: 1 / -1;
|
||||
grid-column: 1 / 4 !important;
|
||||
margin-top: 8px !important;
|
||||
}
|
||||
|
||||
.quota-label {
|
||||
@@ -107,15 +150,29 @@ defineEmits(['call']);
|
||||
|
||||
.quota-value {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: var(--color-neutral-900);
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.quota-available {
|
||||
color: var(--color-success-600);
|
||||
}
|
||||
|
||||
.quota-waiting {
|
||||
color: var(--color-warning-600);
|
||||
}
|
||||
|
||||
.quota-callable {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--color-primary-600);
|
||||
margin-top: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quota-used {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
@@ -139,5 +196,18 @@ defineEmits(['call']);
|
||||
.call-buttons {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.quota-info {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.quota-item {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.quota-value {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -18,16 +18,6 @@
|
||||
{{ status.label }} ({{ status.count }})
|
||||
</v-chip>
|
||||
</v-chip-group>
|
||||
|
||||
<!-- Search Field -->
|
||||
<v-text-field
|
||||
v-model="searchModel"
|
||||
placeholder="Cari barcode, nomor antrian..."
|
||||
density="compact"
|
||||
hide-details
|
||||
class="search-field"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Filters -->
|
||||
@@ -200,6 +190,10 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
waitingCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
showDiproses: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -295,6 +289,16 @@ const statusOptions = computed(() => {
|
||||
});
|
||||
}
|
||||
|
||||
// Tambahkan kategori "Menunggu" (pasien yang sudah dipanggil tapi belum check-in)
|
||||
if (props.waitingCount > 0) {
|
||||
baseOptions.push({
|
||||
value: 'waiting',
|
||||
label: 'Menunggu',
|
||||
count: props.waitingCount,
|
||||
icon: 'mdi-clock-outline'
|
||||
});
|
||||
}
|
||||
|
||||
baseOptions.push(
|
||||
{ value: 'terlambat', label: props.statusLabels.terlambat, count: props.terlambatCount },
|
||||
{ value: 'pending', label: props.statusLabels.pending, count: props.pendingCount }
|
||||
@@ -459,11 +463,6 @@ const handleAction = (patient, action) => {
|
||||
border-color: var(--color-secondary-600);
|
||||
}
|
||||
|
||||
.search-field {
|
||||
max-width: 300px;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.advanced-filters {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
@@ -543,11 +542,6 @@ const handleAction = (patient, action) => {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.search-field {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.advanced-filters {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -55,9 +55,13 @@ export const useQueue = (adminType = "loket") => {
|
||||
});
|
||||
|
||||
const waitingPatients = computed(() => stagePatients.value.waiting || []);
|
||||
|
||||
// Pasien yang belum dipanggil (status "menunggu")
|
||||
const menungguPatients = computed(() => stagePatients.value.menunggu || []);
|
||||
|
||||
const nextPatient = computed(() => {
|
||||
return waitingPatients.value[0] || null;
|
||||
// Prioritaskan pasien menunggu, baru waiting
|
||||
return menungguPatients.value[0] || waitingPatients.value[0] || null;
|
||||
});
|
||||
|
||||
// Total pasien hanya untuk stage admin ini
|
||||
@@ -185,6 +189,7 @@ export const useQueue = (adminType = "loket") => {
|
||||
terlambatPatients,
|
||||
pendingPatients,
|
||||
waitingPatients,
|
||||
menungguPatients,
|
||||
nextPatient,
|
||||
totalPasien,
|
||||
quotaUsed,
|
||||
|
||||
+276
-5
@@ -42,12 +42,12 @@
|
||||
<v-btn
|
||||
block
|
||||
class="py-6"
|
||||
color="primary-600"
|
||||
color="warning"
|
||||
:disabled="!currentProcessingPatient"
|
||||
@click="showKlinikDialog = true"
|
||||
@click="openKlinikRuangDialog()"
|
||||
>
|
||||
<v-icon start>mdi-hospital-building</v-icon>
|
||||
Buat Antrean Klinik
|
||||
<v-icon start>mdi-door</v-icon>
|
||||
Buat Antrean Ruang
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<v-card-text class="pa-4">
|
||||
<!-- Header with Filters -->
|
||||
<div class="data-header mb-4">
|
||||
<div class="section-label">DATA PASIEN</div>
|
||||
<div class="section-label">CARI PASIEN</div>
|
||||
|
||||
<div class="filters">
|
||||
<!-- Search Field -->
|
||||
@@ -137,6 +137,90 @@
|
||||
@select="changeKlinik"
|
||||
/>
|
||||
|
||||
<!-- Dialog Klinik Ruang -->
|
||||
<v-dialog v-model="showKlinikRuangDialog" max-width="900px" persistent scrollable>
|
||||
<v-card class="dialog-card">
|
||||
<v-card-title class="dialog-header dialog-header-warning">
|
||||
<span class="headline-4">Pilih Klinik Ruang</span>
|
||||
<v-btn icon variant="text" size="small" class="btn-close" @click="closeKlinikRuangDialog">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider/>
|
||||
|
||||
<v-card-text class="dialog-content">
|
||||
<div v-if="currentProcessingPatient" class="patient-card mb-4">
|
||||
<h4 class="headline-5 mb-3">Informasi Pasien</h4>
|
||||
<v-row dense>
|
||||
<v-col cols="6">
|
||||
<div class="detail-item">
|
||||
<span class="caption-2 text-muted">Barcode</span>
|
||||
<span class="body-2 text-semibold">{{ currentProcessingPatient.barcode }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<div class="detail-item">
|
||||
<span class="caption-2 text-muted">No. Antrian</span>
|
||||
<span class="body-2 text-semibold">{{ currentProcessingPatient.noAntrian }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-text-field
|
||||
v-model="klinikRuangSearch"
|
||||
placeholder="Cari Klinik Ruang..."
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<v-expansion-panels class="expansion-panels">
|
||||
<v-expansion-panel
|
||||
v-for="klinikRuang in filteredKlinikRuang"
|
||||
:key="klinikRuang.id"
|
||||
class="expansion-panel"
|
||||
>
|
||||
<v-expansion-panel-title class="expansion-title">
|
||||
<div class="expansion-header">
|
||||
<v-chip size="small" color="warning" class="mr-2">
|
||||
{{ klinikRuang.kodeKlinik }}
|
||||
</v-chip>
|
||||
<span class="body-2 text-semibold">{{ klinikRuang.namaKlinik }}</span>
|
||||
</div>
|
||||
<template #actions>
|
||||
<v-chip size="x-small" variant="outlined">
|
||||
{{ klinikRuang.ruangList.length }} Ruang
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-expansion-panel-title>
|
||||
|
||||
<v-expansion-panel-text>
|
||||
<div class="ruang-list">
|
||||
<div
|
||||
v-for="ruang in klinikRuang.ruangList"
|
||||
:key="ruang.nomorRuang"
|
||||
class="ruang-item"
|
||||
@click="buatAntreanKlinikRuang(klinikRuang, ruang)"
|
||||
>
|
||||
<v-icon color="warning" size="20">mdi-door</v-icon>
|
||||
<div class="ruang-info">
|
||||
<span class="body-3 text-semibold">Ruang {{ ruang.nomorRuang }} - {{ ruang.namaRuang }}</span>
|
||||
<span class="caption-2 text-muted">Screen: {{ ruang.nomorScreen }}</span>
|
||||
</div>
|
||||
<v-icon size="20" color="grey">mdi-chevron-right</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<AppSnackbar
|
||||
v-model="snackbar"
|
||||
@@ -149,6 +233,8 @@
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { useQueue } from "@/composables/useQueue";
|
||||
import { useQueueStore } from "@/stores/queueStore";
|
||||
import { useMasterStore } from "@/stores/masterStore";
|
||||
import PageHeader from "@/components/common/PageHeader.vue";
|
||||
import CurrentPatientCard from "@/components/features/queue/CurrentPatientCard.vue";
|
||||
import QueueActionsCard from "@/components/features/queue/QueueActionsCard.vue";
|
||||
@@ -156,6 +242,9 @@ import PatientDataTable from "@/components/features/queue/TabelPatientData.vue";
|
||||
import SelectionDialog from "@/components/common/SelectionDialog.vue";
|
||||
import AppSnackbar from "@/components/common/AppSnackbar.vue";
|
||||
|
||||
const masterStore = useMasterStore();
|
||||
const queueStore = useQueueStore();
|
||||
|
||||
const {
|
||||
snackbar,
|
||||
snackbarText,
|
||||
@@ -198,6 +287,24 @@ const selectedStatus = ref("all");
|
||||
const searchQuery = ref("");
|
||||
const selectedFastTrack = ref(null);
|
||||
|
||||
// Dialog Klinik Ruang
|
||||
const showKlinikRuangDialog = ref(false);
|
||||
const klinikRuangSearch = ref("");
|
||||
|
||||
// Get klinik ruang data from masterStore
|
||||
const klinikRuangList = computed(() => {
|
||||
return masterStore.ruangData || [];
|
||||
});
|
||||
|
||||
const filteredKlinikRuang = computed(() => {
|
||||
if (!klinikRuangSearch.value) return klinikRuangList.value;
|
||||
return klinikRuangList.value.filter(
|
||||
(k) =>
|
||||
k.namaKlinik.toLowerCase().includes(klinikRuangSearch.value.toLowerCase()) ||
|
||||
k.kodeKlinik.toLowerCase().includes(klinikRuangSearch.value.toLowerCase())
|
||||
);
|
||||
});
|
||||
|
||||
// Fast Track options from all patients
|
||||
const fastTrackOptions = computed(() => {
|
||||
const normalizedValues = allPatients.value
|
||||
@@ -276,6 +383,32 @@ const handleCallPatient = () => {
|
||||
// Placeholder for text-to-speech integration
|
||||
}
|
||||
};
|
||||
|
||||
const openKlinikRuangDialog = () => {
|
||||
showKlinikRuangDialog.value = true;
|
||||
};
|
||||
|
||||
const closeKlinikRuangDialog = () => {
|
||||
showKlinikRuangDialog.value = false;
|
||||
klinikRuangSearch.value = "";
|
||||
};
|
||||
|
||||
const buatAntreanKlinikRuang = (klinikRuang, ruang) => {
|
||||
if (!currentProcessingPatient.value) return;
|
||||
|
||||
const result = queueStore.createAntreanKlinikRuang(
|
||||
klinikRuang,
|
||||
ruang,
|
||||
currentProcessingPatient.value,
|
||||
"klinik"
|
||||
);
|
||||
|
||||
snackbarText.value = result.message;
|
||||
snackbarColor.value = result.success ? "success" : "error";
|
||||
snackbar.value = true;
|
||||
|
||||
closeKlinikRuangDialog();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -339,6 +472,144 @@ const handleCallPatient = () => {
|
||||
background: var(--color-neutral-100);
|
||||
}
|
||||
|
||||
/* Dialog Klinik Ruang Styles */
|
||||
.dialog-card {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
background: linear-gradient(135deg, var(--color-warning-600) 0%, var(--color-primary-700) 100%);
|
||||
color: var(--color-neutral-100);
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dialog-header-warning {
|
||||
background: linear-gradient(135deg, var(--color-warning-600) 0%, var(--color-primary-700) 100%);
|
||||
}
|
||||
|
||||
.headline-4 {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.headline-5 {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--color-neutral-900);
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
color: var(--color-neutral-100) !important;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
padding: 24px !important;
|
||||
background: var(--color-neutral-300);
|
||||
}
|
||||
|
||||
.patient-card {
|
||||
background: var(--color-neutral-100);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--color-neutral-400);
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.caption-2 {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.body-2 {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.body-3 {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.text-semibold {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--color-neutral-700);
|
||||
}
|
||||
|
||||
.expansion-panels {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.expansion-panel {
|
||||
background: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-400);
|
||||
border-radius: 12px !important;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.expansion-panel::before {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.expansion-title {
|
||||
background: var(--color-neutral-100);
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.expansion-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ruang-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.ruang-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: var(--color-neutral-300);
|
||||
border: 1px solid var(--color-neutral-400);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.ruang-item:hover {
|
||||
border-color: var(--color-warning-600);
|
||||
background: rgba(255, 185, 95, 0.15);
|
||||
}
|
||||
|
||||
.ruang-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.loket-container {
|
||||
padding: 12px;
|
||||
|
||||
+367
-8
@@ -30,14 +30,15 @@
|
||||
class="mt-3"
|
||||
:total-quota="150"
|
||||
:used-quota="quotaUsed"
|
||||
:menunggu-count="menungguCount"
|
||||
:has-next="!!nextPatient"
|
||||
@call="handleCall"
|
||||
/>
|
||||
|
||||
<!-- Create Queue Buttons -->
|
||||
<div class="create-buttons mt-3">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="6" class="pr-2">
|
||||
<v-row dense>
|
||||
<v-col cols="4">
|
||||
<v-btn
|
||||
block
|
||||
class="py-6"
|
||||
@@ -50,7 +51,20 @@
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6" class="pl-2">
|
||||
<v-col cols="4">
|
||||
<v-btn
|
||||
block
|
||||
class="py-6"
|
||||
color="warning"
|
||||
:disabled="!currentProcessingPatient"
|
||||
@click="openKlinikRuangDialog()"
|
||||
>
|
||||
<v-icon start>mdi-door</v-icon>
|
||||
Buat Antrean Ruang
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="4">
|
||||
<v-btn
|
||||
block
|
||||
class="py-6 text-white"
|
||||
@@ -73,7 +87,7 @@
|
||||
<v-card-text class="pa-4">
|
||||
<!-- Header with Filters -->
|
||||
<div class="data-header mb-4">
|
||||
<div class="section-label">DATA PASIEN</div>
|
||||
<div class="section-label">CARI PASIEN</div>
|
||||
|
||||
<div class="filters">
|
||||
<!-- Search Field -->
|
||||
@@ -95,6 +109,7 @@
|
||||
v-model:selected-fast-track="selectedFastTrack"
|
||||
:di-loket-count="diLoketCount"
|
||||
:diproses-count="currentProcessingPatient ? 1 : 0"
|
||||
:waiting-count="waitingCount"
|
||||
:terlambat-count="(terlambatPatients || []).length"
|
||||
:pending-count="(pendingPatients || []).length"
|
||||
:show-diproses="false"
|
||||
@@ -136,6 +151,90 @@
|
||||
@select="changeKlinik"
|
||||
/>
|
||||
|
||||
<!-- Dialog Klinik Ruang -->
|
||||
<v-dialog v-model="showKlinikRuangDialog" max-width="900px" persistent scrollable>
|
||||
<v-card class="dialog-card">
|
||||
<v-card-title class="dialog-header dialog-header-warning">
|
||||
<span class="headline-4">Pilih Klinik Ruang</span>
|
||||
<v-btn icon variant="text" size="small" class="btn-close" @click="closeKlinikRuangDialog">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider/>
|
||||
|
||||
<v-card-text class="dialog-content">
|
||||
<div v-if="currentProcessingPatient" class="patient-card mb-4">
|
||||
<h4 class="headline-5 mb-3">Informasi Pasien</h4>
|
||||
<v-row dense>
|
||||
<v-col cols="6">
|
||||
<div class="detail-item">
|
||||
<span class="caption-2 text-muted">Barcode</span>
|
||||
<span class="body-2 text-semibold">{{ currentProcessingPatient.barcode }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<div class="detail-item">
|
||||
<span class="caption-2 text-muted">No. Antrian</span>
|
||||
<span class="body-2 text-semibold">{{ currentProcessingPatient.noAntrian }}</span>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-text-field
|
||||
v-model="klinikRuangSearch"
|
||||
placeholder="Cari Klinik Ruang..."
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<v-expansion-panels class="expansion-panels">
|
||||
<v-expansion-panel
|
||||
v-for="klinikRuang in filteredKlinikRuang"
|
||||
:key="klinikRuang.id"
|
||||
class="expansion-panel"
|
||||
>
|
||||
<v-expansion-panel-title class="expansion-title">
|
||||
<div class="expansion-header">
|
||||
<v-chip size="small" color="warning" class="mr-2">
|
||||
{{ klinikRuang.kodeKlinik }}
|
||||
</v-chip>
|
||||
<span class="body-2 text-semibold">{{ klinikRuang.namaKlinik }}</span>
|
||||
</div>
|
||||
<template #actions>
|
||||
<v-chip size="x-small" variant="outlined">
|
||||
{{ klinikRuang.ruangList.length }} Ruang
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-expansion-panel-title>
|
||||
|
||||
<v-expansion-panel-text>
|
||||
<div class="ruang-list">
|
||||
<div
|
||||
v-for="ruang in klinikRuang.ruangList"
|
||||
:key="ruang.nomorRuang"
|
||||
class="ruang-item"
|
||||
@click="buatAntreanKlinikRuang(klinikRuang, ruang)"
|
||||
>
|
||||
<v-icon color="warning" size="20">mdi-door</v-icon>
|
||||
<div class="ruang-info">
|
||||
<span class="body-3 text-semibold">Ruang {{ ruang.nomorRuang }} - {{ ruang.namaRuang }}</span>
|
||||
<span class="caption-2 text-muted">Screen: {{ ruang.nomorScreen }}</span>
|
||||
</div>
|
||||
<v-icon size="20" color="grey">mdi-chevron-right</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<AppSnackbar
|
||||
v-model="snackbar"
|
||||
@@ -148,6 +247,8 @@
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { useQueue } from "@/composables/useQueue";
|
||||
import { useQueueStore } from "@/stores/queueStore";
|
||||
import { useMasterStore } from "@/stores/masterStore";
|
||||
import PageHeader from "@/components/common/PageHeader.vue";
|
||||
import CurrentPatientCard from "@/components/features/queue/CurrentPatientCard.vue";
|
||||
import QueueActionsCard from "@/components/features/queue/QueueActionsCard.vue";
|
||||
@@ -155,7 +256,10 @@ import PatientDataTable from "@/components/features/queue/TabelPatientData.vue";
|
||||
import SelectionDialog from "@/components/common/SelectionDialog.vue";
|
||||
import AppSnackbar from "@/components/common/AppSnackbar.vue";
|
||||
|
||||
const {
|
||||
const masterStore = useMasterStore();
|
||||
const queueStore = useQueueStore();
|
||||
|
||||
const {
|
||||
snackbar,
|
||||
snackbarText,
|
||||
snackbarColor,
|
||||
@@ -169,6 +273,8 @@ const {
|
||||
diLoketPatients,
|
||||
terlambatPatients,
|
||||
pendingPatients,
|
||||
waitingPatients,
|
||||
menungguPatients,
|
||||
nextPatient,
|
||||
quotaUsed,
|
||||
filteredKliniks,
|
||||
@@ -197,6 +303,24 @@ const selectedStatus = ref("all");
|
||||
const searchQuery = ref("");
|
||||
const selectedFastTrack = ref(null);
|
||||
|
||||
// Dialog Klinik Ruang
|
||||
const showKlinikRuangDialog = ref(false);
|
||||
const klinikRuangSearch = ref("");
|
||||
|
||||
// Get klinik ruang data from masterStore
|
||||
const klinikRuangList = computed(() => {
|
||||
return masterStore.ruangData || [];
|
||||
});
|
||||
|
||||
const filteredKlinikRuang = computed(() => {
|
||||
if (!klinikRuangSearch.value) return klinikRuangList.value;
|
||||
return klinikRuangList.value.filter(
|
||||
(k) =>
|
||||
k.namaKlinik.toLowerCase().includes(klinikRuangSearch.value.toLowerCase()) ||
|
||||
k.kodeKlinik.toLowerCase().includes(klinikRuangSearch.value.toLowerCase())
|
||||
);
|
||||
});
|
||||
|
||||
// Fast Track options from all patients
|
||||
const fastTrackOptions = computed(() => {
|
||||
const normalizedValues = allPatientsForStage.value
|
||||
@@ -207,25 +331,39 @@ const fastTrackOptions = computed(() => {
|
||||
});
|
||||
|
||||
// Combine all patients with status - PRESERVE ALL PROPERTIES
|
||||
// Hanya tampilkan pasien yang sudah dipanggil (bukan status "menunggu")
|
||||
const allPatientsForStage = computed(() => {
|
||||
const currentPatientNo = currentProcessingPatient.value?.no;
|
||||
|
||||
// Pasien yang sudah di loket (sudah check-in)
|
||||
const diLoket = (diLoketPatients.value || []).map((p) => ({
|
||||
...p, // Spread all properties first
|
||||
// If this patient is currently being processed, mark as "diproses", otherwise "diloket"
|
||||
status: p.no === currentPatientNo ? "diproses" : "diloket",
|
||||
}));
|
||||
|
||||
// Pasien terlambat
|
||||
const terlambat = (terlambatPatients.value || []).map((p) => ({
|
||||
...p,
|
||||
status: "terlambat",
|
||||
}));
|
||||
|
||||
// Pasien pending
|
||||
const pending = (pendingPatients.value || []).map((p) => ({
|
||||
...p,
|
||||
status: "pending",
|
||||
}));
|
||||
|
||||
// Pasien dengan status "waiting" (sudah dipanggil tapi belum check-in)
|
||||
// Ini adalah kategori baru "Menunggu" di tabel Admin Loket
|
||||
const waiting = (waitingPatients.value || []).map((p) => ({
|
||||
...p,
|
||||
status: "waiting", // Tampilkan sebagai "waiting" atau "menunggu" di UI
|
||||
}));
|
||||
|
||||
// Do not show waiting patients in the table until called
|
||||
const combined = [...diLoket, ...terlambat, ...pending];
|
||||
// Jangan tampilkan pasien dengan status "menunggu" (belum dipanggil) di tabel
|
||||
// Hanya tampilkan pasien yang sudah dipanggil oleh admin loket
|
||||
const combined = [...diLoket, ...waiting, ...terlambat, ...pending];
|
||||
|
||||
// Debug: check if fastTrack property exists
|
||||
console.log("📊 AdminLoket - allPatientsForStage:", combined.length);
|
||||
@@ -248,6 +386,16 @@ const diLoketCount = computed(() => {
|
||||
return (diLoketPatients.value || []).filter(p => p.no !== currentPatientNo).length;
|
||||
});
|
||||
|
||||
// Count menunggu patients (belum dipanggil)
|
||||
const menungguCount = computed(() => {
|
||||
return (menungguPatients.value || []).length;
|
||||
});
|
||||
|
||||
// Count waiting patients (sudah dipanggil tapi belum check-in)
|
||||
const waitingCount = computed(() => {
|
||||
return (waitingPatients.value || []).length;
|
||||
});
|
||||
|
||||
// Next queue info for CurrentPatientCard
|
||||
const nextQueueInfo = computed(() => {
|
||||
const currentPatientNo = currentProcessingPatient.value?.no;
|
||||
@@ -288,6 +436,32 @@ const handleCallPatient = () => {
|
||||
// Placeholder for text-to-speech integration
|
||||
}
|
||||
};
|
||||
|
||||
const openKlinikRuangDialog = () => {
|
||||
showKlinikRuangDialog.value = true;
|
||||
};
|
||||
|
||||
const closeKlinikRuangDialog = () => {
|
||||
showKlinikRuangDialog.value = false;
|
||||
klinikRuangSearch.value = "";
|
||||
};
|
||||
|
||||
const buatAntreanKlinikRuang = (klinikRuang, ruang) => {
|
||||
if (!currentProcessingPatient.value) return;
|
||||
|
||||
const result = queueStore.createAntreanKlinikRuang(
|
||||
klinikRuang,
|
||||
ruang,
|
||||
currentProcessingPatient.value,
|
||||
"loket"
|
||||
);
|
||||
|
||||
snackbarText.value = result.message;
|
||||
snackbarColor.value = result.success ? "success" : "error";
|
||||
snackbar.value = true;
|
||||
|
||||
closeKlinikRuangDialog();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -295,19 +469,66 @@ const handleCallPatient = () => {
|
||||
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;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
margin: 0 -8px;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-grid > .v-col {
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
min-width: 0; /* Prevent flex item overflow */
|
||||
}
|
||||
|
||||
.content-grid > .v-col:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.content-grid > .v-col:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.create-buttons {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.create-buttons :deep(.v-row) {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.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) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.create-buttons :deep(.v-col:last-child) {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.create-buttons .v-btn {
|
||||
text-transform: none;
|
||||
font-weight: 600;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sticky-wrapper {
|
||||
@@ -375,6 +596,144 @@ const handleCallPatient = () => {
|
||||
background: var(--color-neutral-100);
|
||||
}
|
||||
|
||||
/* Dialog Klinik Ruang Styles */
|
||||
.dialog-card {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
background: linear-gradient(135deg, var(--color-warning-600) 0%, var(--color-primary-700) 100%);
|
||||
color: var(--color-neutral-100);
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dialog-header-warning {
|
||||
background: linear-gradient(135deg, var(--color-warning-600) 0%, var(--color-primary-700) 100%);
|
||||
}
|
||||
|
||||
.headline-4 {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.headline-5 {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--color-neutral-900);
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
color: var(--color-neutral-100) !important;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
padding: 24px !important;
|
||||
background: var(--color-neutral-300);
|
||||
}
|
||||
|
||||
.patient-card {
|
||||
background: var(--color-neutral-100);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--color-neutral-400);
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.caption-2 {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.body-2 {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.body-3 {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.text-semibold {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--color-neutral-700);
|
||||
}
|
||||
|
||||
.expansion-panels {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.expansion-panel {
|
||||
background: var(--color-neutral-100);
|
||||
border: 1px solid var(--color-neutral-400);
|
||||
border-radius: 12px !important;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.expansion-panel::before {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.expansion-title {
|
||||
background: var(--color-neutral-100);
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.expansion-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ruang-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.ruang-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: var(--color-neutral-300);
|
||||
border: 1px solid var(--color-neutral-400);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.ruang-item:hover {
|
||||
border-color: var(--color-warning-600);
|
||||
background: rgba(255, 185, 95, 0.15);
|
||||
}
|
||||
|
||||
.ruang-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.loket-container {
|
||||
padding: 12px;
|
||||
|
||||
@@ -222,7 +222,7 @@ const displayedRuang = computed(() => {
|
||||
|
||||
return ruangListForKlinik.value.map(ruang => {
|
||||
const queues = klinikPatients.value
|
||||
.filter(p => p.klinik === ruang.namaKlinik)
|
||||
.filter(p => p.klinik === ruang.namaKlinik && (p.ruang === ruang.namaRuang || !p.ruang))
|
||||
.sort((a, b) => {
|
||||
const statusPriority = {
|
||||
'di-loket': 1,
|
||||
|
||||
+99
-19
@@ -222,6 +222,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
return {
|
||||
all: patients,
|
||||
waiting: patients.filter(p => p.status === 'waiting'),
|
||||
menunggu: patients.filter(p => p.status === 'menunggu'), // Pasien yang belum dipanggil
|
||||
diLoket: patients.filter(p => p.status === 'di-loket'),
|
||||
terlambat: patients.filter(p => p.status === 'terlambat'),
|
||||
pending: patients.filter(p => p.status === 'pending'),
|
||||
@@ -253,7 +254,10 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
};
|
||||
|
||||
const targetStage = stageMap[adminType];
|
||||
// Prioritaskan pasien dengan status 'menunggu' (yang belum dipanggil)
|
||||
const nextPatient = allPatients.value.find(p =>
|
||||
p.status === 'menunggu' && p.processStage === targetStage
|
||||
) || allPatients.value.find(p =>
|
||||
p.status === 'waiting' && p.processStage === targetStage
|
||||
);
|
||||
|
||||
@@ -261,17 +265,28 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
return { success: false, message: "Tidak ada pasien selanjutnya" };
|
||||
}
|
||||
|
||||
if (quotaUsed.value >= 150) {
|
||||
return { success: false, message: "Quota sudah penuh" };
|
||||
}
|
||||
// Hitung kuota yang tersedia
|
||||
const menungguCount = allPatients.value.filter(p =>
|
||||
p.status === 'menunggu' && p.processStage === targetStage
|
||||
).length;
|
||||
const diLoketCount = allPatients.value.filter(p =>
|
||||
p.status === 'di-loket' && p.processStage === targetStage
|
||||
).length;
|
||||
const availableQuota = 150 - diLoketCount;
|
||||
|
||||
// Update dengan cara yang Vue reactive
|
||||
const index = allPatients.value.findIndex(p => p.no === nextPatient.no);
|
||||
if (index !== -1) {
|
||||
allPatients.value[index] = { ...allPatients.value[index], status: "di-loket" };
|
||||
if (availableQuota <= 0) {
|
||||
return { success: false, message: "Kuota sudah penuh" };
|
||||
}
|
||||
|
||||
quotaUsed.value++;
|
||||
if (menungguCount === 0) {
|
||||
return { success: false, message: "Tidak ada pasien yang menunggu untuk dipanggil" };
|
||||
}
|
||||
|
||||
// Update status dari 'menunggu' menjadi 'waiting' (sudah dipanggil, bisa check-in)
|
||||
const index = allPatients.value.findIndex(p => p.no === nextPatient.no);
|
||||
if (index !== -1) {
|
||||
allPatients.value[index] = { ...allPatients.value[index], status: "waiting" };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -287,30 +302,49 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
};
|
||||
|
||||
const targetStage = stageMap[adminType];
|
||||
// Prioritaskan pasien dengan status 'menunggu' (yang belum dipanggil)
|
||||
const menungguList = allPatients.value.filter(p =>
|
||||
p.status === 'menunggu' && p.processStage === targetStage
|
||||
);
|
||||
const waitingList = allPatients.value.filter(p =>
|
||||
p.status === 'waiting' && p.processStage === targetStage
|
||||
);
|
||||
|
||||
const patientsToCall = waitingList.slice(0, count);
|
||||
// Gabungkan: menunggu dulu, baru waiting
|
||||
const combinedList = [...menungguList, ...waitingList];
|
||||
|
||||
if (patientsToCall.length === 0) {
|
||||
if (combinedList.length === 0) {
|
||||
return { success: false, message: "Tidak ada pasien yang menunggu" };
|
||||
}
|
||||
|
||||
if (quotaUsed.value + patientsToCall.length > 150) {
|
||||
return { success: false, message: "Quota tidak mencukupi" };
|
||||
// Hitung kuota yang tersedia
|
||||
const diLoketCount = allPatients.value.filter(p =>
|
||||
p.status === 'di-loket' && p.processStage === targetStage
|
||||
).length;
|
||||
const availableQuota = 150 - diLoketCount;
|
||||
|
||||
// Kuota yang bisa dipanggil = min(count yang diminta, jumlah pasien menunggu, kuota tersedia)
|
||||
const maxCallable = Math.min(count, menungguList.length, availableQuota);
|
||||
|
||||
if (maxCallable <= 0) {
|
||||
if (menungguList.length === 0) {
|
||||
return { success: false, message: "Tidak ada pasien yang menunggu untuk dipanggil" };
|
||||
}
|
||||
if (availableQuota <= 0) {
|
||||
return { success: false, message: "Kuota sudah penuh. Tidak bisa memanggil pasien lagi" };
|
||||
}
|
||||
}
|
||||
|
||||
// Update dengan cara yang Vue reactive
|
||||
const patientsToCall = combinedList.slice(0, maxCallable);
|
||||
|
||||
// Update status dari 'menunggu' menjadi 'waiting' (sudah dipanggil, bisa check-in)
|
||||
patientsToCall.forEach((patient) => {
|
||||
const index = allPatients.value.findIndex(p => p.no === patient.no);
|
||||
if (index !== -1) {
|
||||
allPatients.value[index] = { ...allPatients.value[index], status: "di-loket" };
|
||||
allPatients.value[index] = { ...allPatients.value[index], status: "waiting" };
|
||||
}
|
||||
});
|
||||
|
||||
quotaUsed.value += patientsToCall.length;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Memanggil ${patientsToCall.length} pasien ke loket`,
|
||||
@@ -495,6 +529,41 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const createAntreanKlinikRuang = (klinikRuang, ruang, patient = null, adminType = 'klinik') => {
|
||||
const newNo = allPatients.value.length + 1;
|
||||
const timestamp = new Date();
|
||||
const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`;
|
||||
|
||||
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",
|
||||
klinik: klinikRuang.namaKlinik,
|
||||
ruang: ruang.namaRuang,
|
||||
kodeKlinik: klinikRuang.kodeKlinik,
|
||||
nomorRuang: ruang.nomorRuang,
|
||||
nomorScreen: ruang.nomorScreen,
|
||||
fastTrack: "TIDAK",
|
||||
pembayaran: patient ? patient.pembayaran : "UMUM",
|
||||
status: "waiting",
|
||||
processStage: "klinik",
|
||||
createdAt: timestamp.toISOString(),
|
||||
referencePatient: patient ? patient.noAntrian : null,
|
||||
};
|
||||
|
||||
allPatients.value.push(newPatient);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Antrean ${klinikRuang.namaKlinik} Ruang ${ruang.nomorRuang} berhasil dibuat dan akan ditampilkan di layar antrian`,
|
||||
patient: newPatient,
|
||||
};
|
||||
};
|
||||
|
||||
const changeKlinik = (patient, newKlinik, adminType = 'loket') => {
|
||||
const patientIndex = allPatients.value.findIndex((p) => p.no === patient.no);
|
||||
|
||||
@@ -548,8 +617,9 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
// Format nomor antrean: UMXXXX | Onsite - barcode
|
||||
const noAntrian = `UM${String(newNo).padStart(4, "0")} | Onsite - ${barcode}`;
|
||||
|
||||
// Determine status based on visit type
|
||||
const status = visitType === 'SEKARANG' ? 'waiting' : 'pending';
|
||||
// Status awal untuk pasien dari anjungan adalah "menunggu" (belum dipanggil)
|
||||
// Hanya setelah dipanggil oleh admin loket, status berubah menjadi "waiting" (bisa check-in)
|
||||
const status = 'menunggu';
|
||||
|
||||
const newPatient = {
|
||||
no: newNo,
|
||||
@@ -641,7 +711,16 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const patient = allPatients.value[patientIndex];
|
||||
console.log('✅ Patient found:', patient);
|
||||
|
||||
// Only allow check-in if status is waiting or pending
|
||||
// Only allow check-in if status is waiting (sudah dipanggil) or pending
|
||||
// Pasien dengan status "menunggu" belum bisa check-in (belum dipanggil)
|
||||
if (patient.status === 'menunggu') {
|
||||
console.log('⚠️ Patient status is menunggu (belum dipanggil):', patient.status);
|
||||
return {
|
||||
success: false,
|
||||
message: `Pasien belum dipanggil oleh admin loket. Mohon menunggu hingga nomor antrean Anda dipanggil.`
|
||||
};
|
||||
}
|
||||
|
||||
if (patient.status !== 'waiting' && patient.status !== 'pending') {
|
||||
console.log('⚠️ Patient status is not waiting/pending:', patient.status);
|
||||
return {
|
||||
@@ -685,6 +764,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
processPatient,
|
||||
createAntreanKlinik,
|
||||
createAntreanPenunjang,
|
||||
createAntreanKlinikRuang,
|
||||
changeKlinik,
|
||||
processNextQueue,
|
||||
getPatientsByStage,
|
||||
|
||||
Reference in New Issue
Block a user