Perubahan format No Antrean

This commit is contained in:
Fanrouver
2026-01-09 10:28:41 +07:00
parent 863718020b
commit 6d5d565aa7
2 changed files with 271 additions and 108 deletions
+230 -106
View File
@@ -123,8 +123,8 @@
</div>
</div>
<div class="scanner-instruction">
<v-icon :color="primaryColor" size="20" class="mr-2">mdi-information</v-icon>
<span class="text-body-2">Arahkan kamera ke QR code. Pastikan QR code berada dalam kotak pemindaian.</span>
<v-icon :color="primaryColor" size="16">mdi-information</v-icon>
<span class="text-caption">Arahkan kamera ke QR code. Pastikan QR code berada dalam kotak pemindaian.</span>
</div>
</div>
</div>
@@ -313,72 +313,75 @@
:class="getStatusClass(item.status)"
>
<v-card-text class="pa-3">
<div class="d-flex justify-space-between align-start">
<div class="flex-grow-1">
<!-- Status & Method Chips -->
<div class="d-flex align-center flex-wrap gap-1 mb-2">
<v-chip
:color="getStatusColor(item.status)"
size="x-small"
density="compact"
>
<v-icon start size="12">{{ getStatusIcon(item.status) }}</v-icon>
{{ getStatusText(item.status) }}
</v-chip>
<v-chip
color="grey-lighten-1"
size="x-small"
variant="text"
density="compact"
>
{{ item.method }}
</v-chip>
</div>
<!-- Patient ID -->
<div class="mb-2">
<p class="text-body-2 font-weight-medium mb-0">
<v-icon size="14" class="mr-1" :color="primaryColor">mdi-account-circle</v-icon>
{{ item.patientId }}
</p>
</div>
<!-- Antrean & Pembayaran Chips -->
<div class="d-flex align-center flex-wrap gap-1 mb-2">
<v-chip
v-if="item.klinikQueueNumber"
size="x-small"
:color="secondaryColor"
variant="tonal"
density="compact"
>
<v-icon start size="12">mdi-ticket</v-icon>
{{ item.klinikQueueNumber }}
</v-chip>
<v-chip
v-if="item.pembayaran"
size="x-small"
:color="primaryColor"
variant="tonal"
density="compact"
>
<v-icon start size="12">mdi-credit-card</v-icon>
{{ item.pembayaran }}
</v-chip>
</div>
<!-- Time -->
<div class="d-flex align-center">
<v-chip
size="x-small"
variant="text"
color="grey-darken-1"
density="compact"
>
<v-icon start size="12">mdi-clock-outline</v-icon>
{{ formatTime(item.checkInTime) }}
</v-chip>
</div>
<div class="history-item-content">
<!-- Status & Method Chips -->
<div class="d-flex align-center flex-wrap gap-1 mb-2">
<v-chip
:color="getStatusColor(item.status)"
size="x-small"
density="compact"
class="history-status-chip"
>
<v-icon start size="12">{{ getStatusIcon(item.status) }}</v-icon>
{{ getStatusText(item.status) }}
</v-chip>
<v-chip
color="grey-lighten-1"
size="x-small"
variant="text"
density="compact"
class="history-method-chip"
>
{{ item.method }}
</v-chip>
</div>
<!-- Nomor Antrean -->
<div class="mb-2">
<p class="text-body-2 font-weight-medium mb-0 history-queue-number">
<v-icon size="14" class="mr-1" :color="secondaryColor">mdi-ticket</v-icon>
{{ item.klinikQueueNumber || item.queueNumber || 'N/A' }}
</p>
</div>
<!-- Patient ID & Pembayaran Chips -->
<div class="d-flex align-center flex-wrap gap-1 mb-2">
<v-chip
v-if="item.patientId"
size="x-small"
:color="primaryColor"
variant="tonal"
density="compact"
class="history-patient-chip"
>
<v-icon start size="12">mdi-account-circle</v-icon>
{{ item.patientId }}
</v-chip>
<v-chip
v-if="item.pembayaran && item.pembayaran !== 'N/A'"
size="x-small"
:color="primaryColor"
variant="tonal"
density="compact"
class="history-payment-chip"
>
<v-icon start size="12">mdi-credit-card</v-icon>
{{ item.pembayaran }}
</v-chip>
</div>
<!-- Time -->
<div class="d-flex align-center">
<v-chip
size="x-small"
variant="text"
color="grey-darken-1"
density="compact"
class="history-time-chip"
>
<v-icon start size="12">mdi-clock-outline</v-icon>
{{ formatTime(item.checkInTime) }}
</v-chip>
</div>
</div>
</v-card-text>
@@ -887,24 +890,24 @@
</v-chip>
</div>
<!-- Patient Info -->
<!-- Nomor Antrean Info -->
<div class="mb-3">
<p class="text-body-1 font-weight-bold mb-2">
<v-icon size="18" class="mr-1" :color="primaryColor">mdi-account-circle</v-icon>
{{ item.patientId }}
<v-icon size="18" class="mr-1" :color="secondaryColor">mdi-ticket</v-icon>
{{ item.klinikQueueNumber || item.queueNumber || 'N/A' }}
</p>
<!-- Antrean & Pembayaran Info Grid -->
<!-- Patient ID & Pembayaran Info Grid -->
<div class="d-flex flex-wrap gap-2 mb-2">
<v-chip
v-if="item.klinikQueueNumber"
v-if="item.patientId"
size="small"
:color="secondaryColor"
:color="primaryColor"
variant="tonal"
density="comfortable"
>
<v-icon start size="16">mdi-ticket</v-icon>
{{ item.klinikQueueNumber }}
<v-icon start size="16">mdi-account-circle</v-icon>
{{ item.patientId }}
</v-chip>
<v-chip
v-if="item.pembayaran"
@@ -3131,11 +3134,63 @@ if (typeof window !== 'undefined') {
.recent-history-list {
max-height: 400px;
overflow-y: auto;
padding-right: 4px;
}
.recent-history-list::-webkit-scrollbar {
width: 4px;
}
.recent-history-list::-webkit-scrollbar-track {
background: transparent;
}
.recent-history-list::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 2px;
}
.history-item-content {
width: 100%;
position: relative;
padding-left: 0;
}
.history-item-compact :deep(.v-card-text) {
padding: 12px 16px !important;
border-left: none !important;
position: relative;
z-index: 0;
}
.history-item-compact {
transition: all 0.2s ease;
border-left: 3px solid transparent;
position: relative;
overflow: hidden;
background: white !important;
}
.history-item-compact::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: transparent;
z-index: 1;
}
.history-item-compact :deep(.v-card__underlay) {
display: none;
}
.history-item-compact :deep(.v-card__border) {
border-left: none !important;
}
.history-item-compact :deep(.v-card__border)::before {
border-left: none !important;
}
.history-item-compact:hover {
@@ -3143,19 +3198,69 @@ if (typeof window !== 'undefined') {
transform: translateY(-1px);
}
.history-item-compact.history-success::before {
background: #4caf50 !important;
}
.history-item-compact.history-success {
border-left-color: #4caf50;
background: rgba(76, 175, 80, 0.03);
background: rgba(76, 175, 80, 0.03) !important;
}
.history-item-compact.history-success :deep(.v-card-text) {
background: rgba(76, 175, 80, 0.03) !important;
}
.history-item-compact.history-failed::before {
background: #f44336 !important;
}
.history-item-compact.history-failed {
border-left-color: #f44336;
background: rgba(244, 67, 54, 0.03);
background: rgba(244, 67, 54, 0.03) !important;
}
.history-item-compact.history-failed :deep(.v-card-text) {
background: rgba(244, 67, 54, 0.03) !important;
}
.history-item-compact.history-pending::before {
background: #ff9800 !important;
}
.history-item-compact.history-pending {
border-left-color: #ff9800;
background: rgba(255, 152, 0, 0.03);
background: rgba(255, 152, 0, 0.03) !important;
}
.history-item-compact.history-pending :deep(.v-card-text) {
background: rgba(255, 152, 0, 0.03) !important;
}
.history-status-chip {
font-weight: 600;
font-size: 11px !important;
}
.history-method-chip {
font-size: 10px !important;
color: #757575 !important;
}
.history-queue-number {
font-size: 14px;
line-height: 1.5;
color: #1a1a1a;
}
.history-queue-number .v-icon {
margin-right: 4px;
}
.history-patient-chip,
.history-payment-chip {
font-size: 10px !important;
}
.history-time-chip {
font-size: 11px !important;
}
/* Gap utility untuk flex-wrap */
@@ -3245,8 +3350,8 @@ if (typeof window !== 'undefined') {
.qr-placeholder {
width: 100%;
max-width: 300px;
height: 300px;
max-width: 320px;
height: 320px;
background: linear-gradient(135deg, #f5f7fa 0%, #e3f2fd 100%);
border-radius: 16px;
position: relative;
@@ -3261,7 +3366,7 @@ if (typeof window !== 'undefined') {
.qr-reader-container {
width: 100%;
max-width: 500px;
max-width: 320px;
margin: 0 auto;
position: relative;
}
@@ -3274,8 +3379,8 @@ if (typeof window !== 'undefined') {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
background: #000;
position: relative;
max-width: 500px;
max-height: 500px;
max-width: 320px;
max-height: 320px;
display: flex;
align-items: center;
justify-content: center;
@@ -3360,16 +3465,25 @@ if (typeof window !== 'undefined') {
.scanner-instruction {
display: flex;
align-items: center;
justify-content: center;
margin-top: 12px;
padding: 10px 12px;
justify-content: flex-start;
margin-top: 8px;
padding: 6px 10px;
background: linear-gradient(135deg, rgba(21, 101, 192, 0.1) 0%, rgba(13, 71, 161, 0.1) 100%);
border-radius: 10px;
border-radius: 8px;
color: #1565C0;
font-weight: 500;
font-size: 12px;
text-align: center;
font-size: 11px;
line-height: 1.4;
text-align: left;
border: 1px solid rgba(21, 101, 192, 0.2);
max-width: 320px;
margin-left: auto;
margin-right: auto;
}
.scanner-instruction .v-icon {
flex-shrink: 0;
margin-right: 8px;
}
.scanner-loading-overlay {
@@ -3951,7 +4065,7 @@ if (typeof window !== 'undefined') {
.qr-placeholder {
max-width: 100%;
height: 280px;
height: 250px;
}
.qr-reader-container {
@@ -3959,13 +4073,14 @@ if (typeof window !== 'undefined') {
}
.qr-reader-wrapper {
min-height: 300px;
max-height: 70vh;
min-height: 250px;
max-height: 250px;
max-width: 250px;
border-radius: 16px;
}
.qr-reader-wrapper :deep(video) {
max-height: 70vh;
max-height: 250px;
object-fit: contain;
transform: scaleX(-1); /* Mirror effect */
-webkit-transform: scaleX(-1); /* Safari support */
@@ -3975,8 +4090,15 @@ if (typeof window !== 'undefined') {
}
.scanner-instruction {
font-size: 12px;
padding: 12px;
font-size: 10px;
padding: 6px 8px;
margin-top: 6px;
max-width: 100%;
}
.scanner-instruction .v-icon {
font-size: 14px !important;
margin-right: 6px !important;
}
.stats-footer-modern {
@@ -4023,24 +4145,26 @@ if (typeof window !== 'undefined') {
.qr-placeholder {
max-width: 100%;
height: 240px;
height: 220px;
}
.qr-reader-wrapper {
min-height: 240px;
max-height: 280px;
min-height: 220px;
max-height: 220px;
max-width: 220px;
}
}
/* Mobile Landscape */
@media (max-width: 900px) and (orientation: landscape) {
.qr-reader-wrapper {
min-height: 50vh;
max-height: 60vh;
min-height: 250px;
max-height: 250px;
max-width: 250px;
}
.qr-reader-wrapper :deep(video) {
max-height: 60vh;
max-height: 250px;
transform: scaleX(-1); /* Mirror effect */
-webkit-transform: scaleX(-1); /* Safari support */
/* Reduce brightness to prevent overexposure/bloom */
+41 -2
View File
@@ -881,6 +881,43 @@ export const useQueueStore = defineStore('queue', () => {
currentProcessingPatient.value[adminType] = patient;
};
// Helper function untuk generate nomor antrean baru
// Format: 2 huruf (kode klinik) + 3 digit (001-999)
// Jika pembayaran BPJS: gunakan kode klinik dari master
// Jika pembayaran UMUM/JKMM/SPM/DLL: gunakan "UM" sebagai prefix
const generateQueueNumber = (clinic, paymentType) => {
// Tentukan prefix berdasarkan jenis pembayaran
let prefix = 'UM'; // Default untuk UMUM/JKMM/SPM/DLL
if (paymentType === 'BPJS') {
// Gunakan kode klinik dari master (2 huruf)
const clinicCode = clinic?.kode || clinic?.code || 'UM';
prefix = clinicCode.length >= 2 ? clinicCode.substring(0, 2).toUpperCase() : 'UM';
}
// Get counter untuk klinik ini per hari
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
const counterKey = `queue_counter_${prefix}_${today}`;
// Get current counter dari localStorage
let counter = 0;
if (typeof window !== 'undefined') {
const stored = localStorage.getItem(counterKey);
counter = stored ? parseInt(stored, 10) : 0;
}
// Increment counter (max 999)
counter = Math.min(counter + 1, 999);
// Save counter back to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem(counterKey, counter.toString());
}
// Format: prefix (2 huruf) + 3 digit
return `${prefix}${String(counter).padStart(3, '0')}`;
};
// Register patient from Anjungan (onsite registration)
const registerPatientFromAnjungan = (clinic, paymentType, visitType = 'SEKARANG', visitDate = null, shift = 'Shift 1', namaDokter = null) => {
const newNo = allPatients.value.length > 0
@@ -896,8 +933,9 @@ export const useQueueStore = defineStore('queue', () => {
// Generate barcode (simulasi - bisa diganti dengan API call)
const barcode = `250811${String(Date.now()).slice(-6)}${String(newNo).padStart(3, "0")}`;
// Format nomor antrean: UMXXXX | Onsite - barcode
const noAntrian = `UM${String(newNo).padStart(4, "0")} | Onsite - ${barcode}`;
// Generate nomor antrean dengan format baru: 2 huruf + 3 digit
const queueNumber = generateQueueNumber(clinic, paymentType);
const noAntrian = `${queueNumber} | 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)
@@ -1066,6 +1104,7 @@ export const useQueueStore = defineStore('queue', () => {
setCurrentProcessing,
registerPatientFromAnjungan,
checkInPatient,
generateQueueNumber,
};
}, {
persist: {