<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Receipt Generator Pro</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- html2pdf.js for Receipt-Sized PDF Output -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<!-- Authentic Receipt Monospace & UI Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
body {
font-family: 'Inter', sans-serif;
}
.receipt-font {
font-family: 'Space Mono', 'Courier New', Courier, monospace;
}
/* 80mm standard POS paper styling */
.receipt-container-80mm {
width: 80mm;
min-height: 120mm;
padding: 6mm 5mm;
background: #ffffff;
color: #000000;
box-sizing: border-box;
}
/* 58mm compact POS paper styling */
.receipt-container-58mm {
width: 58mm;
min-height: 100mm;
padding: 4mm 3mm;
background: #ffffff;
color: #000000;
box-sizing: border-box;
font-size: 10px !important;
}
.dashed-line {
border-top: 1px dashed #000000;
margin: 8px 0;
}
.double-line {
border-top: 3px double #000000;
margin: 8px 0;
}
.receipt-paper-wrapper {
position: relative;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.15), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
}
/* Print media optimization for direct thermal printers */
@media print {
@page {
size: 80mm auto;
margin: 0mm;
}
body {
background: #ffffff !important;
margin: 0 !important;
padding: 0 !important;
}
.no-print {
display: none !important;
}
.print-area {
display: block !important;
margin: 0 !important;
padding: 0 !important;
box-shadow: none !important;
}
#receipt-element {
box-shadow: none !important;
margin: 0 !important;
}
}
</style>
</head>
<body class="bg-slate-100 min-h-screen text-slate-800">
<!-- Header Bar -->
<header class="bg-slate-900 text-white py-4 px-6 shadow-md no-print">
<div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="flex items-center gap-3">
<div class="bg-emerald-500 text-slate-900 font-bold p-2 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<h1 class="text-xl font-bold tracking-tight">ReceiptGen Pro</h1>
<p class="text-xs text-slate-400">Custom Thermal Receipt Builder & Receipt-Size PDF Exporter</p>
</div>
</div>
<div class="flex flex-wrap items-center gap-3">
<button onclick="resetForm()" class="px-3.5 py-2 text-xs font-medium bg-slate-800 hover:bg-slate-700 text-slate-200 rounded-lg border border-slate-700 transition">
Reset Defaults
</button>
<button onclick="window.print()" class="px-4 py-2 text-sm font-medium bg-slate-700 hover:bg-slate-600 text-white rounded-lg flex items-center gap-2 transition">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
</svg>
Print Thermal
</button>
<button onclick="downloadReceiptPDF()" class="px-4 py-2 text-sm font-semibold bg-emerald-500 hover:bg-emerald-400 text-slate-950 rounded-lg flex items-center gap-2 shadow-sm transition">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
Download Receipt PDF
</button>
</div>
</div>
</header>
<!-- Main Interface -->
<main class="max-w-7xl mx-auto p-4 sm:p-6 grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Form Section -->
<section class="lg:col-span-7 space-y-6 no-print">
<!-- Paper & Currency -->
<div class="bg-white p-5 rounded-xl shadow-sm border border-slate-200">
<h2 class="text-sm font-bold uppercase tracking-wider text-slate-500 mb-4">1. Format & Paper Size</h2>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Paper Width</label>
<select id="paperWidth" onchange="updateReceiptSize()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="80">80mm (Standard POS)</option>
<option value="58">58mm (Compact Thermal)</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Currency Symbol</label>
<input type="text" id="currencySymbol" value="RM" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" placeholder="RM, $, €, £" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Show Barcode</label>
<select id="showBarcode" onchange="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="yes">Yes (Bottom Barcode)</option>
<option value="no">No</option>
</select>
</div>
</div>
</div>
<!-- Merchant Details -->
<div class="bg-white p-5 rounded-xl shadow-sm border border-slate-200">
<h2 class="text-sm font-bold uppercase tracking-wider text-slate-500 mb-4">2. Store / Business Information</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="sm:col-span-2">
<label class="block text-xs font-semibold text-slate-600 mb-1">Business / Store Name</label>
<input type="text" id="storeName" value="KOPI & CO. ROASTERS" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Registration / Tax ID</label>
<input type="text" id="storeReg" value="SSM: 202401009876 (1456789-X)" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Phone Number</label>
<input type="text" id="storePhone" value="+60 3-2145 8899" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
<div class="sm:col-span-2">
<label class="block text-xs font-semibold text-slate-600 mb-1">Store Address</label>
<textarea id="storeAddress" rows="2" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none">Lot G-12, Jalan Bukit Bintang,
55100 Kuala Lumpur, Malaysia</textarea>
</div>
</div>
</div>
<!-- Invoice Details -->
<div class="bg-white p-5 rounded-xl shadow-sm border border-slate-200">
<h2 class="text-sm font-bold uppercase tracking-wider text-slate-500 mb-4">3. Receipt & Transaction Details</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Receipt / Invoice No.</label>
<input type="text" id="receiptNo" value="INV-2025-0842" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Date & Time</label>
<input type="text" id="receiptDate" value="" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Cashier / Staff</label>
<input type="text" id="cashierName" value="Farhan" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Customer / Table (Optional)</label>
<input type="text" id="customerName" value="Walk-in (Table 04)" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
</div>
</div>
<!-- Line Items -->
<div class="bg-white p-5 rounded-xl shadow-sm border border-slate-200">
<div class="flex items-center justify-between mb-4">
<h2 class="text-sm font-bold uppercase tracking-wider text-slate-500">4. Purchased Items</h2>
<button onclick="addItem()" class="px-3 py-1.5 bg-emerald-50 hover:bg-emerald-100 text-emerald-700 font-semibold text-xs rounded-lg border border-emerald-200 transition flex items-center gap-1">
+ Add Item
</button>
</div>
<div id="itemsList" class="space-y-3"></div>
</div>
<!-- Taxes, Discounts, Payment -->
<div class="bg-white p-5 rounded-xl shadow-sm border border-slate-200">
<h2 class="text-sm font-bold uppercase tracking-wider text-slate-500 mb-4">5. Tax, Discounts & Payment</h2>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4">
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Discount Amount</label>
<input type="number" step="0.01" id="discountAmt" value="0.00" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Tax / SST Rate (%)</label>
<input type="number" step="0.1" id="taxRate" value="6" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Service Charge (%)</label>
<input type="number" step="0.1" id="serviceRate" value="0" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Payment Method</label>
<select id="paymentMethod" onchange="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="CASH">CASH</option>
<option value="DUITNOW QR">DUITNOW QR</option>
<option value="VISA / MASTERCARD">VISA / MASTERCARD</option>
<option value="E-WALLET">E-WALLET</option>
<option value="ONLINE TRANSFER">ONLINE TRANSFER</option>
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-600 mb-1">Amount Paid / Tendered</label>
<input type="number" step="0.01" id="amountPaid" value="50.00" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
</div>
<div class="sm:col-span-2">
<label class="block text-xs font-semibold text-slate-600 mb-1">Footer Message</label>
<textarea id="footerNote" rows="2" oninput="renderReceipt()" class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:ring-2 focus:ring-emerald-500 outline-none">Thank you for visiting!
Goods sold are not returnable or refundable.</textarea>
</div>
</div>
</div>
</section>
<!-- Live Receipt Preview Section -->
<section class="lg:col-span-5 flex flex-col items-center">
<div class="sticky top-6 flex flex-col items-center w-full">
<div class="mb-3 flex items-center justify-between w-full max-w-[80mm] no-print">
<span class="text-xs font-semibold uppercase tracking-wider text-slate-500">Live Preview</span>
<span id="sizeBadge" class="text-xs bg-slate-200 text-slate-700 font-medium px-2 py-0.5 rounded">80mm Thermal</span>
</div>
<div class="print-area receipt-paper-wrapper bg-white">
<div id="receipt-element" class="receipt-container-80mm receipt-font text-xs leading-relaxed">
<!-- Store Header -->
<div class="text-center">
<div id="r-storeName" class="font-bold text-base tracking-tight uppercase"></div>
<div id="r-storeReg" class="text-[10px] mt-0.5"></div>
<div id="r-storeAddress" class="text-[11px] whitespace-pre-line mt-1"></div>
<div id="r-storePhone" class="text-[11px] mt-0.5"></div>
</div>
<div class="dashed-line"></div>
<!-- Transaction Info -->
<div class="text-[11px] space-y-0.5">
<div class="flex justify-between">
<span>Receipt #:</span>
<span id="r-receiptNo" class="font-bold"></span>
</div>
<div class="flex justify-between">
<span>Date:</span>
<span id="r-receiptDate"></span>
</div>
<div class="flex justify-between">
<span>Cashier:</span>
<span id="r-cashierName"></span>
</div>
<div id="r-customerRow" class="flex justify-between">
<span>Customer:</span>
<span id="r-customerName"></span>
</div>
</div>
<div class="dashed-line"></div>
<!-- Items Table Header -->
<div class="flex justify-between font-bold text-[11px]">
<span>ITEM</span>
<span>AMT (<span class="r-curr"></span>)</span>
</div>
<div class="dashed-line"></div>
<!-- Items Output -->
<div id="r-itemsList" class="space-y-1.5 text-[11px]"></div>
<div class="dashed-line"></div>
<!-- Totals Breakdown -->
<div class="space-y-1 text-[11px]">
<div class="flex justify-between">
<span>Subtotal</span>
<span id="r-subtotal">0.00</span>
</div>
<div id="r-discountRow" class="flex justify-between">
<span>Discount</span>
<span id="r-discount">-0.00</span>
</div>
<div id="r-serviceRow" class="flex justify-between">
<span>Service Charge (<span id="r-serviceRate">0</span>%)</span>
<span id="r-serviceAmt">0.00</span>
</div>
<div id="r-taxRow" class="flex justify-between">
<span>Tax / SST (<span id="r-taxRate">6</span>%)</span>
<span id="r-taxAmt">0.00</span>
</div>
</div>
<div class="double-line"></div>
<div class="flex justify-between font-bold text-sm my-1">
<span>TOTAL</span>
<span><span class="r-curr"></span> <span id="r-total">0.00</span></span>
</div>
<div class="dashed-line"></div>
<!-- Payment & Change -->
<div class="space-y-0.5 text-[11px]">
<div class="flex justify-between">
<span id="r-paymentMethod">CASH</span>
<span id="r-amountPaid">0.00</span>
</div>
<div class="flex justify-between font-bold">
<span>CHANGE</span>
<span id="r-change">0.00</span>
</div>
</div>
<div class="dashed-line"></div>
<!-- Footer Message -->
<div id="r-footerNote" class="text-center text-[10px] whitespace-pre-line mt-3"></div>
<!-- Barcode -->
<div id="r-barcodeContainer" class="mt-4 flex flex-col items-center">
<div class="h-9 w-44 bg-[repeating-linear-gradient(90deg,#000,#000_2px,#fff_2px,#fff_4px,#000_4px,#000_5px,#fff_5px,#fff_8px,#000_8px,#000_11px,#fff_11px,#fff_13px)]"></div>
<span id="r-barcodeText" class="text-[9px] tracking-widest mt-1"></span>
</div>
</div>
</div>
</div>
</section>
</main>
<script>
let items = [
{ name: "Iced Spanish Latte", qty: 2, price: 13.50 },
{ name: "Butter Croissant", qty: 1, price: 9.00 },
{ name: "Espresso Macchiato", qty: 1, price: 10.00 }
];
function setInitialDateTime() {
const now = new Date();
const formatted = now.getFullYear() + "-" +
String(now.getMonth() + 1).padStart(2, '0') + "-" +
String(now.getDate()).padStart(2, '0') + " " +
String(now.getHours()).padStart(2, '0') + ":" +
String(now.getMinutes()).padStart(2, '0');
document.getElementById('receiptDate').value = formatted;
}
function renderItemInputs() {
const container = document.getElementById('itemsList');
container.innerHTML = '';
items.forEach((item, index) => {
const row = document.createElement('div');
row.className = 'grid grid-cols-12 gap-2 items-center bg-slate-50 p-2 rounded-lg border border-slate-200';
row.innerHTML = `
<div class="col-span-6">
<label class="block text-[10px] text-slate-400 uppercase">Item Description</label>
<input type="text" value="${item.name}" oninput="updateItem(${index}, 'name', this.value)"
class="w-full bg-white rounded border border-slate-300 px-2 py-1 text-xs focus:ring-1 focus:ring-emerald-500 outline-none" />
</div>
<div class="col-span-2">
<label class="block text-[10px] text-slate-400 uppercase">Qty</label>
<input type="number" min="1" step="1" value="${item.qty}" oninput="updateItem(${index}, 'qty', this.value)"
class="w-full bg-white rounded border border-slate-300 px-2 py-1 text-xs focus:ring-1 focus:ring-emerald-500 outline-none" />
</div>
<div class="col-span-3">
<label class="block text-[10px] text-slate-400 uppercase">Unit Price</label>
<input type="number" step="0.01" value="${item.price}" oninput="updateItem(${index}, 'price', this.value)"
class="w-full bg-white rounded border border-slate-300 px-2 py-1 text-xs focus:ring-1 focus:ring-emerald-500 outline-none" />
</div>
<div class="col-span-1 flex justify-end pt-4">
<button onclick="removeItem(${index})" class="text-rose-500 hover:text-rose-700 p-1 font-bold" title="Remove Item">✕</button>
</div>
`;
container.appendChild(row);
});
}
function updateItem(index, field, value) {
if (field === 'qty' || field === 'price') {
items[index][field] = parseFloat(value) || 0;
} else {
items[index][field] = value;
}
renderReceipt();
}
function addItem() {
items.push({ name: "New Item", qty: 1, price: 5.00 });
renderItemInputs();
renderReceipt();
}
function removeItem(index) {
if (items.length <= 1) return;
items.splice(index, 1);
renderItemInputs();
renderReceipt();
}
function updateReceiptSize() {
const width = document.getElementById('paperWidth').value;
const receiptEl = document.getElementById('receipt-element');
const badge = document.getElementById('sizeBadge');
if (width === '58') {
receiptEl.classList.remove('receipt-container-80mm');
receiptEl.classList.add('receipt-container-58mm');
badge.textContent = '58mm Thermal';
} else {
receiptEl.classList.remove('receipt-container-58mm');
receiptEl.classList.add('receipt-container-80mm');
badge.textContent = '80mm Thermal';
}
}
function renderReceipt() {
const curr = document.getElementById('currencySymbol').value || '';
document.querySelectorAll('.r-curr').forEach(el => el.textContent = curr);
document.getElementById('r-storeName').textContent = document.getElementById('storeName').value;
document.getElementById('r-storeReg').textContent = document.getElementById('storeReg').value;
document.getElementById('r-storeAddress').textContent = document.getElementById('storeAddress').value;
const phone = document.getElementById('storePhone').value;
document.getElementById('r-storePhone').textContent = phone ? `Tel: ${phone}` : '';
const receiptNo = document.getElementById('receiptNo').value;
document.getElementById('r-receiptNo').textContent = receiptNo;
document.getElementById('r-barcodeText').textContent = `*${receiptNo}*`;
document.getElementById('r-receiptDate').textContent = document.getElementById('receiptDate').value;
document.getElementById('cashierName').textContent = document.getElementById('cashierName').value;
const customer = document.getElementById('customerName').value;
const customerRow = document.getElementById('r-customerRow');
if (customer.trim() !== '') {
customerRow.style.display = 'flex';
document.getElementById('r-customerName').textContent = customer;
} else {
customerRow.style.display = 'none';
}
const rItemsContainer = document.getElementById('r-itemsList');
rItemsContainer.innerHTML = '';
let subtotal = 0;
items.forEach(item => {
const lineTotal = item.qty * item.price;
subtotal += lineTotal;
const itemDiv = document.createElement('div');
itemDiv.innerHTML = `
<div class="font-medium">${item.name || 'Unnamed Item'}</div>
<div class="flex justify-between text-[10px] pl-2">
<span>${item.qty} x ${item.price.toFixed(2)}</span>
<span>${lineTotal.toFixed(2)}</span>
</div>
`;
rItemsContainer.appendChild(itemDiv);
});
const discount = parseFloat(document.getElementById('discountAmt').value) || 0;
const taxRate = parseFloat(document.getElementById('taxRate').value) || 0;
const serviceRate = parseFloat(document.getElementById('serviceRate').value) || 0;
const taxableAmount = Math.max(0, subtotal - discount);
const serviceAmt = taxableAmount * (serviceRate / 100);
const taxAmt = taxableAmount * (taxRate / 100);
const grandTotal = taxableAmount + serviceAmt + taxAmt;
document.getElementById('r-subtotal').textContent = subtotal.toFixed(2);
const discountRow = document.getElementById('r-discountRow');
if (discount > 0) {
discountRow.style.display = 'flex';
document.getElementById('r-discount').textContent = `-${discount.toFixed(2)}`;
} else {
discountRow.style.display = 'none';
}
const serviceRow = document.getElementById('r-serviceRow');
if (serviceRate > 0) {
serviceRow.style.display = 'flex';
document.getElementById('r-serviceRate').textContent = serviceRate;
document.getElementById('r-serviceAmt').textContent = serviceAmt.toFixed(2);
} else {
serviceRow.style.display = 'none';
}
const taxRow = document.getElementById('r-taxRow');
if (taxRate > 0) {
taxRow.style.display = 'flex';
document.getElementById('r-taxRate').textContent = taxRate;
document.getElementById('r-taxAmt').textContent = taxAmt.toFixed(2);
} else {
taxRow.style.display = 'none';
}
document.getElementById('r-total').textContent = grandTotal.toFixed(2);
const paymentMethod = document.getElementById('paymentMethod').value;
const amountPaid = parseFloat(document.getElementById('amountPaid').value) || 0;
const change = Math.max(0, amountPaid - grandTotal);
document.getElementById('r-paymentMethod').textContent = paymentMethod;
document.getElementById('r-amountPaid').textContent = `${curr} ${amountPaid.toFixed(2)}`;
document.getElementById('r-change').textContent = `${curr} ${change.toFixed(2)}`;
document.getElementById('r-footerNote').textContent = document.getElementById('footerNote').value;
const showBarcode = document.getElementById('showBarcode').value;
document.getElementById('r-barcodeContainer').style.display = showBarcode === 'yes' ? 'flex' : 'none';
}
function downloadReceiptPDF() {
const receiptEl = document.getElementById('receipt-element');
const paperWidthMM = parseInt(document.getElementById('paperWidth').value, 10) || 80;
const receiptNo = document.getElementById('receiptNo').value || 'receipt';
const pixelWidth = receiptEl.offsetWidth;
const pixelHeight = receiptEl.offsetHeight;
const calculatedHeightMM = Math.ceil((pixelHeight / pixelWidth) * paperWidthMM) + 4;
const opt = {
margin: 0,
filename: `${receiptNo}.pdf`,
image: { type: 'jpeg', quality: 1 },
html2canvas: {
scale: 3,
useCORS: true,
backgroundColor: '#ffffff'
},
jsPDF: {
unit: 'mm',
format: [paperWidthMM, calculatedHeightMM],
orientation: 'portrait'
}
};
html2pdf().set(opt).from(receiptEl).save();
}
function resetForm() {
document.getElementById('storeName').value = "KOPI & CO. ROASTERS";
document.getElementById('storeReg').value = "SSM: 202401009876 (1456789-X)";
document.getElementById('storePhone').value = "+60 3-2145 8899";
document.getElementById('storeAddress').value = "Lot G-12, Jalan Bukit Bintang,\n55100 Kuala Lumpur, Malaysia";
document.getElementById('receiptNo').value = "INV-2025-0842";
document.getElementById('cashierName').value = "Farhan";
document.getElementById('customerName').value = "Walk-in (Table 04)";
document.getElementById('discountAmt').value = "0.00";
document.getElementById('taxRate').value = "6";
document.getElementById('serviceRate').value = "0";
document.getElementById('amountPaid').value = "50.00";
setInitialDateTime();
items = [
{ name: "Iced Spanish Latte", qty: 2, price: 13.50 },
{ name: "Butter Croissant", qty: 1, price: 9.00 },
{ name: "Espresso Macchiato", qty: 1, price: 10.00 }
];
renderItemInputs();
renderReceipt();
}
window.onload = () => {
setInitialDateTime();
renderItemInputs();
renderReceipt();
};
</script>
</body>
</html>