perubahan tiket dan alur antrean
This commit is contained in:
No files matched your search
+3
-3
@@ -35,15 +35,15 @@ KEYCLOAK_ISSUER="https://auth.rssa.top/realms/sandbox"
|
||||
# AUTH_ORIGIN="http://localhost:3000"
|
||||
# AUTH_ORIGIN="http://10.10.150.175:3001"
|
||||
# AUTH_ORIGIN="http://0.0.0.0:3000"
|
||||
# AUTH_ORIGIN="https://antrean.dev.rssa.id/
|
||||
# AUTH_ORIGIN="https://antrean.rssa.id/
|
||||
# AUTH_ORIGIN="https://antrean.dev.rssa.id"
|
||||
# AUTH_ORIGIN="https://antrean.rssa.id"
|
||||
|
||||
#nuxt config.ts dev server host and port
|
||||
# HOST="localhost"
|
||||
# PORT=3000
|
||||
# HOST="http://10.10.150.175:3000"
|
||||
# PORT=3000
|
||||
# HOST="http://0.0.0.0:3000"
|
||||
# HOST="0.0.0.0"
|
||||
# PORT=3000
|
||||
# HOST="https://antrean.dev.rssa.id/"
|
||||
# PORT=3000
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
:rules="[v => !!v || 'Nama loket harus diisi']"
|
||||
hide-details="auto"
|
||||
class="mb-3 input-field"
|
||||
placeholder="Loket 1"
|
||||
placeholder="Loket A"
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
|
||||
@@ -43,31 +43,36 @@ export const useThermalPrint = () => {
|
||||
|
||||
/**
|
||||
* Format date untuk tampilan
|
||||
* Format: senin, 12 Jan 2026
|
||||
*/
|
||||
const formatDate = (dateString: string): string => {
|
||||
try {
|
||||
const date = dateString ? new Date(dateString) : new Date();
|
||||
let date = dateString ? new Date(dateString) : new Date();
|
||||
if (isNaN(date.getTime())) {
|
||||
return new Date().toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
date = new Date();
|
||||
}
|
||||
return date.toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
|
||||
// Array nama hari dalam bahasa Indonesia (lowercase)
|
||||
const days = ['minggu', 'senin', 'selasa', 'rabu', 'kamis', 'jumat', 'sabtu'];
|
||||
// Array nama bulan singkat (title case)
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
||||
|
||||
const dayName = days[date.getDay()];
|
||||
const day = date.getDate();
|
||||
const month = months[date.getMonth()];
|
||||
const year = date.getFullYear(); // Tahun 4 digit
|
||||
|
||||
return `${dayName}, ${day} ${month} ${year}`;
|
||||
} catch (error) {
|
||||
return new Date().toLocaleDateString('id-ID', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
// Fallback jika error
|
||||
const date = new Date();
|
||||
const days = ['minggu', 'senin', 'selasa', 'rabu', 'kamis', 'jumat', 'sabtu'];
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
||||
const dayName = days[date.getDay()];
|
||||
const day = date.getDate();
|
||||
const month = months[date.getMonth()];
|
||||
const year = date.getFullYear();
|
||||
return `${dayName}, ${day} ${month} ${year}`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -203,7 +208,8 @@ export const useThermalPrint = () => {
|
||||
}
|
||||
|
||||
.qr-code-section {
|
||||
margin: 3mm 0;
|
||||
margin-top: -1.5mm;
|
||||
margin-bottom: 1mm;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -211,8 +217,8 @@ export const useThermalPrint = () => {
|
||||
width: 50mm;
|
||||
height: 50mm;
|
||||
margin: 0 auto;
|
||||
border: 1px solid #000;
|
||||
padding: 2mm;
|
||||
border: none;
|
||||
padding: 0.5mm;
|
||||
background: white;
|
||||
}
|
||||
|
||||
@@ -225,7 +231,7 @@ export const useThermalPrint = () => {
|
||||
.barcode-number {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
margin-top: 1mm;
|
||||
margin-top: -1mm;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@@ -243,30 +249,43 @@ export const useThermalPrint = () => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 4mm;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
.info-label-text {
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
flex: 0 0 38%;
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.info-colon {
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
margin-left: auto;
|
||||
padding-right: 2mm;
|
||||
}
|
||||
|
||||
.info-label-wrapper {
|
||||
display: flex;
|
||||
width: 22mm;
|
||||
flex-shrink: 0;
|
||||
align-items: flex-start;
|
||||
gap: 1mm;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.no-antrian {
|
||||
font-size: 18px;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
margin: 2mm 0;
|
||||
margin: 0.5mm 0;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@@ -274,8 +293,11 @@ export const useThermalPrint = () => {
|
||||
.klinik-name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 2mm 0;
|
||||
margin-top: 0.5mm;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
text-transform: uppercase;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.footer {
|
||||
@@ -373,28 +395,38 @@ export const useThermalPrint = () => {
|
||||
|
||||
<div class="info-section">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Tanggal:</span>
|
||||
<div class="info-label-wrapper">
|
||||
<span class="info-label-text">Tanggal</span><span class="info-colon">:</span>
|
||||
</div>
|
||||
<span class="info-value">${data.tanggal}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<span class="info-label">Waktu:</span>
|
||||
<div class="info-label-wrapper">
|
||||
<span class="info-label-text">Waktu</span><span class="info-colon">:</span>
|
||||
</div>
|
||||
<span class="info-value">${data.waktu}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<span class="info-label">Shift:</span>
|
||||
<div class="info-label-wrapper">
|
||||
<span class="info-label-text">Shift</span><span class="info-colon">:</span>
|
||||
</div>
|
||||
<span class="info-value">${data.shift}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<span class="info-label">Pembayaran:</span>
|
||||
<div class="info-label-wrapper">
|
||||
<span class="info-label-text">Pembayaran</span><span class="info-colon">:</span>
|
||||
</div>
|
||||
<span class="info-value">${data.pembayaran}</span>
|
||||
</div>
|
||||
|
||||
${data.namaDokter ? `
|
||||
<div class="info-row">
|
||||
<span class="info-label">Dokter:</span>
|
||||
<div class="info-label-wrapper">
|
||||
<span class="info-label-text">Dokter</span><span class="info-colon">:</span>
|
||||
</div>
|
||||
<span class="info-value">${data.namaDokter}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
@@ -487,13 +487,13 @@
|
||||
Cetak Nomor Antrian
|
||||
</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text class="pa-6" v-if="lastRegisteredPatient">
|
||||
<div class="dialog-content">
|
||||
<v-card-text class="pa-6">
|
||||
<div v-if="lastRegisteredPatient" class="dialog-content">
|
||||
<div class="text-center mb-4">
|
||||
<v-icon size="64" color="primary-600" class="mb-3">mdi-ticket-confirmation</v-icon>
|
||||
<h3 class="dialog-clinic-name mb-2">Tiket Berhasil Dibuat</h3>
|
||||
<p class="text-body-2 text-grey mb-4">
|
||||
Nomor antrean Anda: <strong>{{ lastRegisteredPatient.noAntrian.split(" |")[0] }}</strong>
|
||||
Nomor antrean Anda: <strong>{{ lastRegisteredPatient.noAntrian?.split(" |")[0] || lastRegisteredPatient.noAntrian || 'N/A' }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -502,19 +502,19 @@
|
||||
<div class="dialog-info">
|
||||
<div class="info-row mb-3">
|
||||
<span class="info-label">Klinik:</span>
|
||||
<span class="info-value">{{ lastRegisteredPatient.klinik }}</span>
|
||||
<span class="info-value">{{ lastRegisteredPatient.klinik || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-row mb-3">
|
||||
<span class="info-label">Shift:</span>
|
||||
<span class="info-value">{{ lastRegisteredPatient.shift }}</span>
|
||||
<span class="info-value">{{ lastRegisteredPatient.shift || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-row mb-3">
|
||||
<span class="info-label">Pembayaran:</span>
|
||||
<span class="info-value">{{ lastRegisteredPatient.pembayaran }}</span>
|
||||
<span class="info-value">{{ lastRegisteredPatient.pembayaran || 'N/A' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Barcode:</span>
|
||||
<span class="info-value">{{ lastRegisteredPatient.barcode }}</span>
|
||||
<span class="info-value">{{ lastRegisteredPatient.barcode || 'N/A' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -532,6 +532,10 @@
|
||||
</div>
|
||||
</v-alert>
|
||||
</div>
|
||||
<div v-else class="text-center py-6">
|
||||
<v-progress-circular indeterminate color="primary-600" class="mb-3"></v-progress-circular>
|
||||
<p class="text-body-2 text-grey">Memuat data tiket...</p>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
@@ -580,7 +584,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick } from "vue";
|
||||
import { ref, computed, onMounted, nextTick, watch } from "vue";
|
||||
import { useRoute } from "#app";
|
||||
import { useAnjunganStore } from "@/stores/anjunganStore";
|
||||
import { useClinicStore } from "@/stores/clinicStore";
|
||||
@@ -597,6 +601,16 @@ const clinicStore = useClinicStore();
|
||||
const queueStore = useQueueStore();
|
||||
const { printTicketFromPatient, isPrinting } = useThermalPrint();
|
||||
|
||||
// Validasi bahwa queueStore ter-load dengan benar
|
||||
onMounted(() => {
|
||||
console.log('🔍 QueueStore initialized:', queueStore);
|
||||
console.log('🔍 registerPatientFromAnjungan available:', typeof queueStore.registerPatientFromAnjungan);
|
||||
if (typeof queueStore.registerPatientFromAnjungan !== 'function') {
|
||||
console.error('❌ registerPatientFromAnjungan is not a function!');
|
||||
console.error('Available methods:', Object.keys(queueStore));
|
||||
}
|
||||
});
|
||||
|
||||
const anjunganId = computed(() => {
|
||||
const raw = route.params.id;
|
||||
const val = Array.isArray(raw) ? raw[0] : raw;
|
||||
@@ -1031,33 +1045,57 @@ const confirmDoctorSelection = () => {
|
||||
};
|
||||
|
||||
const registerPatient = async (visitType, paymentType, namaDokter) => {
|
||||
// Register patient to queueStore
|
||||
const result = queueStore.registerPatientFromAnjungan(
|
||||
selectedClinic.value,
|
||||
paymentType,
|
||||
visitType,
|
||||
null,
|
||||
'Shift 1',
|
||||
namaDokter
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
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]}`;
|
||||
showSnackbar(message, "success");
|
||||
try {
|
||||
// Validasi bahwa fungsi registerPatientFromAnjungan ada
|
||||
if (!queueStore) {
|
||||
console.error('❌ queueStore is not initialized');
|
||||
showSnackbar('Error', 'Store tidak ter-initialize. Silakan refresh halaman.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Simpan data pasien untuk print
|
||||
lastRegisteredPatient.value = result.patient;
|
||||
if (typeof queueStore.registerPatientFromAnjungan !== 'function') {
|
||||
console.error('❌ queueStore.registerPatientFromAnjungan is not a function');
|
||||
console.error('queueStore:', queueStore);
|
||||
console.error('Available methods:', Object.keys(queueStore || {}));
|
||||
showSnackbar('Error', 'Fungsi registrasi tidak tersedia. Silakan refresh halaman.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Tampilkan dialog print
|
||||
showPrintDialog.value = true;
|
||||
} else {
|
||||
showSnackbar(result.message || "Gagal melakukan pendaftaran", "error");
|
||||
// Register patient to queueStore
|
||||
const result = queueStore.registerPatientFromAnjungan(
|
||||
selectedClinic.value,
|
||||
paymentType,
|
||||
visitType,
|
||||
null,
|
||||
'Shift 1',
|
||||
namaDokter
|
||||
);
|
||||
|
||||
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'}`;
|
||||
showSnackbar(message, "success");
|
||||
|
||||
// Simpan data pasien untuk print
|
||||
lastRegisteredPatient.value = result.patient;
|
||||
console.log('📋 Patient data saved for print:', lastRegisteredPatient.value);
|
||||
|
||||
// Tampilkan dialog print - gunakan nextTick untuk memastikan reactive update
|
||||
await nextTick();
|
||||
showPrintDialog.value = true;
|
||||
console.log('✅ Print dialog opened, showPrintDialog:', showPrintDialog.value, 'patient:', lastRegisteredPatient.value);
|
||||
} else {
|
||||
console.error('❌ Registration failed or patient data missing:', result);
|
||||
showSnackbar(result?.message || "Gagal melakukan pendaftaran", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error in registerPatient:', error);
|
||||
showSnackbar('Terjadi kesalahan saat melakukan pendaftaran. Silakan coba lagi.', 'error');
|
||||
} finally {
|
||||
selectedClinic.value = null;
|
||||
selectedDoctor.value = null;
|
||||
pendingPaymentType.value = null;
|
||||
}
|
||||
|
||||
selectedClinic.value = null;
|
||||
selectedDoctor.value = null;
|
||||
pendingPaymentType.value = null;
|
||||
};
|
||||
|
||||
const submitBooking = async () => {
|
||||
@@ -1078,36 +1116,60 @@ const submitBooking = async () => {
|
||||
|
||||
showBookingFormDialog.value = false;
|
||||
|
||||
// Register patient to queueStore for future visit
|
||||
const paymentType = bookingForm.value.payment === 'BPJS' ? 'BPJS' : 'UMUM';
|
||||
const namaDokter = isEksekutif.value ? bookingForm.value.doctor : null;
|
||||
|
||||
const result = queueStore.registerPatientFromAnjungan(
|
||||
selectedClinic.value,
|
||||
paymentType,
|
||||
'JADWAL_LAIN',
|
||||
bookingForm.value.date,
|
||||
bookingForm.value.shift,
|
||||
namaDokter
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
const doctorInfo = namaDokter ? ` dengan dokter ${namaDokter}` : '';
|
||||
const message = `Jadwal kunjungan ${selectedClinic.value.name} pada ${bookingForm.value.date} - ${bookingForm.value.shift} (${paymentType})${doctorInfo} berhasil disimpan. Nomor antrean: ${result.patient.noAntrian.split(" |")[0]}`;
|
||||
showSnackbar(message, "success");
|
||||
try {
|
||||
// Register patient to queueStore for future visit
|
||||
const paymentType = bookingForm.value.payment === 'BPJS' ? 'BPJS' : 'UMUM';
|
||||
const namaDokter = isEksekutif.value ? bookingForm.value.doctor : null;
|
||||
|
||||
// Simpan data pasien untuk print
|
||||
lastRegisteredPatient.value = result.patient;
|
||||
// Validasi bahwa fungsi registerPatientFromAnjungan ada
|
||||
if (!queueStore) {
|
||||
console.error('❌ queueStore is not initialized');
|
||||
showSnackbar('Error', 'Store tidak ter-initialize. Silakan refresh halaman.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Tampilkan dialog print
|
||||
showPrintDialog.value = true;
|
||||
} else {
|
||||
showSnackbar(result.message || "Gagal menyimpan jadwal", "error");
|
||||
if (typeof queueStore.registerPatientFromAnjungan !== 'function') {
|
||||
console.error('❌ queueStore.registerPatientFromAnjungan is not a function');
|
||||
console.error('queueStore:', queueStore);
|
||||
console.error('Available methods:', Object.keys(queueStore || {}));
|
||||
showSnackbar('Error', 'Fungsi registrasi tidak tersedia. Silakan refresh halaman.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = queueStore.registerPatientFromAnjungan(
|
||||
selectedClinic.value,
|
||||
paymentType,
|
||||
'JADWAL_LAIN',
|
||||
bookingForm.value.date,
|
||||
bookingForm.value.shift,
|
||||
namaDokter
|
||||
);
|
||||
|
||||
if (result && result.success && result.patient) {
|
||||
const doctorInfo = namaDokter ? ` dengan dokter ${namaDokter}` : '';
|
||||
const message = `Jadwal kunjungan ${selectedClinic.value.name} pada ${bookingForm.value.date} - ${bookingForm.value.shift} (${paymentType})${doctorInfo} berhasil disimpan. Nomor antrean: ${result.patient.noAntrian?.split(" |")[0] || result.patient.noAntrian || 'N/A'}`;
|
||||
showSnackbar(message, "success");
|
||||
|
||||
// Simpan data pasien untuk print
|
||||
lastRegisteredPatient.value = result.patient;
|
||||
console.log('📋 Patient data saved for print (booking):', lastRegisteredPatient.value);
|
||||
|
||||
// Tampilkan dialog print - gunakan nextTick untuk memastikan reactive update
|
||||
await nextTick();
|
||||
showPrintDialog.value = true;
|
||||
console.log('✅ Print dialog opened (booking), showPrintDialog:', showPrintDialog.value, 'patient:', lastRegisteredPatient.value);
|
||||
} else {
|
||||
console.error('❌ Booking failed or patient data missing:', result);
|
||||
showSnackbar(result?.message || "Gagal menyimpan jadwal", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error in submitBooking:', error);
|
||||
showSnackbar('Terjadi kesalahan saat menyimpan jadwal. Silakan coba lagi.', 'error');
|
||||
} finally {
|
||||
selectedClinic.value = null;
|
||||
selectedDoctor.value = null;
|
||||
bookingForm.value.doctor = null;
|
||||
}
|
||||
|
||||
selectedClinic.value = null;
|
||||
selectedDoctor.value = null;
|
||||
bookingForm.value.doctor = null;
|
||||
};
|
||||
|
||||
const handlePrintTicket = async () => {
|
||||
@@ -1133,9 +1195,18 @@ const backToList = () => {
|
||||
navigateTo("/anjungan/anjungan");
|
||||
};
|
||||
|
||||
const navigateToCheckIn = () => {
|
||||
navigateTo("/check-in-pasien/check-in");
|
||||
};
|
||||
// Watch showPrintDialog untuk debugging
|
||||
watch(showPrintDialog, (newVal) => {
|
||||
console.log('🔔 showPrintDialog changed:', newVal);
|
||||
if (newVal) {
|
||||
console.log('📋 lastRegisteredPatient:', lastRegisteredPatient.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Watch lastRegisteredPatient untuk debugging
|
||||
watch(lastRegisteredPatient, (newVal) => {
|
||||
console.log('🔔 lastRegisteredPatient changed:', newVal);
|
||||
}, { deep: true });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="queue-grid-container">
|
||||
<!-- Loket Header -->
|
||||
<div class="loket-header">
|
||||
<h2 class="loket-title">{{ loketData?.namaLoket || 'LOKET 1' }}</h2>
|
||||
<h2 class="loket-title">{{ loketData?.namaLoket || 'LOKET A' }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Patient Cards Grid (like AdminLoket) -->
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<v-icon size="32" color="primary">mdi-counter</v-icon>
|
||||
<div class="loket-info">
|
||||
<h3 class="loket-name">{{ loket.namaLoket }}</h3>
|
||||
<p class="loket-details">No. {{ loket.no }}</p>
|
||||
<p class="loket-details">No. {{ numberToLetter(loket.no) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -100,6 +100,13 @@ definePageMeta({
|
||||
const masterStore = useMasterStore();
|
||||
const route = useRoute();
|
||||
|
||||
// Helper function: Convert nomor loket ke huruf (1 -> A, 2 -> B, dst)
|
||||
const numberToLetter = (num) => {
|
||||
if (num < 1) return 'A';
|
||||
const charCode = 64 + num;
|
||||
return String.fromCharCode(Math.min(charCode, 90));
|
||||
};
|
||||
|
||||
// Get all lokets
|
||||
// Untuk saat ini, hanya menampilkan loket 1
|
||||
// Nanti setiap loket akan menampilkan data yang berbeda
|
||||
|
||||
@@ -159,7 +159,7 @@ const allLokets = computed(() => {
|
||||
const getDefaultLoket = () => {
|
||||
const allLoketsData = loketStore.loketData?.value || loketStore.loketData || []
|
||||
const loket1 = allLoketsData.find(l => l.id === 1 || l.no === 1)
|
||||
return loket1 ? loket1.namaLoket : 'Loket 1'
|
||||
return loket1 ? loket1.namaLoket : 'Loket A'
|
||||
}
|
||||
|
||||
// Display lokets with their queues
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<v-icon size="32" color="primary-600">mdi-view-dashboard</v-icon>
|
||||
<div class="loket-info">
|
||||
<h3 class="loket-name">{{ loket.namaLoket }}</h3>
|
||||
<p class="loket-details">No. {{ loket.no }}</p>
|
||||
<p class="loket-details">No. {{ numberToLetter(loket.no) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -100,6 +100,13 @@ definePageMeta({
|
||||
const loketStore = useLoketStore();
|
||||
const route = useRoute();
|
||||
|
||||
// Helper function: Convert nomor loket ke huruf (1 -> A, 2 -> B, dst)
|
||||
const numberToLetter = (num) => {
|
||||
if (num < 1) return 'A';
|
||||
const charCode = 64 + num;
|
||||
return String.fromCharCode(Math.min(charCode, 90));
|
||||
};
|
||||
|
||||
// Get all lokets
|
||||
const allLokets = computed(() => {
|
||||
return loketStore.loketData?.value || loketStore.loketData || [];
|
||||
|
||||
+562
-231
File diff suppressed because it is too large
Load diff
@@ -102,7 +102,7 @@
|
||||
:rules="[v => !!v || 'Nama loket harus diisi']"
|
||||
hide-details="auto"
|
||||
class="mb-3 input-field"
|
||||
placeholder="Loket 1"
|
||||
placeholder="Loket A"
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
|
||||
@@ -91,11 +91,22 @@ export default defineEventHandler(async (event: H3Event) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function untuk generate barcode dengan format: YYMMDD + 5 digit random
|
||||
const generateBarcode = () => {
|
||||
const now = new Date();
|
||||
const year = String(now.getFullYear()).slice(-2); // 2 digit tahun terakhir
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0'); // 2 digit bulan
|
||||
const day = String(now.getDate()).padStart(2, '0'); // 2 digit tanggal
|
||||
const randomCode = String(Math.floor(Math.random() * 100000)).padStart(5, '0'); // 5 digit random
|
||||
return `${year}${month}${day}${randomCode}`;
|
||||
};
|
||||
|
||||
// Generate patient data
|
||||
const barcode = generateBarcode();
|
||||
const newPatient: Patient = {
|
||||
no: mockDB.length + 1,
|
||||
jamPanggil: new Date().toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }),
|
||||
barcode: `250811${String(Date.now()).slice(-6)}${String(mockDB.length + 1).padStart(3, '0')}`,
|
||||
barcode: barcode,
|
||||
noAntrian: `UM${String(mockDB.length + 1).padStart(4, '0')} | Onsite - ${barcode}`,
|
||||
shift: body.shift || 'Shift 1',
|
||||
klinik: body.clinic,
|
||||
|
||||
+24
-4
@@ -3,6 +3,14 @@ import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useClinicStore } from './clinicStore';
|
||||
|
||||
// Helper function: Convert nomor loket ke huruf (1 -> A, 2 -> B, dst)
|
||||
const numberToLetter = (num) => {
|
||||
if (num < 1) return 'A';
|
||||
// 1 -> A (65), 2 -> B (66), ..., 26 -> Z (90)
|
||||
const charCode = 64 + num; // 64 = '@', 65 = 'A'
|
||||
return String.fromCharCode(Math.min(charCode, 90)); // Maksimal Z (90)
|
||||
};
|
||||
|
||||
export const useLoketStore = defineStore('loket', () => {
|
||||
const clinicStore = useClinicStore();
|
||||
|
||||
@@ -11,7 +19,7 @@ export const useLoketStore = defineStore('loket', () => {
|
||||
{
|
||||
id: 1,
|
||||
no: 1,
|
||||
namaLoket: "Loket 1",
|
||||
namaLoket: "Loket A",
|
||||
kuota: 500,
|
||||
pelayanan: ["RT", "RM", "TD"],
|
||||
pembayaran: "JKN",
|
||||
@@ -21,7 +29,7 @@ export const useLoketStore = defineStore('loket', () => {
|
||||
{
|
||||
id: 2,
|
||||
no: 2,
|
||||
namaLoket: "Loket 2",
|
||||
namaLoket: "Loket B",
|
||||
kuota: 666,
|
||||
pelayanan: ["JW", "SR"],
|
||||
pembayaran: "JKN",
|
||||
@@ -31,7 +39,7 @@ export const useLoketStore = defineStore('loket', () => {
|
||||
{
|
||||
id: 3,
|
||||
no: 3,
|
||||
namaLoket: "Loket 3",
|
||||
namaLoket: "Loket C",
|
||||
kuota: 666,
|
||||
pelayanan: ["AS", "JT"],
|
||||
pembayaran: "JKN",
|
||||
@@ -41,7 +49,7 @@ export const useLoketStore = defineStore('loket', () => {
|
||||
{
|
||||
id: 4,
|
||||
no: 4,
|
||||
namaLoket: "Loket 4",
|
||||
namaLoket: "Loket D",
|
||||
kuota: 3676,
|
||||
pelayanan: ["KK", "PR"],
|
||||
pembayaran: "JKN",
|
||||
@@ -65,10 +73,18 @@ export const useLoketStore = defineStore('loket', () => {
|
||||
const newId = Math.max(...loketData.value.map(l => l.id), 0) + 1;
|
||||
const newNo = loketData.value.length + 1;
|
||||
|
||||
// Jika namaLoket tidak disediakan atau masih menggunakan angka, ubah ke huruf
|
||||
let namaLoket = loketPayload.namaLoket;
|
||||
if (!namaLoket || namaLoket.match(/Loket \d+/)) {
|
||||
const letter = numberToLetter(newNo);
|
||||
namaLoket = `Loket ${letter}`;
|
||||
}
|
||||
|
||||
const newLoket = {
|
||||
id: newId,
|
||||
no: newNo,
|
||||
...loketPayload,
|
||||
namaLoket: namaLoket,
|
||||
};
|
||||
|
||||
loketData.value.push(newLoket);
|
||||
@@ -104,6 +120,9 @@ export const useLoketStore = defineStore('loket', () => {
|
||||
return loketData.value.find(l => l.id === id);
|
||||
};
|
||||
|
||||
// Helper function untuk convert nomor ke huruf (export untuk digunakan di komponen)
|
||||
const getLoketLetter = (num) => numberToLetter(num);
|
||||
|
||||
return {
|
||||
// State
|
||||
loketData,
|
||||
@@ -114,6 +133,7 @@ export const useLoketStore = defineStore('loket', () => {
|
||||
updateLoket,
|
||||
deleteLoket,
|
||||
getLoketById,
|
||||
getLoketLetter,
|
||||
};
|
||||
}, {
|
||||
persist: {
|
||||
|
||||
+176
-51
@@ -10,19 +10,93 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const penunjangStore = usePenunjangStore();
|
||||
const loketStore = useLoketStore();
|
||||
|
||||
// Helper function untuk mendapatkan loket default (Loket 1)
|
||||
// Helper function untuk mendapatkan loket default (Loket A)
|
||||
const getDefaultLoket = () => {
|
||||
const allLokets = loketStore.loketData?.value || loketStore.loketData || [];
|
||||
const loket1 = allLokets.find(l => l.id === 1 || l.no === 1);
|
||||
return loket1 ? loket1.namaLoket : 'Loket 1';
|
||||
return loket1 ? loket1.namaLoket : 'Loket A';
|
||||
};
|
||||
|
||||
// Helper function untuk generate barcode dengan format: YYMMDD + 5 digit random
|
||||
// Format: YY (tahun 2 digit) + MM (bulan 2 digit) + DD (tanggal 2 digit) + XXXXX (5 digit random)
|
||||
// Contoh: 25011212345 (12 Januari 2025 dengan random 5 digit 12345)
|
||||
// IMPORTANT: Memastikan barcode selalu UNIQUE meskipun reset harian
|
||||
// Menggunakan timestamp milisecond + random untuk memastikan uniqueness
|
||||
// NOTE: allPatientsRef adalah optional parameter untuk menghindari TDZ error saat seed initialization
|
||||
const generateBarcode = (existingBarcodes = [], allPatientsRef = null) => {
|
||||
const now = new Date();
|
||||
const year = String(now.getFullYear()).slice(-2); // 2 digit tahun terakhir
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0'); // 2 digit bulan
|
||||
const day = String(now.getDate()).padStart(2, '0'); // 2 digit tanggal
|
||||
|
||||
// Generate 5 digit code dari timestamp milisecond untuk memastikan uniqueness
|
||||
// Ambil 5 digit terakhir dari timestamp + random untuk menghindari duplikasi
|
||||
const timestamp = Date.now();
|
||||
const randomOffset = Math.floor(Math.random() * 1000); // 0-999
|
||||
const uniqueCode = String((timestamp % 100000) + randomOffset).slice(-5).padStart(5, '0');
|
||||
|
||||
// Pastikan barcode unique dengan mengecek di existingBarcodes atau allPatientsRef
|
||||
let barcode = `${year}${month}${day}${uniqueCode}`;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 100;
|
||||
|
||||
// Cek apakah barcode sudah ada
|
||||
// NOTE: allPatientsRef adalah optional untuk menghindari TDZ error saat seed initialization
|
||||
const checkExists = (b) => {
|
||||
if (existingBarcodes.includes(b)) return true;
|
||||
// Hanya cek allPatientsRef jika diberikan (tidak null/undefined)
|
||||
if (allPatientsRef && allPatientsRef.value && allPatientsRef.value.some(p => p.barcode === b)) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
// Generate ulang jika duplikat
|
||||
while (checkExists(barcode) && attempts < maxAttempts) {
|
||||
const newTimestamp = Date.now();
|
||||
const newRandomOffset = Math.floor(Math.random() * 1000);
|
||||
const newUniqueCode = String((newTimestamp % 100000) + newRandomOffset).slice(-5).padStart(5, '0');
|
||||
barcode = `${year}${month}${day}${newUniqueCode}`;
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// Jika masih duplikat setelah maxAttempts, tambahkan counter berdasarkan existing barcodes
|
||||
if (attempts >= maxAttempts) {
|
||||
const datePrefix = `${year}${month}${day}`;
|
||||
const existingCount = existingBarcodes.filter(b => b && b.startsWith(datePrefix)).length;
|
||||
// Hanya cek allPatientsRef jika diberikan (tidak null/undefined)
|
||||
const allCount = allPatientsRef && allPatientsRef.value
|
||||
? allPatientsRef.value.filter(p => p.barcode && p.barcode.startsWith(datePrefix)).length
|
||||
: 0;
|
||||
const counter = Math.max(existingCount, allCount) + 1;
|
||||
const counterCode = String(counter).padStart(5, '0');
|
||||
barcode = `${datePrefix}${counterCode}`;
|
||||
}
|
||||
|
||||
return barcode;
|
||||
};
|
||||
// Seed data for easy reset during dev
|
||||
// IMPORTANT: Setiap pasien HARUS memiliki barcode UNIK untuk menghindari konflik
|
||||
// Format barcode menggunakan generateBarcode() untuk konsistensi dengan format baru
|
||||
// Generate barcode dengan memastikan uniqueness menggunakan existingBarcodes array
|
||||
const seedBarcodes = [];
|
||||
const seedBarcode1 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode1);
|
||||
const seedBarcode2 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode2);
|
||||
const seedBarcode3 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode3);
|
||||
const seedBarcode4 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode4);
|
||||
const seedBarcode5 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode5);
|
||||
const seedBarcode6 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode6);
|
||||
const seedBarcode7 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode7);
|
||||
const seedBarcode8 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode8);
|
||||
const seedBarcode9 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode9);
|
||||
const seedBarcode10 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode10);
|
||||
const seedBarcode11 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode11);
|
||||
const seedBarcode12 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode12);
|
||||
|
||||
const seedPatients = [
|
||||
{
|
||||
no: 1,
|
||||
jamPanggil: "12:49",
|
||||
barcode: "250811100163",
|
||||
noAntrian: "UM1001 | Online - 250811100163",
|
||||
barcode: seedBarcode1, // Barcode unik untuk setiap pasien
|
||||
noAntrian: `UM1001 | Online - ${seedBarcode1}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "KANDUNGAN",
|
||||
fastTrack: "YA", // Fast Track Patient
|
||||
@@ -34,8 +108,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 2,
|
||||
jamPanggil: "10:52",
|
||||
barcode: "250811100155",
|
||||
noAntrian: "UM1002 | Online - 250811100155",
|
||||
barcode: seedBarcode2,
|
||||
noAntrian: `UM1002 | Online - ${seedBarcode2}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "IPD",
|
||||
fastTrack: "TIDAK",
|
||||
@@ -47,8 +121,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 3,
|
||||
jamPanggil: "09:30",
|
||||
barcode: "250811100200",
|
||||
noAntrian: "UM1003 | Online - 250811100200",
|
||||
barcode: seedBarcode3,
|
||||
noAntrian: `UM1003 | Online - ${seedBarcode3}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "SARAF",
|
||||
fastTrack: "YA", // Fast Track Patient
|
||||
@@ -60,8 +134,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 4,
|
||||
jamPanggil: "14:15",
|
||||
barcode: "250811100210",
|
||||
noAntrian: "UM1004 | Online - 250811100210",
|
||||
barcode: seedBarcode4,
|
||||
noAntrian: `UM1004 | Online - ${seedBarcode4}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "THT",
|
||||
fastTrack: "TIDAK",
|
||||
@@ -73,8 +147,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 5,
|
||||
jamPanggil: "12:49",
|
||||
barcode: "250811100163",
|
||||
noAntrian: "UM1005 | Online - 250811100163",
|
||||
barcode: seedBarcode5,
|
||||
noAntrian: `UM1005 | Online - ${seedBarcode5}`,
|
||||
shift: "Shift 2",
|
||||
klinik: "KANDUNGAN",
|
||||
fastTrack: "TIDAK",
|
||||
@@ -86,8 +160,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 6,
|
||||
jamPanggil: "10:52",
|
||||
barcode: "250811100155",
|
||||
noAntrian: "UM1006 | Online - 250811100155",
|
||||
barcode: seedBarcode6,
|
||||
noAntrian: `UM1006 | Online - ${seedBarcode6}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "IPD",
|
||||
fastTrack: "YA", // Fast Track Patient
|
||||
@@ -99,8 +173,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 7,
|
||||
jamPanggil: "09:30",
|
||||
barcode: "250811100200",
|
||||
noAntrian: "UM1007 | Online - 250811100200",
|
||||
barcode: seedBarcode7,
|
||||
noAntrian: `UM1007 | Online - ${seedBarcode7}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "SARAF",
|
||||
fastTrack: "TIDAK",
|
||||
@@ -112,8 +186,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 8,
|
||||
jamPanggil: "14:15",
|
||||
barcode: "250811100210",
|
||||
noAntrian: "UM1008 | Online - 250811100210",
|
||||
barcode: seedBarcode8,
|
||||
noAntrian: `UM1008 | Online - ${seedBarcode8}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "THT",
|
||||
fastTrack: "TIDAK",
|
||||
@@ -125,8 +199,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 9,
|
||||
jamPanggil: "12:49",
|
||||
barcode: "250811100163",
|
||||
noAntrian: "UM1009 | Online - 250811100163",
|
||||
barcode: seedBarcode9,
|
||||
noAntrian: `UM1009 | Online - ${seedBarcode9}`,
|
||||
shift: "Shift 2",
|
||||
klinik: "KANDUNGAN",
|
||||
fastTrack: "YA", // Fast Track Patient
|
||||
@@ -138,8 +212,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 10,
|
||||
jamPanggil: "10:52",
|
||||
barcode: "250811100155",
|
||||
noAntrian: "UM1010 | Online - 250811100155",
|
||||
barcode: seedBarcode10,
|
||||
noAntrian: `UM1010 | Online - ${seedBarcode10}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "IPD",
|
||||
fastTrack: "TIDAK",
|
||||
@@ -151,8 +225,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 11,
|
||||
jamPanggil: "09:30",
|
||||
barcode: "250811100200",
|
||||
noAntrian: "UM1011 | Online - 250811100200",
|
||||
barcode: seedBarcode11,
|
||||
noAntrian: `UM1011 | Online - ${seedBarcode11}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "SARAF",
|
||||
fastTrack: "TIDAK",
|
||||
@@ -164,8 +238,8 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
{
|
||||
no: 12,
|
||||
jamPanggil: "14:15",
|
||||
barcode: "250811100210",
|
||||
noAntrian: "UM1012 | Online - 250811100210",
|
||||
barcode: seedBarcode12,
|
||||
noAntrian: `UM1012 | Online - ${seedBarcode12}`,
|
||||
shift: "Shift 2",
|
||||
klinik: "THT",
|
||||
fastTrack: "YA", // Fast Track Patient
|
||||
@@ -506,7 +580,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const createAntreanKlinik = (klinik, patient = null, adminType = 'loket') => {
|
||||
const newNo = allPatients.value.length + 1;
|
||||
const timestamp = new Date();
|
||||
const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`;
|
||||
const barcode = patient ? patient.barcode : generateBarcode([], allPatients);
|
||||
|
||||
const newPatient = {
|
||||
no: newNo,
|
||||
@@ -537,7 +611,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const createAntreanPenunjang = (penunjang, patient = null, adminType = 'loket') => {
|
||||
const newNo = allPatients.value.length + 1;
|
||||
const timestamp = new Date();
|
||||
const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`;
|
||||
const barcode = patient ? patient.barcode : generateBarcode([], allPatients);
|
||||
|
||||
const newPatient = {
|
||||
no: newNo,
|
||||
@@ -568,7 +642,7 @@ 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 barcode = patient ? patient.barcode : generateBarcode([], allPatients);
|
||||
|
||||
// Generate nomor antrian baru dengan format: [huruf pertama poli + urutan abjad ruang + nomor antrian ruang]
|
||||
// Contoh: "Anak" ruang 1 = "AA001" (A dari Anak, A dari ruang 1, 001 nomor antrian)
|
||||
@@ -883,21 +957,23 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
|
||||
// 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
|
||||
// Generate nomor antrian dengan format baru untuk anjungan: R/E + Loket (A-N) + 3 digit
|
||||
// Format: R/E (jenis pelayanan Reguler/Eksekutif) + A-N (nomor loket) + 3 digit angka
|
||||
// Contoh: RA001, EA001, RB001, RB002
|
||||
// - R = Reguler (untuk pasien Reguler/BPJS)
|
||||
// - E = Eksekutif (untuk pasien Eksekutif/Grand Pavilion)
|
||||
// Urutan terpisah untuk UMUM dan JKN/BPJS
|
||||
// Setiap loket menampung maksimal 999 nomor (001-999)
|
||||
const generateQueueNumber = (clinic, paymentType, isEksekutif = false) => {
|
||||
// Tentukan prefix jenis pelayanan: R untuk Reguler, E untuk Eksekutif (Grand Pavilion)
|
||||
const serviceType = isEksekutif ? 'E' : 'R';
|
||||
|
||||
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';
|
||||
}
|
||||
// Tentukan payment group untuk counter terpisah (UMUM atau JKN/BPJS)
|
||||
const paymentGroup = paymentType === 'BPJS' || paymentType === 'JKN' ? 'JKN' : 'UMUM';
|
||||
|
||||
// Get counter untuk klinik ini per hari
|
||||
// Get counter untuk payment group ini per hari
|
||||
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
||||
const counterKey = `queue_counter_${prefix}_${today}`;
|
||||
const counterKey = `queue_counter_loket_${paymentGroup}_${today}`;
|
||||
|
||||
// Get current counter dari localStorage
|
||||
let counter = 0;
|
||||
@@ -906,16 +982,44 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
counter = stored ? parseInt(stored, 10) : 0;
|
||||
}
|
||||
|
||||
// Increment counter (max 999)
|
||||
counter = Math.min(counter + 1, 999);
|
||||
// Increment counter
|
||||
counter = counter + 1;
|
||||
|
||||
// Tentukan loket berdasarkan counter
|
||||
// Loket A untuk 1-999, B untuk 1000-1998, C untuk 2000-2997, dst sampai N (14 loket)
|
||||
// Setiap loket menampung maksimal 999 nomor (001-999)
|
||||
// Loket index: 0=A, 1=B, 2=C, ..., 13=N
|
||||
const loketIndex = Math.floor((counter - 1) / 999); // 0 untuk A, 1 untuk B, dst
|
||||
|
||||
// Jika loket melebihi N (14 loket, index 13), wrap kembali ke A
|
||||
// Maksimal 14 loket (A-N), jadi maksimal counter per hari = 14 * 999 = 13986
|
||||
// Jika melebihi, reset counter ke 1
|
||||
if (loketIndex > 13) {
|
||||
counter = 1;
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(counterKey, '1');
|
||||
}
|
||||
// Format: R/E + Loket A + 001
|
||||
return `${serviceType}A001`;
|
||||
}
|
||||
|
||||
// Loket letter (A-N)
|
||||
const loketLetter = String.fromCharCode(65 + loketIndex); // 65 = 'A', 66 = 'B', dst
|
||||
|
||||
// Nomor dalam loket (1-999, di-display sebagai 001-999)
|
||||
// counter 1-999 → loket A, nomor 001-999
|
||||
// counter 1000-1998 → loket B, nomor 001-999
|
||||
const numberInLoket = ((counter - 1) % 999) + 1;
|
||||
const numberPart = String(numberInLoket).padStart(3, '0');
|
||||
|
||||
// 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')}`;
|
||||
// Format: R/E + Loket + 3 digit
|
||||
// Contoh: RA001, RA002, ..., RA999, RB001, RB002, ...
|
||||
return `${serviceType}${loketLetter}${numberPart}`;
|
||||
};
|
||||
|
||||
// Register patient from Anjungan (onsite registration)
|
||||
@@ -930,11 +1034,15 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
? `${String(visitDateTime.getHours()).padStart(2, "0")}:${String(visitDateTime.getMinutes()).padStart(2, "0")}`
|
||||
: `${String(timestamp.getHours()).padStart(2, "0")}:${String(timestamp.getMinutes()).padStart(2, "0")}`;
|
||||
|
||||
// Generate barcode (simulasi - bisa diganti dengan API call)
|
||||
const barcode = `250811${String(Date.now()).slice(-6)}${String(newNo).padStart(3, "0")}`;
|
||||
// Generate barcode dengan format: YYMMDD + 5 digit random
|
||||
const barcode = generateBarcode([], allPatients);
|
||||
|
||||
// Generate nomor antrean dengan format baru: 2 huruf + 3 digit
|
||||
const queueNumber = generateQueueNumber(clinic, paymentType);
|
||||
// Tentukan apakah pasien Eksekutif/Grand Pavilion (jika ada namaDokter, berarti Eksekutif)
|
||||
const isEksekutif = namaDokter !== null && namaDokter !== undefined && namaDokter !== '';
|
||||
|
||||
// Generate nomor antrean dengan format baru: R/E + Loket (A-N) + 3 digit
|
||||
// Format: RA001 (Reguler), EA001 (Eksekutif/Grand Pavilion), RB001, RB002, dll
|
||||
const queueNumber = generateQueueNumber(clinic, paymentType, isEksekutif);
|
||||
const noAntrian = `${queueNumber} | Onsite - ${barcode}`;
|
||||
|
||||
// Status awal untuk pasien dari anjungan adalah "menunggu" (belum dipanggil)
|
||||
@@ -1035,8 +1143,16 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
return { success: false, message: "Pasien tidak ditemukan" };
|
||||
}
|
||||
|
||||
// IMPORTANT: Get fresh patient data from array to avoid stale data
|
||||
// Use the patientIndex we found earlier, but get fresh data from array
|
||||
const patient = allPatients.value[patientIndex];
|
||||
console.log('✅ Patient found:', patient);
|
||||
console.log('✅ Patient found (fresh):', {
|
||||
no: patient.no,
|
||||
barcode: patient.barcode,
|
||||
noAntrian: patient.noAntrian,
|
||||
status: patient.status,
|
||||
processStage: patient.processStage
|
||||
});
|
||||
|
||||
// Only allow check-in if status is waiting (sudah dipanggil) or pending
|
||||
// Pasien dengan status "menunggu" belum bisa check-in (belum dipanggil)
|
||||
@@ -1048,6 +1164,15 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
};
|
||||
}
|
||||
|
||||
// Check if already checked in
|
||||
if (patient.status === 'di-loket') {
|
||||
console.log('⚠️ Patient already checked in:', patient.status);
|
||||
return {
|
||||
success: false,
|
||||
message: `Pasien sudah melakukan check-in sebelumnya. Status saat ini: ${patient.status}`
|
||||
};
|
||||
}
|
||||
|
||||
if (patient.status !== 'waiting' && patient.status !== 'pending') {
|
||||
console.log('⚠️ Patient status is not waiting/pending:', patient.status);
|
||||
return {
|
||||
@@ -1056,7 +1181,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
};
|
||||
}
|
||||
|
||||
// Update status to di-loket
|
||||
// Update status to di-loket using the patientIndex
|
||||
allPatients.value[patientIndex] = {
|
||||
...allPatients.value[patientIndex],
|
||||
status: "di-loket",
|
||||
|
||||
Reference in New Issue
Block a user