| <!DOCTYPE html> |
| <html lang="my"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> |
| <title>Cashier - POS</title> |
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Myanmar:wght@300;400;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet"> |
| <script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script> |
| <style> |
| * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } |
| |
| :root { |
| --bg: #0a0e1a; |
| --card: #111827; |
| --card2: #1a2234; |
| --border: #1e293b; |
| --accent: #f59e0b; |
| --green: #10b981; |
| --red: #ef4444; |
| --text: #f1f5f9; |
| --muted: #64748b; |
| --scan: #3b82f6; |
| } |
| |
| body { |
| background: var(--bg); |
| color: var(--text); |
| font-family: 'Noto Sans Myanmar', sans-serif; |
| min-height: 100vh; |
| max-width: 480px; |
| margin: 0 auto; |
| } |
| |
| |
| .header { |
| background: var(--card); |
| border-bottom: 1px solid var(--border); |
| padding: 14px 16px; |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| position: sticky; top: 0; z-index: 100; |
| } |
| |
| .header-left { display: flex; align-items: center; gap: 10px; } |
| .logo-sm { |
| width: 36px; height: 36px; |
| background: linear-gradient(135deg, var(--accent), #d97706); |
| border-radius: 10px; |
| display: flex; align-items: center; justify-content: center; |
| font-size: 16px; |
| } |
| .header h2 { font-size: 15px; font-weight: 600; } |
| .header p { font-size: 11px; color: var(--muted); } |
| |
| .btn-logout { |
| background: transparent; |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| padding: 7px 12px; |
| color: var(--muted); |
| font-size: 12px; |
| cursor: pointer; |
| font-family: 'Noto Sans Myanmar', sans-serif; |
| } |
| |
| |
| .scanner-section { |
| padding: 16px; |
| } |
| |
| .scan-toggle { |
| width: 100%; |
| background: linear-gradient(135deg, var(--scan), #2563eb); |
| border: none; |
| border-radius: 14px; |
| padding: 16px; |
| color: white; |
| font-size: 15px; |
| font-weight: 600; |
| font-family: 'Noto Sans Myanmar', sans-serif; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 10px; |
| transition: all 0.2s; |
| box-shadow: 0 4px 16px rgba(59,130,246,0.3); |
| } |
| |
| .scan-toggle.active { |
| background: linear-gradient(135deg, var(--red), #dc2626); |
| box-shadow: 0 4px 16px rgba(239,68,68,0.3); |
| } |
| |
| .scanner-wrap { |
| display: none; |
| margin-top: 12px; |
| border-radius: 14px; |
| overflow: hidden; |
| border: 2px solid var(--scan); |
| position: relative; |
| } |
| |
| .scanner-wrap.visible { display: block; } |
| |
| #reader { width: 100%; } |
| |
| .scan-overlay { |
| position: absolute; inset: 0; |
| display: flex; align-items: center; justify-content: center; |
| pointer-events: none; |
| } |
| |
| .scan-frame { |
| width: 200px; height: 100px; |
| border: 2px solid var(--accent); |
| border-radius: 8px; |
| box-shadow: 0 0 0 9999px rgba(0,0,0,0.4); |
| position: relative; |
| } |
| |
| .scan-frame::before, .scan-frame::after { |
| content: ''; |
| position: absolute; |
| width: 20px; height: 20px; |
| border-color: var(--accent); |
| border-style: solid; |
| } |
| .scan-frame::before { top: -2px; left: -2px; border-width: 3px 0 0 3px; } |
| .scan-frame::after { bottom: -2px; right: -2px; border-width: 0 3px 3px 0; } |
| |
| .scan-line { |
| position: absolute; |
| left: 0; right: 0; height: 2px; |
| background: var(--accent); |
| animation: scanMove 2s ease-in-out infinite; |
| } |
| |
| @keyframes scanMove { |
| 0% { top: 5px; } |
| 100% { top: calc(100% - 5px); } |
| } |
| |
| |
| .manual-input { |
| display: flex; gap: 8px; |
| margin-top: 10px; |
| } |
| |
| .manual-input input { |
| flex: 1; |
| background: var(--card2); |
| border: 1px solid var(--border); |
| border-radius: 10px; |
| padding: 12px 14px; |
| color: var(--text); |
| font-size: 14px; |
| font-family: 'JetBrains Mono', monospace; |
| outline: none; |
| } |
| |
| .manual-input input:focus { border-color: var(--scan); } |
| |
| .btn-search { |
| background: var(--scan); |
| border: none; |
| border-radius: 10px; |
| padding: 12px 16px; |
| color: white; |
| font-size: 18px; |
| cursor: pointer; |
| } |
| |
| |
| .product-toast { |
| margin: 0 16px; |
| background: rgba(16,185,129,0.1); |
| border: 1px solid rgba(16,185,129,0.3); |
| border-radius: 12px; |
| padding: 14px 16px; |
| display: none; |
| align-items: center; |
| gap: 12px; |
| animation: fadeIn 0.3s ease; |
| } |
| |
| .product-toast.show { display: flex; } |
| .product-toast.error { |
| background: rgba(239,68,68,0.1); |
| border-color: rgba(239,68,68,0.3); |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(-8px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .toast-icon { font-size: 28px; } |
| .toast-info { flex: 1; } |
| .toast-info h3 { font-size: 14px; font-weight: 600; } |
| .toast-info p { font-size: 13px; color: var(--accent); font-weight: 700; margin-top: 2px; } |
| .toast-info .toast-err { color: #fca5a5; font-size: 12px; } |
| |
| |
| .cart-section { |
| padding: 16px; |
| margin-top: 8px; |
| } |
| |
| .cart-header { |
| display: flex; align-items: center; justify-content: space-between; |
| margin-bottom: 12px; |
| } |
| |
| .cart-title { |
| font-size: 14px; |
| font-weight: 700; |
| display: flex; align-items: center; gap: 8px; |
| } |
| |
| .cart-badge { |
| background: var(--accent); |
| color: #000; |
| border-radius: 20px; |
| padding: 2px 8px; |
| font-size: 11px; |
| font-weight: 700; |
| font-family: 'JetBrains Mono', monospace; |
| } |
| |
| .btn-clear { |
| background: transparent; |
| border: 1px solid rgba(239,68,68,0.3); |
| border-radius: 8px; |
| padding: 5px 10px; |
| color: var(--red); |
| font-size: 11px; |
| cursor: pointer; |
| font-family: 'Noto Sans Myanmar', sans-serif; |
| } |
| |
| .cart-empty { |
| text-align: center; |
| padding: 32px 16px; |
| color: var(--muted); |
| font-size: 13px; |
| background: var(--card2); |
| border-radius: 12px; |
| border: 1px dashed var(--border); |
| } |
| |
| .cart-empty span { font-size: 36px; display: block; margin-bottom: 8px; } |
| |
| .cart-item { |
| background: var(--card); |
| border: 1px solid var(--border); |
| border-radius: 12px; |
| padding: 12px 14px; |
| margin-bottom: 8px; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| animation: slideIn 0.2s ease; |
| } |
| |
| @keyframes slideIn { |
| from { opacity: 0; transform: translateX(-10px); } |
| to { opacity: 1; transform: translateX(0); } |
| } |
| |
| .item-info { flex: 1; min-width: 0; } |
| .item-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| .item-price { font-size: 12px; color: var(--accent); font-family: 'JetBrains Mono', monospace; margin-top: 2px; } |
| |
| .qty-ctrl { |
| display: flex; align-items: center; gap: 8px; |
| flex-shrink: 0; |
| } |
| |
| .qty-btn { |
| width: 30px; height: 30px; |
| border-radius: 8px; |
| border: 1px solid var(--border); |
| background: var(--card2); |
| color: var(--text); |
| font-size: 16px; |
| cursor: pointer; |
| display: flex; align-items: center; justify-content: center; |
| transition: all 0.15s; |
| } |
| |
| .qty-btn:active { transform: scale(0.9); } |
| .qty-btn.minus:active { background: rgba(239,68,68,0.2); } |
| .qty-btn.plus:active { background: rgba(16,185,129,0.2); } |
| |
| .qty-num { |
| font-family: 'JetBrains Mono', monospace; |
| font-weight: 700; |
| font-size: 15px; |
| min-width: 24px; |
| text-align: center; |
| } |
| |
| .item-total { |
| font-family: 'JetBrains Mono', monospace; |
| font-weight: 700; |
| font-size: 13px; |
| color: var(--green); |
| min-width: 60px; |
| text-align: right; |
| flex-shrink: 0; |
| } |
| |
| .btn-del { |
| background: transparent; |
| border: none; |
| color: var(--muted); |
| font-size: 16px; |
| cursor: pointer; |
| padding: 4px; |
| transition: color 0.15s; |
| } |
| |
| .btn-del:hover { color: var(--red); } |
| |
| |
| .checkout-bar { |
| position: sticky; bottom: 0; |
| background: var(--card); |
| border-top: 1px solid var(--border); |
| padding: 16px; |
| z-index: 100; |
| } |
| |
| .total-row { |
| display: flex; justify-content: space-between; |
| align-items: center; |
| margin-bottom: 12px; |
| } |
| |
| .total-label { font-size: 13px; color: var(--muted); } |
| .total-amount { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 22px; |
| font-weight: 700; |
| color: var(--accent); |
| } |
| |
| .cash-input-row { |
| display: flex; gap: 8px; |
| margin-bottom: 12px; |
| align-items: center; |
| } |
| |
| .cash-input-row label { color: var(--muted); font-size: 12px; white-space: nowrap; } |
| |
| .cash-input { |
| flex: 1; |
| background: var(--card2); |
| border: 1px solid var(--border); |
| border-radius: 10px; |
| padding: 10px 14px; |
| color: var(--text); |
| font-size: 16px; |
| font-family: 'JetBrains Mono', monospace; |
| font-weight: 700; |
| outline: none; |
| text-align: right; |
| } |
| |
| .cash-input:focus { border-color: var(--accent); } |
| |
| .change-row { |
| display: flex; justify-content: space-between; |
| margin-bottom: 12px; |
| padding: 8px 12px; |
| background: rgba(16,185,129,0.1); |
| border-radius: 8px; |
| display: none; |
| } |
| |
| .change-row.show { display: flex; } |
| .change-row span { font-size: 13px; color: var(--green); } |
| .change-row strong { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 15px; |
| color: var(--green); |
| } |
| |
| .btn-checkout { |
| width: 100%; |
| background: linear-gradient(135deg, var(--accent), #d97706); |
| border: none; |
| border-radius: 12px; |
| padding: 16px; |
| color: #000; |
| font-size: 16px; |
| font-weight: 700; |
| font-family: 'Noto Sans Myanmar', sans-serif; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .btn-checkout:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(245,158,11,0.4); } |
| .btn-checkout:active { transform: scale(0.98); } |
| .btn-checkout:disabled { opacity: 0.4; cursor: not-allowed; transform: none; } |
| |
| |
| .modal-overlay { |
| position: fixed; inset: 0; |
| background: rgba(0,0,0,0.8); |
| z-index: 200; |
| display: flex; align-items: flex-end; justify-content: center; |
| display: none; |
| } |
| |
| .modal-overlay.show { display: flex; animation: fadeIn 0.2s; } |
| |
| .receipt-modal { |
| background: var(--card); |
| border-radius: 20px 20px 0 0; |
| padding: 24px 20px; |
| width: 100%; max-width: 480px; |
| max-height: 90vh; |
| overflow-y: auto; |
| animation: slideUp2 0.3s ease; |
| } |
| |
| @keyframes slideUp2 { |
| from { transform: translateY(100%); } |
| to { transform: translateY(0); } |
| } |
| |
| .receipt-header { |
| text-align: center; margin-bottom: 20px; |
| } |
| |
| .receipt-header h2 { font-size: 18px; margin-bottom: 4px; } |
| .receipt-header p { color: var(--muted); font-size: 12px; font-family: 'JetBrains Mono', monospace; } |
| |
| .receipt-body { |
| background: #0f172a; |
| border-radius: 12px; |
| padding: 16px; |
| margin-bottom: 16px; |
| font-family: 'JetBrains Mono', monospace; |
| } |
| |
| .r-row { |
| display: flex; justify-content: space-between; |
| font-size: 12px; |
| padding: 4px 0; |
| border-bottom: 1px dashed rgba(255,255,255,0.06); |
| } |
| |
| .r-row:last-child { border: none; } |
| .r-total { |
| font-size: 15px; font-weight: 700; |
| color: var(--accent); |
| padding-top: 8px; |
| border-top: 1px solid var(--border); |
| margin-top: 4px; |
| } |
| |
| .receipt-actions { |
| display: grid; grid-template-columns: 1fr 1fr; |
| gap: 10px; |
| } |
| |
| .btn-print { |
| background: var(--scan); |
| border: none; border-radius: 10px; |
| padding: 13px; color: white; |
| font-size: 14px; font-weight: 600; |
| cursor: pointer; |
| font-family: 'Noto Sans Myanmar', sans-serif; |
| } |
| |
| .btn-new { |
| background: var(--green); |
| border: none; border-radius: 10px; |
| padding: 13px; color: white; |
| font-size: 14px; font-weight: 600; |
| cursor: pointer; |
| font-family: 'Noto Sans Myanmar', sans-serif; |
| } |
| |
| |
| @media print { |
| body * { visibility: hidden; } |
| #printArea, #printArea * { visibility: visible; } |
| #printArea { |
| position: fixed; left: 0; top: 0; |
| width: 80mm; |
| font-family: monospace; |
| font-size: 12px; |
| color: #000; |
| } |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="header"> |
| <div class="header-left"> |
| <div class="logo-sm">🛒</div> |
| <div> |
| <h2>Cashier POS</h2> |
| <p id="cashierName">Loading...</p> |
| </div> |
| </div> |
| <button class="btn-logout" onclick="logout()">ထွက်မည်</button> |
| </div> |
|
|
| |
| <div class="scanner-section"> |
| <button class="scan-toggle" id="scanBtn" onclick="toggleScanner()"> |
| <span id="scanIcon">📷</span> |
| <span id="scanText">ကင်မရာဖြင့် Scan လုပ်မည်</span> |
| </button> |
|
|
| <div class="scanner-wrap" id="scannerWrap"> |
| <div id="reader"></div> |
| <div class="scan-overlay"> |
| <div class="scan-frame"> |
| <div class="scan-line"></div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="manual-input"> |
| <input type="text" id="barcodeInput" placeholder="Barcode ကိုယ်တိုင်ရိုက်ထည့်ပါ..." |
| inputmode="numeric" onkeydown="if(event.key==='Enter') manualSearch()"> |
| <button class="btn-search" onclick="manualSearch()">🔍</button> |
| </div> |
| </div> |
|
|
| |
| <div class="product-toast" id="productToast"> |
| <span class="toast-icon" id="toastIcon">📦</span> |
| <div class="toast-info"> |
| <h3 id="toastName"></h3> |
| <p id="toastPrice"></p> |
| <p class="toast-err" id="toastErr"></p> |
| </div> |
| </div> |
|
|
| |
| <div class="cart-section"> |
| <div class="cart-header"> |
| <div class="cart-title"> |
| 🛒 Cart <span class="cart-badge" id="cartBadge">0</span> |
| </div> |
| <button class="btn-clear" onclick="clearCart()">အားလုံးဖျက်</button> |
| </div> |
|
|
| <div id="cartEmpty" class="cart-empty"> |
| <span>🛍️</span> |
| Barcode Scan လုပ်ပြီး ကုန်ပစ္စည်း ထည့်ပါ |
| </div> |
|
|
| <div id="cartItems"></div> |
| </div> |
|
|
| |
| <div class="checkout-bar"> |
| <div class="total-row"> |
| <span class="total-label">စုစုပေါင်းဈေး</span> |
| <span class="total-amount" id="totalDisplay">0 ကျပ်</span> |
| </div> |
|
|
| <div class="cash-input-row"> |
| <label>ငွေပေး</label> |
| <input type="number" class="cash-input" id="cashInput" |
| placeholder="0" oninput="calcChange()" inputmode="numeric"> |
| </div> |
|
|
| <div class="change-row" id="changeRow"> |
| <span>အကြွေ</span> |
| <strong id="changeDisplay">0 ကျပ်</strong> |
| </div> |
|
|
| <button class="btn-checkout" id="checkoutBtn" onclick="checkout()" disabled> |
| 💰 ငွေရှင်းမည် |
| </button> |
| </div> |
|
|
| |
| <div class="modal-overlay" id="receiptModal"> |
| <div class="receipt-modal"> |
| <div class="receipt-header"> |
| <h2>✅ ငွေရှင်းပြီးပါပြီ</h2> |
| <p id="receiptDate"></p> |
| </div> |
| <div class="receipt-body" id="receiptBody"></div> |
| <div class="receipt-actions"> |
| <button class="btn-print" onclick="printReceipt()">🖨️ Print</button> |
| <button class="btn-new" onclick="newSale()">🔄 အသစ်ဆက်မည်</button> |
| </div> |
| </div> |
| </div> |
|
|
| <div id="printArea" style="display:none"></div> |
|
|
| <script> |
| let cart = []; |
| let scanner = null; |
| let scannerActive = false; |
| let lastSaleData = null; |
| let scanCooldown = false; |
| |
| |
| document.addEventListener('DOMContentLoaded', async () => { |
| const r = await fetch('/api/me'); |
| if (!r.ok) { window.location.href = '/login'; return; } |
| const d = await r.json(); |
| document.getElementById('cashierName').textContent = d.username; |
| }); |
| |
| |
| function toggleScanner() { |
| if (!scannerActive) startScanner(); else stopScanner(); |
| } |
| |
| function startScanner() { |
| const wrap = document.getElementById('scannerWrap'); |
| wrap.classList.add('visible'); |
| document.getElementById('scanBtn').classList.add('active'); |
| document.getElementById('scanText').textContent = 'Scanner ပိတ်မည်'; |
| document.getElementById('scanIcon').textContent = '⏹️'; |
| scannerActive = true; |
| |
| scanner = new Html5Qrcode("reader"); |
| scanner.start( |
| { facingMode: "environment" }, |
| { fps: 10, qrbox: { width: 250, height: 100 } }, |
| (code) => { |
| if (scanCooldown) return; |
| scanCooldown = true; |
| lookupBarcode(code); |
| setTimeout(() => { scanCooldown = false; }, 2000); |
| }, |
| () => {} |
| ).catch(err => { |
| showToast(null, 'ကင်မရာ ဖွင့်၍မရပါ: ' + err); |
| }); |
| } |
| |
| function stopScanner() { |
| if (scanner) { |
| scanner.stop().catch(() => {}); |
| scanner = null; |
| } |
| scannerActive = false; |
| document.getElementById('scannerWrap').classList.remove('visible'); |
| document.getElementById('scanBtn').classList.remove('active'); |
| document.getElementById('scanText').textContent = 'ကင်မရာဖြင့် Scan လုပ်မည်'; |
| document.getElementById('scanIcon').textContent = '📷'; |
| } |
| |
| function manualSearch() { |
| const v = document.getElementById('barcodeInput').value.trim(); |
| if (!v) return; |
| lookupBarcode(v); |
| document.getElementById('barcodeInput').value = ''; |
| } |
| |
| |
| async function lookupBarcode(barcode) { |
| |
| if (navigator.vibrate) navigator.vibrate(50); |
| |
| try { |
| const r = await fetch(`/api/products/scan/${encodeURIComponent(barcode)}`); |
| if (r.ok) { |
| const product = await r.json(); |
| showToast(product, null); |
| addToCart(product); |
| } else { |
| showToast(null, `"${barcode}" — ကုန်ပစ္စည်း မတွေ့ပါ။ Admin မှ ထည့်ပါ။`); |
| } |
| } catch(e) { |
| showToast(null, 'ချိတ်ဆက်မှု ပြဿနာ'); |
| } |
| } |
| |
| function showToast(product, err) { |
| const t = document.getElementById('productToast'); |
| const icon = document.getElementById('toastIcon'); |
| const name = document.getElementById('toastName'); |
| const price = document.getElementById('toastPrice'); |
| const errEl = document.getElementById('toastErr'); |
| |
| t.className = 'product-toast'; |
| if (product) { |
| icon.textContent = '✅'; |
| name.textContent = product.name; |
| price.textContent = `${product.price.toLocaleString()} ကျပ်`; |
| errEl.textContent = ''; |
| t.classList.add('show'); |
| } else { |
| icon.textContent = '❌'; |
| name.textContent = 'မတွေ့ပါ'; |
| price.textContent = ''; |
| errEl.textContent = err; |
| t.classList.add('show', 'error'); |
| } |
| |
| clearTimeout(t._timer); |
| t._timer = setTimeout(() => t.classList.remove('show', 'error'), 3000); |
| } |
| |
| |
| function addToCart(product) { |
| const existing = cart.find(i => i.product_id === product.id); |
| if (existing) { |
| existing.quantity++; |
| } else { |
| cart.push({ |
| product_id: product.id, |
| name: product.name, |
| price: product.price, |
| quantity: 1 |
| }); |
| } |
| renderCart(); |
| } |
| |
| function changeQty(idx, delta) { |
| cart[idx].quantity += delta; |
| if (cart[idx].quantity <= 0) cart.splice(idx, 1); |
| renderCart(); |
| } |
| |
| function removeItem(idx) { |
| cart.splice(idx, 1); |
| renderCart(); |
| } |
| |
| function clearCart() { |
| if (cart.length === 0) return; |
| if (confirm('Cart ကို အားလုံးဖျက်မည်လား?')) { |
| cart = []; |
| renderCart(); |
| } |
| } |
| |
| function renderCart() { |
| const el = document.getElementById('cartItems'); |
| const empty = document.getElementById('cartEmpty'); |
| const badge = document.getElementById('cartBadge'); |
| const total = cart.reduce((s, i) => s + i.price * i.quantity, 0); |
| const count = cart.reduce((s, i) => s + i.quantity, 0); |
| |
| badge.textContent = count; |
| document.getElementById('totalDisplay').textContent = total.toLocaleString() + ' ကျပ်'; |
| document.getElementById('checkoutBtn').disabled = cart.length === 0; |
| calcChange(); |
| |
| if (cart.length === 0) { |
| empty.style.display = 'block'; |
| el.innerHTML = ''; |
| return; |
| } |
| empty.style.display = 'none'; |
| |
| el.innerHTML = cart.map((item, i) => ` |
| <div class="cart-item"> |
| <div class="item-info"> |
| <div class="item-name">${item.name}</div> |
| <div class="item-price">${item.price.toLocaleString()} × ${item.quantity}</div> |
| </div> |
| <div class="qty-ctrl"> |
| <button class="qty-btn minus" onclick="changeQty(${i}, -1)">−</button> |
| <span class="qty-num">${item.quantity}</span> |
| <button class="qty-btn plus" onclick="changeQty(${i}, 1)">+</button> |
| </div> |
| <div class="item-total">${(item.price * item.quantity).toLocaleString()}</div> |
| <button class="btn-del" onclick="removeItem(${i})">🗑️</button> |
| </div> |
| `).join(''); |
| } |
| |
| function calcChange() { |
| const total = cart.reduce((s, i) => s + i.price * i.quantity, 0); |
| const cash = parseFloat(document.getElementById('cashInput').value) || 0; |
| const change = cash - total; |
| const row = document.getElementById('changeRow'); |
| if (cash > 0 && cash >= total) { |
| document.getElementById('changeDisplay').textContent = change.toLocaleString() + ' ကျပ်'; |
| row.classList.add('show'); |
| } else { |
| row.classList.remove('show'); |
| } |
| } |
| |
| |
| async function checkout() { |
| if (cart.length === 0) return; |
| const total = cart.reduce((s, i) => s + i.price * i.quantity, 0); |
| const cash = parseFloat(document.getElementById('cashInput').value) || total; |
| |
| if (cash < total) { |
| alert('ငွေပေးသည် စုစုပေါင်းထက် နည်းနေသည်'); |
| return; |
| } |
| |
| document.getElementById('checkoutBtn').disabled = true; |
| document.getElementById('checkoutBtn').textContent = '⏳ လုပ်ဆောင်နေသည်...'; |
| |
| try { |
| const r = await fetch('/api/sales', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ items: cart, cash_received: cash }) |
| }); |
| const data = await r.json(); |
| if (r.ok) { |
| lastSaleData = data; |
| showReceipt(data); |
| } else { |
| alert('Error: ' + data.error); |
| } |
| } catch(e) { |
| alert('ချိတ်ဆက်မှု ပြဿနာ'); |
| } |
| |
| document.getElementById('checkoutBtn').disabled = false; |
| document.getElementById('checkoutBtn').textContent = '💰 ငွေရှင်းမည်'; |
| } |
| |
| |
| function showReceipt(data) { |
| stopScanner(); |
| document.getElementById('receiptDate').textContent = data.created_at; |
| |
| const rows = data.items.map(i => |
| `<div class="r-row"><span>${i.name} ×${i.quantity}</span><span>${(i.price_at_time * i.quantity).toLocaleString()} ကျပ်</span></div>` |
| ).join(''); |
| |
| document.getElementById('receiptBody').innerHTML = ` |
| ${rows} |
| <div class="r-row r-total"><span>စုစုပေါင်း</span><span>${data.total.toLocaleString()} ကျပ်</span></div> |
| <div class="r-row"><span>ငွေပေး</span><span>${data.cash.toLocaleString()} ကျပ်</span></div> |
| <div class="r-row" style="color:var(--green)"><span>အကြွေ</span><span>${data.change.toLocaleString()} ကျပ်</span></div> |
| <div class="r-row" style="color:var(--muted);font-size:11px;margin-top:8px"><span>Sale #${data.sale_id}</span><span>${data.created_at}</span></div> |
| `; |
| |
| document.getElementById('receiptModal').classList.add('show'); |
| } |
| |
| function printReceipt() { |
| if (!lastSaleData) return; |
| const d = lastSaleData; |
| const lines = d.items.map(i => |
| `${i.name.substring(0,18).padEnd(18)} x${i.quantity}\n${''.padEnd(18)} ${(i.price_at_time*i.quantity).toLocaleString().padStart(8)} ကျပ်` |
| ).join('\n'); |
| |
| document.getElementById('printArea').innerHTML = ` |
| <div style="text-align:center;font-family:monospace;font-size:12px;padding:10px"> |
| <h2 style="font-size:16px">ကုန်စုံဆိုင် POS</h2> |
| <p>${d.created_at}</p> |
| <p>Sale #${d.sale_id}</p> |
| <hr> |
| <pre style="text-align:left">${lines}</pre> |
| <hr> |
| <p><b>စုစုပေါင်း: ${d.total.toLocaleString()} ကျပ်</b></p> |
| <p>ငွေပေး: ${d.cash.toLocaleString()} ကျပ်</p> |
| <p>အကြွေ: ${d.change.toLocaleString()} ကျပ်</p> |
| <hr> |
| <p>ကျေးဇူးတင်ပါသည်</p> |
| </div> |
| `; |
| window.print(); |
| } |
| |
| function newSale() { |
| cart = []; |
| lastSaleData = null; |
| document.getElementById('cashInput').value = ''; |
| document.getElementById('changeRow').classList.remove('show'); |
| document.getElementById('receiptModal').classList.remove('show'); |
| renderCart(); |
| } |
| |
| function logout() { |
| stopScanner(); |
| fetch('/api/logout', { method: 'POST' }) |
| .then(() => window.location.href = '/login'); |
| } |
| </script> |
| </body> |
| </html> |
|
|