update anjungan fast track dan tampilan ruang

This commit is contained in:
bagus-arie05
2026-01-13 08:19:19 +07:00
parent 48e4aabd4b
commit 3817b2d3f7
4 changed files with 191 additions and 100 deletions

No files matched your search

+40 -10
View File
@@ -108,20 +108,20 @@
<!-- Compact status indicators - only show icons if called -->
<v-chip
v-if="patient.calledPemeriksaanAwal"
size="x-small"
size="small"
color="primary"
class="status-chip"
class="status-chip status-chip-pawal"
>
<v-icon start size="10">mdi-check-circle</v-icon>
<v-icon start size="18">mdi-check-circle</v-icon>
P.Awal
</v-chip>
<v-chip
v-if="patient.calledTindakan"
size="x-small"
size="small"
color="secondary"
class="status-chip"
class="status-chip status-chip-tindakan"
>
<v-icon start size="10">mdi-check-circle</v-icon>
<v-icon start size="18">mdi-check-circle</v-icon>
Tindakan
</v-chip>
</div>
@@ -1417,10 +1417,11 @@ onMounted(() => {
}
.patient-queue-number {
font-size: 15px;
font-size: 20px;
font-weight: 700;
color: var(--color-neutral-900);
line-height: 1.2;
letter-spacing: 0.5px;
}
.patient-queue-status-chips {
@@ -1428,17 +1429,46 @@ onMounted(() => {
gap: 4px;
align-items: center;
flex-wrap: wrap;
margin-top: 4px;
}
.status-chip {
font-size: 10px !important;
height: 18px !important;
font-weight: 700 !important;
height: 20px !important;
padding: 0 6px !important;
min-width: 60px !important;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08) !important;
border: 1px solid transparent !important;
letter-spacing: 0.1px !important;
}
.status-chip-pawal {
background: #6366f1 !important;
color: white !important;
border-color: #4f46e5 !important;
}
.status-chip-pawal:hover {
background: #4f46e5 !important;
box-shadow: 0 1px 3px rgba(99, 102, 241, 0.15) !important;
}
.status-chip-tindakan {
background: #14b8a6 !important;
color: white !important;
border-color: #0d9488 !important;
}
.status-chip-tindakan:hover {
background: #0d9488 !important;
box-shadow: 0 1px 3px rgba(20, 184, 166, 0.15) !important;
}
.status-chip .v-icon {
font-size: 10px !important;
margin-right: 2px;
font-size: 11px !important;
margin-right: 2px !important;
color: white !important;
}
/* Right Section: Actions */
+125 -83
View File
@@ -24,13 +24,9 @@
<div class="guide-item">
<span class="guide-number">2</span>
<span class="guide-text"
>Pilih: Daftar Sekarang atau Jadwal Lain</span
>Pilih jenis pembayaran dan buat nomor antrian</span
>
</div>
<div class="guide-item">
<span class="guide-number">3</span>
<span class="guide-text">Pilih Jenis Pembayaran</span>
</div>
</div>
</div>
</div>
@@ -237,39 +233,112 @@
<v-divider />
<v-card-text class="pa-4">
<v-row dense>
<v-col cols="6">
<v-btn
color="success-600"
class="text-white"
size="large"
block
@click="selectVisitType('SEKARANG')"
:disabled="isShift1Full(selectedClinic) || (isEksekutif && !selectedDoctor)"
:class="{ 'btn-disabled': isShift1Full(selectedClinic) || (isEksekutif && !selectedDoctor) }"
>
DAFTAR SEKARANG
</v-btn>
<p v-if="isShift1Full(selectedClinic)" class="disabled-hint">
Kuota hari ini habis
</p>
<p v-else-if="isEksekutif && !selectedDoctor" class="disabled-hint">
Pilih dokter terlebih dahulu
</p>
</v-col>
<v-col cols="6">
<!-- For Eksekutif: Show DAFTAR SEKARANG button -->
<div v-if="isEksekutif">
<v-row dense>
<v-col cols="12">
<v-btn
color="success-600"
class="text-white"
size="large"
block
@click="registerPatientDirectly('Eksekutif')"
:disabled="isShift1Full(selectedClinic) || !selectedDoctor"
:class="{ 'btn-disabled': isShift1Full(selectedClinic) || !selectedDoctor }"
>
DAFTAR SEKARANG
</v-btn>
<p v-if="isShift1Full(selectedClinic)" class="disabled-hint">
Kuota hari ini habis
</p>
<p v-else-if="!selectedDoctor" class="disabled-hint">
Pilih dokter terlebih dahulu
</p>
</v-col>
</v-row>
</div>
<!-- For Non-Eksekutif: Show 3 payment type buttons -->
<div v-else>
<v-row dense>
<v-col cols="4">
<v-btn
color="success-600"
class="text-white payment-type-btn"
size="large"
block
@click="registerPatientDirectly('BPJS')"
:disabled="isShift1Full(selectedClinic)"
:class="{ 'btn-disabled': isShift1Full(selectedClinic) }"
>
BPJS
</v-btn>
</v-col>
<v-col cols="4">
<v-btn
color="secondary-600"
class="text-white payment-type-btn"
size="large"
block
@click="registerPatientDirectly('UMUM')"
:disabled="isShift1Full(selectedClinic)"
:class="{ 'btn-disabled': isShift1Full(selectedClinic) }"
>
UMUM / JKMM / SPM / DLL
</v-btn>
</v-col>
<v-col cols="4">
<v-btn
color="primary-600"
class="text-white payment-type-btn"
size="large"
block
@click="selectVisitType('JADWAL_LAIN')"
>
JADWAL LAIN
</v-btn>
</v-col>
</v-row>
<p v-if="isShift1Full(selectedClinic)" class="disabled-hint mt-2">
Kuota hari ini habis
</p>
<!-- Fast Track button for non-eksekutif -->
<v-row dense class="mt-3">
<v-col cols="4" offset="4">
<v-btn
color="yellow-darken-2"
class="text-white"
size="large"
block
@click="registerPatientDirectly('FAST_TRACK')"
:disabled="isShift1Full(selectedClinic)"
:class="{ 'btn-disabled': isShift1Full(selectedClinic) }"
>
Fast Track
</v-btn>
<p v-if="isShift1Full(selectedClinic)" class="disabled-hint">
Kuota hari ini habis
</p>
</v-col>
</v-row>
</div>
<!-- JADWAL LAIN button for eksekutif -->
<v-row v-if="isEksekutif" dense class="mt-3">
<v-col cols="12">
<v-btn
color="primary-600"
class="text-white"
size="large"
block
@click="selectVisitType('JADWAL_LAIN')"
:disabled="isEksekutif && !selectedDoctor"
:class="{ 'btn-disabled': isEksekutif && !selectedDoctor }"
:disabled="!selectedDoctor"
:class="{ 'btn-disabled': !selectedDoctor }"
>
JADWAL LAIN
</v-btn>
<p v-if="isEksekutif && !selectedDoctor" class="disabled-hint">
<p v-if="!selectedDoctor" class="disabled-hint">
Pilih dokter terlebih dahulu
</p>
</v-col>
@@ -288,47 +357,6 @@
</v-card>
</v-dialog>
<v-dialog
v-model="showPaymentTypeDialog"
max-width="400"
@click:outside="showPaymentTypeDialog = false"
>
<v-card class="dialog-card">
<v-card-title class="dialog-header-simple"
>Jenis Pembayaran</v-card-title
>
<v-divider />
<v-card-text class="pa-4">
<v-btn
color="success-600"
class="mb-3 payment-btn text-white"
size="x-large"
block
@click="selectPaymentType('BPJS')"
>
BPJS
</v-btn>
<v-btn
color="secondary-600"
class="payment-btn text-white"
size="x-large"
block
@click="selectPaymentType('UMUM')"
>
UMUM / JKMM / SPM / DLL
</v-btn>
</v-card-text>
<v-card-actions class="justify-center pa-4 pt-0">
<v-btn
variant="outlined"
color="neutral-600"
@click="showPaymentTypeDialog = false"
>
Tutup
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Dialog Pemilihan Dokter untuk Pasien Eksekutif -->
<v-dialog
@@ -883,14 +911,12 @@ const filteredClinics = computed(() => {
});
const showVisitTypeDialog = ref(false);
const showPaymentTypeDialog = ref(false);
const showBookingFormDialog = ref(false);
const showDoctorSelectionDialog = ref(false);
const showPrintDialog = ref(false);
const lastRegisteredPatient = ref(null);
const selectedClinic = ref(null);
const selectedDoctor = ref(null);
const pendingPaymentType = ref(null);
const snackbar = ref(false);
const snackbarText = ref("");
const snackbarColor = ref("success");
@@ -1003,9 +1029,7 @@ const selectVisitType = (type) => {
showVisitTypeDialog.value = false;
if (type === "SEKARANG") {
showPaymentTypeDialog.value = true;
} else if (type === "JADWAL_LAIN") {
if (type === "JADWAL_LAIN") {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const tomorrowStr = tomorrow.toISOString().substring(0, 10);
@@ -1020,14 +1044,30 @@ const selectVisitType = (type) => {
}
};
const selectPaymentType = (paymentType) => {
showPaymentTypeDialog.value = false;
const registerPatientDirectly = async (paymentType) => {
// Validasi: untuk pasien Eksekutif, dokter harus dipilih
if (isEksekutif.value && !selectedDoctor.value) {
showSnackbar("Mohon pilih dokter terlebih dahulu", "error");
return;
}
// Validasi kuota
if (isShift1Full(selectedClinic.value)) {
showSnackbar("Kuota hari ini sudah habis", "error");
return;
}
showVisitTypeDialog.value = false;
// Untuk pasien Eksekutif, dokter sudah dipilih di dialog sebelumnya
// Untuk Reguler/BPJS, tidak perlu dokter
const namaDokter = isEksekutif.value ? selectedDoctor.value : null;
registerPatient('SEKARANG', paymentType, namaDokter);
// Handle Fast Track - paymentType akan menjadi 'UMUM' atau 'BPJS' tapi dengan flag fastTrack
const isFastTrack = paymentType === 'FAST_TRACK';
const actualPaymentType = isFastTrack ? 'UMUM' : paymentType;
await registerPatient('SEKARANG', actualPaymentType, namaDokter, isFastTrack);
};
// Fungsi ini tidak lagi digunakan karena dokter dipilih di dialog konfirmasi
@@ -1041,10 +1081,11 @@ const confirmDoctorSelection = () => {
showDoctorSelectionDialog.value = false;
// Register dengan dokter yang dipilih untuk kunjungan hari ini
registerPatient('SEKARANG', pendingPaymentType.value, selectedDoctor.value);
// Untuk eksekutif, paymentType adalah "Eksekutif"
registerPatient('SEKARANG', 'Eksekutif', selectedDoctor.value);
};
const registerPatient = async (visitType, paymentType, namaDokter) => {
const registerPatient = async (visitType, paymentType, namaDokter, isFastTrack = false) => {
try {
// Validasi bahwa fungsi registerPatientFromAnjungan ada
if (!queueStore) {
@@ -1068,12 +1109,14 @@ const registerPatient = async (visitType, paymentType, namaDokter) => {
visitType,
null,
'Shift 1',
namaDokter
namaDokter,
isFastTrack
);
if (result && result.success && result.patient) {
const doctorInfo = namaDokter ? ` dengan dokter ${namaDokter}` : '';
const message = `Pendaftaran ${selectedClinic.value.name} untuk kunjungan HARI INI dengan pembayaran ${paymentType}${doctorInfo} berhasil diproses. Nomor antrean: ${result.patient.noAntrian?.split(" |")[0] || result.patient.noAntrian || 'N/A'}`;
const fastTrackInfo = isFastTrack ? ' (Fast Track)' : '';
const message = `Pendaftaran ${selectedClinic.value.name} untuk kunjungan HARI INI dengan pembayaran ${paymentType}${fastTrackInfo}${doctorInfo} berhasil diproses. Nomor antrean: ${result.patient.noAntrian?.split(" |")[0] || result.patient.noAntrian || 'N/A'}`;
showSnackbar(message, "success");
// Simpan data pasien untuk print
@@ -1094,7 +1137,6 @@ const registerPatient = async (visitType, paymentType, namaDokter) => {
} finally {
selectedClinic.value = null;
selectedDoctor.value = null;
pendingPaymentType.value = null;
}
};
@@ -188,9 +188,12 @@ let timeInterval = null
const klinikPatients = computed(() => {
// Get patients from klinik-ruang stage (created from AdminKlinikRuang)
// Filter out finished patients (status: 'processed' or 'selesai')
return queueStore.allPatients.filter(p =>
p.processStage === 'klinik-ruang' &&
p.kodeKlinik === kodeKlinik.value
p.kodeKlinik === kodeKlinik.value &&
p.status !== 'processed' &&
p.status !== 'selesai'
)
})
@@ -789,12 +792,16 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
}
.tipe-label {
font-size: 12px;
font-size: 20px;
font-weight: 700;
color: var(--color-success-700);
margin-bottom: 8px;
letter-spacing: 0.5px;
margin-bottom: 12px;
letter-spacing: 1px;
text-transform: uppercase;
padding: 8px 12px;
background: var(--color-success-200);
border-radius: 8px;
display: inline-block;
}
.empty-state-small {
@@ -1036,6 +1043,11 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
font-size: clamp(56px, 4.17vw, 80px);
}
.tipe-label {
font-size: clamp(16px, 1.04vw, 20px);
padding: clamp(6px, 0.42vw, 8px) clamp(10px, 0.63vw, 12px);
}
.footer-stats-bar {
padding: clamp(12px, 1.04vw, 20px) clamp(24px, 2.08vw, 40px);
gap: clamp(16px, 1.67vw, 32px);
@@ -1125,6 +1137,11 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
font-size: 72px;
}
.tipe-label {
font-size: 18px;
padding: 6px 10px;
}
.next-item {
font-size: 20px;
padding: 6px 14px;
+5 -3
View File
@@ -1105,7 +1105,7 @@ export const useQueueStore = defineStore('queue', () => {
};
// Register patient from Anjungan (onsite registration)
const registerPatientFromAnjungan = (clinic, paymentType, visitType = 'SEKARANG', visitDate = null, shift = 'Shift 1', namaDokter = null) => {
const registerPatientFromAnjungan = (clinic, paymentType, visitType = 'SEKARANG', visitDate = null, shift = 'Shift 1', namaDokter = null, isFastTrack = false) => {
const newNo = allPatients.value.length > 0
? Math.max(...allPatients.value.map(p => p.no)) + 1
: 1;
@@ -1124,8 +1124,10 @@ export const useQueueStore = defineStore('queue', () => {
// Generate nomor antrean dengan format baru: R/E + Loket (A-N) + 3 digit
// Format: RA001 (Reguler), EA001 (Eksekutif/Grand Pavilion), RB001, RB002, dll
// Untuk Fast Track, tambahkan prefix "F" di depan: FRA001, FEA001, dll
const queueNumber = generateQueueNumber(clinic, paymentType, isEksekutif);
const noAntrian = `${queueNumber} | Onsite - ${barcode}`;
const finalQueueNumber = isFastTrack ? `F${queueNumber}` : queueNumber;
const noAntrian = `${finalQueueNumber} | Onsite - ${barcode}`;
// Status awal untuk pasien dari anjungan adalah "menunggu" (belum dipanggil)
// Hanya setelah dipanggil oleh admin loket, status berubah menjadi "waiting" (bisa check-in)
@@ -1138,7 +1140,7 @@ export const useQueueStore = defineStore('queue', () => {
noAntrian: noAntrian,
shift: shift,
klinik: clinic.name || clinic,
fastTrack: "TIDAK", // Default, bisa diubah nanti
fastTrack: isFastTrack ? "YA" : "TIDAK",
pembayaran: paymentType,
status: status,
processStage: "loket",