QRcode update
This commit is contained in:
No files matched your search
+192
-104
@@ -18,17 +18,95 @@ export interface ThermalPrintData {
|
||||
export const useThermalPrint = () => {
|
||||
const isPrinting = ref(false);
|
||||
|
||||
/**
|
||||
* Generate barcode dengan format: YYMMDD + 5 digit sequential
|
||||
* Format: YY (tahun 2 digit terakhir) + MM (bulan 2 digit) + DD (tanggal 2 digit) + XXXXX (5 digit sequential)
|
||||
* Contoh: 26011400001, 26011400002, dst
|
||||
* Counter akan reset setiap ganti tanggal (mulai dari 00001 lagi)
|
||||
*
|
||||
* @param existingBarcodes - Array barcode yang sudah ada (optional, untuk validasi uniqueness)
|
||||
* @returns Barcode string dengan format YYMMDDXXXXX
|
||||
*/
|
||||
const generateBarcode = (existingBarcodes: string[] = []): string => {
|
||||
if (typeof window === 'undefined') {
|
||||
// Fallback untuk SSR: gunakan counter berdasarkan existingBarcodes
|
||||
const now = new Date();
|
||||
const year = String(now.getFullYear()).slice(-2); // 2 digit tahun terakhir
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const datePrefix = `${year}${month}${day}`; // YYMMDD
|
||||
|
||||
// Gunakan counter berdasarkan existingBarcodes untuk sequential
|
||||
const existingCount = existingBarcodes.filter(b => b && b.startsWith(datePrefix)).length;
|
||||
const counter = existingCount + 1;
|
||||
const counterCode = String(counter).padStart(5, '0');
|
||||
return `${datePrefix}${counterCode}`;
|
||||
}
|
||||
|
||||
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 datePrefix = `${year}${month}${day}`; // YYMMDD
|
||||
|
||||
// Key untuk localStorage berdasarkan tanggal
|
||||
const STORAGE_KEY = `barcode_counter_${datePrefix}`;
|
||||
const LAST_DATE_KEY = 'barcode_last_date';
|
||||
|
||||
// Cek apakah tanggal sudah berubah (reset counter)
|
||||
const lastDate = localStorage.getItem(LAST_DATE_KEY);
|
||||
const currentDate = datePrefix;
|
||||
|
||||
let counter = 1; // Default mulai dari 1
|
||||
|
||||
if (lastDate === currentDate) {
|
||||
// Tanggal sama, lanjutkan counter dari localStorage
|
||||
const storedCounter = localStorage.getItem(STORAGE_KEY);
|
||||
if (storedCounter) {
|
||||
counter = parseInt(storedCounter, 10) || 1;
|
||||
}
|
||||
} else {
|
||||
// Tanggal berbeda, reset counter ke 1
|
||||
counter = 1;
|
||||
// Hapus counter lama untuk tanggal sebelumnya (cleanup)
|
||||
if (lastDate) {
|
||||
localStorage.removeItem(`barcode_counter_${lastDate}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate barcode dengan counter saat ini
|
||||
let barcode = `${datePrefix}${String(counter).padStart(5, '0')}`;
|
||||
|
||||
// Cek apakah barcode sudah ada di existingBarcodes
|
||||
let attempts = 0;
|
||||
const maxAttempts = 1000; // Maksimal 1000 pasien per hari
|
||||
|
||||
while (existingBarcodes.includes(barcode) && attempts < maxAttempts) {
|
||||
counter++;
|
||||
barcode = `${datePrefix}${String(counter).padStart(5, '0')}`;
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// Increment counter untuk next call dan simpan
|
||||
counter++;
|
||||
localStorage.setItem(STORAGE_KEY, String(counter));
|
||||
localStorage.setItem(LAST_DATE_KEY, currentDate);
|
||||
|
||||
return barcode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate QR Code data URL
|
||||
*/
|
||||
const generateQRCode = async (data: string): Promise<string> => {
|
||||
try {
|
||||
const qrDataUrl = await QRCode.toDataURL(data, {
|
||||
errorCorrectionLevel: 'M',
|
||||
errorCorrectionLevel: 'H', // High error correction for better scanning reliability
|
||||
type: 'image/png',
|
||||
quality: 1,
|
||||
margin: 2,
|
||||
width: 200, // Ukuran QR Code untuk thermal printer 80mm
|
||||
margin: 0.5, // Reduced margin untuk memperkecil Positioning Detection Markers
|
||||
width: 100, // Ukuran QR Code untuk thermal printer 80mm
|
||||
version: 3, // QR Code version 5 (37x37 modules)
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF'
|
||||
@@ -53,7 +131,7 @@ export const useThermalPrint = () => {
|
||||
}
|
||||
|
||||
// Array nama hari dalam bahasa Indonesia (lowercase)
|
||||
const days = ['minggu', 'senin', 'selasa', 'rabu', 'kamis', 'jumat', 'sabtu'];
|
||||
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'];
|
||||
|
||||
@@ -81,25 +159,20 @@ export const useThermalPrint = () => {
|
||||
*/
|
||||
const formatTime = (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().toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
date = new Date();
|
||||
}
|
||||
return date.toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
|
||||
return `${hours}:${minutes}`;
|
||||
} catch (error) {
|
||||
return new Date().toLocaleTimeString('id-ID', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
const date = new Date();
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -174,13 +247,22 @@ export const useThermalPrint = () => {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.ticket-container {
|
||||
.ticket-table {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
border-collapse: collapse;
|
||||
border: 2px dashed #000;
|
||||
padding: 2mm 4mm 4mm 4mm;
|
||||
margin: 0;
|
||||
background: white;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ticket-table td {
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ticket-content {
|
||||
padding: 2mm 4mm 4mm 4mm;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -215,14 +297,14 @@ export const useThermalPrint = () => {
|
||||
}
|
||||
|
||||
.qr-code-section {
|
||||
margin-top: -1mm;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5mm;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
width: 50mm;
|
||||
height: 50mm;
|
||||
width: 40mm;
|
||||
height: 40mm;
|
||||
margin: 0 auto;
|
||||
border: none;
|
||||
padding: 0.5mm;
|
||||
@@ -304,10 +386,10 @@ export const useThermalPrint = () => {
|
||||
}
|
||||
|
||||
.klinik-name {
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-top: 0.3mm;
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 2mm;
|
||||
padding-bottom: 0;
|
||||
text-transform: uppercase;
|
||||
overflow: visible;
|
||||
@@ -352,15 +434,16 @@ export const useThermalPrint = () => {
|
||||
padding: 2mm 4mm 6mm 4mm;
|
||||
}
|
||||
|
||||
.ticket-container {
|
||||
.ticket-table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.ticket-content {
|
||||
padding: 2mm 4mm 4mm 4mm;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Feed setelah print untuk auto cutter */
|
||||
.ticket-container::after {
|
||||
.ticket-table::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 10mm;
|
||||
@@ -384,86 +467,90 @@ export const useThermalPrint = () => {
|
||||
}
|
||||
|
||||
/* Pastikan tidak ada page break di tengah content */
|
||||
.ticket-container > * {
|
||||
.ticket-content > * {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ticket-container">
|
||||
<div class="header">
|
||||
<div class="hospital-name">RSUD DR. SAIFUL ANWAR</div>
|
||||
<div class="hospital-address">Jl. Jaksa Agung Suprapto No.2, Malang</div>
|
||||
<div class="ticket-title">Tiket Antrian</div>
|
||||
</div>
|
||||
|
||||
<div class="no-antrian">${noAntrianDisplay}</div>
|
||||
|
||||
${ruangInfo ? `<div class="klinik-name">${ruangInfo}</div>` : `<div class="klinik-name">${data.klinik}</div>`}
|
||||
|
||||
<div class="qr-code-section">
|
||||
<div class="qr-code">
|
||||
<img src="${qrCodeImage}" alt="QR Code" />
|
||||
</div>
|
||||
<div class="barcode-number">${data.barcode}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<div class="info-row">
|
||||
<div class="info-label-wrapper">
|
||||
<span class="info-label-text">Tanggal</span><span class="info-colon">:</span>
|
||||
<table class="ticket-table" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="ticket-content">
|
||||
<div class="header">
|
||||
<div class="hospital-name">RSUD DR. SAIFUL ANWAR</div>
|
||||
<div class="hospital-address">Jl. Jaksa Agung Suprapto No.2, Malang</div>
|
||||
<div class="ticket-title">Tiket Antrean</div>
|
||||
</div>
|
||||
<span class="info-value">${data.tanggal}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label-wrapper">
|
||||
<span class="info-label-text">Waktu</span><span class="info-colon">:</span>
|
||||
|
||||
<div class="no-antrian">${noAntrianDisplay}</div>
|
||||
|
||||
${ruangInfo ? `<div class="klinik-name">${ruangInfo}</div>` : `<div class="klinik-name">${data.klinik}</div>`}
|
||||
|
||||
<div class="qr-code-section">
|
||||
<div class="qr-code">
|
||||
<img src="${qrCodeImage}" alt="QR Code" />
|
||||
</div>
|
||||
<div class="barcode-number">${data.barcode}</div>
|
||||
</div>
|
||||
<span class="info-value">${data.waktu}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label-wrapper">
|
||||
<span class="info-label-text">Shift</span><span class="info-colon">:</span>
|
||||
|
||||
<div class="info-section">
|
||||
<div class="info-row">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
` : ''}
|
||||
</div>
|
||||
<span class="info-value">${data.shift}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label-wrapper">
|
||||
<span class="info-label-text">Pembayaran</span><span class="info-colon">:</span>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer-text">Tunjukkan tiket ini saat check-in</div>
|
||||
<div class="footer-text">QR Code digunakan untuk check-in pasien</div>
|
||||
<div class="footer-text">Terima kasih</div>
|
||||
</div>
|
||||
<span class="info-value">${data.pembayaran}</span>
|
||||
</div>
|
||||
|
||||
${data.namaDokter ? `
|
||||
<div class="info-row">
|
||||
<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>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer-text">Tunjukkan tiket ini saat check-in</div>
|
||||
<div class="footer-text">QR Code digunakan untuk check-in pasien</div>
|
||||
<div class="footer-text">Terima kasih</div>
|
||||
</div>
|
||||
|
||||
<!-- Feed lines untuk auto cutter (space setelah footer untuk cutter) -->
|
||||
<div class="cut-section"></div>
|
||||
|
||||
<!-- ESC/POS Auto Cut Command Comment (untuk printer driver yang support) -->
|
||||
<!-- ESC/POS Commands for EPSON M352A: -->
|
||||
<!-- ESC i = Full Cut (0x1B 0x69) -->
|
||||
<!-- ESC m = Partial Cut (0x1B 0x6D) -->
|
||||
<!-- GS V 0 = Full Cut (0x1D 0x56 0x00) -->
|
||||
<!-- GS V 1 = Partial Cut (0x1D 0x56 0x01) -->
|
||||
</div>
|
||||
|
||||
<!-- Feed lines untuk auto cutter (space setelah footer untuk cutter) -->
|
||||
<div class="cut-section"></div>
|
||||
|
||||
<!-- ESC/POS Auto Cut Command Comment (untuk printer driver yang support) -->
|
||||
<!-- ESC/POS Commands for EPSON M352A: -->
|
||||
<!-- ESC i = Full Cut (0x1B 0x69) -->
|
||||
<!-- ESC m = Partial Cut (0x1B 0x6D) -->
|
||||
<!-- GS V 0 = Full Cut (0x1D 0x56 0x00) -->
|
||||
<!-- GS V 1 = Partial Cut (0x1D 0x56 0x01) -->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
@@ -612,6 +699,7 @@ export const useThermalPrint = () => {
|
||||
isPrinting,
|
||||
printTicket,
|
||||
printTicketFromPatient,
|
||||
generateQRCode
|
||||
generateQRCode,
|
||||
generateBarcode
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user