diff --git a/backend/templates/admin_pos_live.html b/backend/templates/admin_pos_live.html index dd9b2e2..ddc27db 100644 --- a/backend/templates/admin_pos_live.html +++ b/backend/templates/admin_pos_live.html @@ -125,6 +125,16 @@ color: #94a3b8; cursor: not-allowed; } + .btn-kiosk { + background: linear-gradient(135deg, #6366f1, #4f46e5); + color: #fff; + } + .btn-kiosk:hover { background: linear-gradient(135deg, #4f46e5, #4338ca); } + .btn-kiosk:disabled { + background: #e2e8f0; + color: #94a3b8; + cursor: not-allowed; + } .auto-refresh { display: flex; @@ -598,7 +608,8 @@
- + +
- -
-
-
- 🏷️ QR 라벨 λ°œν–‰ - -
-
- -
-
-
-
@@ -838,15 +836,15 @@ }); // ═══════════════════════════════════════════════════════════════ - // QR λ°œν–‰ κΈ°λŠ₯ + // ν‚€μ˜€μŠ€ν¬ & 라벨 좜λ ₯ κΈ°λŠ₯ // ═══════════════════════════════════════════════════════════════ let selectedSale = null; - let selectedPrinter = 'brother'; - let qrGenerated = false; + let selectedIdx = -1; - // ν…Œμ΄λΈ” ν–‰ 선택 μ‹œ QR λ²„νŠΌ ν™œμ„±ν™” + // ν…Œμ΄λΈ” ν–‰ 선택 μ‹œ λ²„νŠΌ ν™œμ„±ν™” function showDetail(orderNo, idx) { + selectedIdx = idx; // 선택 ν‘œμ‹œ document.querySelectorAll('#salesTable tr').forEach(tr => tr.classList.remove('selected')); document.querySelector(`#salesTable tr[data-idx="${idx}"]`)?.classList.add('selected'); @@ -854,15 +852,13 @@ const sale = salesData[idx]; selectedSale = sale; - // QR λ²„νŠΌ ν™œμ„±ν™” (QR λ―Έλ°œν–‰ 건만) + // λ²„νŠΌ ν™œμ„±ν™” + document.getElementById('kioskBtn').disabled = false; + document.getElementById('qrBtn').disabled = false; + + // QR λ°œν–‰ 여뢀에 따라 λ²„νŠΌ ν…μŠ€νŠΈ λ³€κ²½ const qrBtn = document.getElementById('qrBtn'); - if (!sale.qr_issued) { - qrBtn.disabled = false; - qrBtn.textContent = '🏷️ QR λ°œν–‰'; - } else { - qrBtn.disabled = false; - qrBtn.textContent = 'πŸ”„ 재좜λ ₯'; - } + qrBtn.textContent = sale.qr_issued ? 'πŸ”„ 재좜λ ₯' : '🏷️ 라벨좜λ ₯'; // νŒ¨λ„ μ—΄κΈ° document.getElementById('overlay').classList.add('visible'); @@ -928,166 +924,174 @@ } } - function openQrModal() { + // ═══════════════════════════════════════════════════════════════ + // ν‚€μ˜€μŠ€ν¬ 전솑 + // ═══════════════════════════════════════════════════════════════ + async function triggerKiosk() { if (!selectedSale) { alert('λ¨Όμ € 판맀 건을 μ„ νƒν•΄μ£Όμ„Έμš”.'); return; } - qrGenerated = false; - const sale = selectedSale; - const isReprint = sale.qr_issued; - - document.getElementById('qrModalBody').innerHTML = ` -
-
-
거래번호
-
${sale.order_no}
-
-
-
μ‹œκ°„
-
${sale.time}
-
-
-
νŒλ§€κΈˆμ•‘
-
β‚©${Math.floor(sale.amount).toLocaleString()}
-
-
-
μ˜ˆμƒ 적립
-
${Math.floor(sale.amount * 0.03).toLocaleString()}P
-
-
- -
- ${isReprint - ? '
이미 λ°œν–‰λœ QRμž…λ‹ˆλ‹€.
재좜λ ₯ν•˜λ €λ©΄ ν”„λ¦°ν„°λ₯Ό 선택 ν›„ "좜λ ₯" λ²„νŠΌμ„ λˆ„λ₯΄μ„Έμš”.
' - : '
QR 생성 λ²„νŠΌμ„ λˆŒλŸ¬μ£Όμ„Έμš”
' - } -
- -
-
-
🏷️
-
Brother QL
-
-
-
🧾
-
POS 영수증
-
-
- -
- ${isReprint - ? '' - : '' - } -
- `; - - document.getElementById('qrModal').classList.add('visible'); - } - - function closeQrModal() { - document.getElementById('qrModal').classList.remove('visible'); - } - - function selectPrinter(type) { - selectedPrinter = type; - document.querySelectorAll('.printer-option').forEach(el => el.classList.remove('selected')); - document.querySelector(`.printer-option:nth-child(${type === 'brother' ? 1 : 2})`).classList.add('selected'); - } - - async function generateQr() { - if (!selectedSale) return; - - const container = document.getElementById('qrPreviewContainer'); - container.innerHTML = '
QR 생성 쀑...
'; + const btn = document.getElementById('kioskBtn'); + const originalText = btn.textContent; + btn.disabled = true; + btn.textContent = '전솑 쀑...'; try { - const res = await fetch('/api/admin/qr/generate', { + const res = await fetch('/api/kiosk/trigger', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - order_no: selectedSale.order_no, - amount: selectedSale.amount, - preview: true + transaction_id: selectedSale.order_no, + amount: selectedSale.amount }) }); const data = await res.json(); if (data.success) { - qrGenerated = true; - container.innerHTML = ` - QR Label -
- βœ“ ${data.claimable_points}P 적립 κ°€λŠ₯ -
- `; + btn.textContent = `βœ“ ${data.points}P`; + btn.style.background = '#10b981'; - // 좜λ ₯ λ²„νŠΌ ν™œμ„±ν™” - const printBtn = document.getElementById('printBtn'); - if (printBtn) printBtn.disabled = false; + // ν† μŠ€νŠΈ λ©”μ‹œμ§€ + showToast(`ν‚€μ˜€μŠ€ν¬ 전솑 μ™„λ£Œ! (${data.points}P)`, 'success'); - // ν…Œμ΄λΈ” κ°±μ‹  - loadSales(); + // ν…Œμ΄λΈ” μƒˆλ‘œκ³ μΉ¨ + setTimeout(() => { + loadSales(); + btn.textContent = originalText; + btn.style.background = ''; + btn.disabled = false; + }, 2000); } else { - container.innerHTML = `
❌ ${data.error}
`; + showToast(data.message || '전솑 μ‹€νŒ¨', 'error'); + btn.textContent = originalText; + btn.disabled = false; } } catch (err) { - container.innerHTML = `
였λ₯˜: ${err.message}
`; + showToast(`였λ₯˜: ${err.message}`, 'error'); + btn.textContent = originalText; + btn.disabled = false; } } - async function printQrLabel() { - if (!selectedSale) return; + // ═══════════════════════════════════════════════════════════════ + // 라벨 좜λ ₯ (Brother QL-810W) + // ═══════════════════════════════════════════════════════════════ + async function triggerQrPrint() { + if (!selectedSale) { + alert('λ¨Όμ € 판맀 건을 μ„ νƒν•΄μ£Όμ„Έμš”.'); + return; + } - const container = document.getElementById('qrPreviewContainer'); - const originalContent = container.innerHTML; - container.innerHTML = '
ν”„λ¦°ν„°λ‘œ 전솑 쀑...
'; + const btn = document.getElementById('qrBtn'); + const originalText = btn.textContent; + btn.disabled = true; + btn.textContent = '좜λ ₯ 쀑...'; try { + // 1. QR λ―Έλ°œν–‰μ΄λ©΄ λ¨Όμ € 생성 + if (!selectedSale.qr_issued) { + const genRes = await fetch('/api/admin/qr/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + order_no: selectedSale.order_no, + amount: selectedSale.amount, + preview: false + }) + }); + const genData = await genRes.json(); + if (!genData.success) { + throw new Error(genData.error); + } + } + + // 2. ν”„λ¦°ν„° 좜λ ₯ const res = await fetch('/api/admin/qr/print', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ order_no: selectedSale.order_no, - printer: selectedPrinter + printer: 'brother' }) }); const data = await res.json(); if (data.success) { - container.innerHTML = ` - ${originalContent.includes('qr-preview-img') ? originalContent : ''} -
- βœ“ ${data.message} -
- `; + btn.textContent = 'βœ“ 좜λ ₯μ™„λ£Œ'; + btn.style.background = '#10b981'; + showToast(data.message, 'success'); - // μž μ‹œ ν›„ λͺ¨λ‹¬ λ‹«κΈ° setTimeout(() => { - closeQrModal(); loadSales(); + btn.textContent = originalText; + btn.style.background = ''; + btn.disabled = false; }, 2000); } else { - container.innerHTML = ` - ${originalContent} -
- ❌ ${data.error} -
- `; + showToast(data.error || '좜λ ₯ μ‹€νŒ¨', 'error'); + btn.textContent = originalText; + btn.disabled = false; } } catch (err) { - container.innerHTML = ` - ${originalContent} -
- 였λ₯˜: ${err.message} -
- `; + showToast(`였λ₯˜: ${err.message}`, 'error'); + btn.textContent = originalText; + btn.disabled = false; } } + + // ═══════════════════════════════════════════════════════════════ + // ν† μŠ€νŠΈ λ©”μ‹œμ§€ + // ═══════════════════════════════════════════════════════════════ + function showToast(message, type = 'info') { + // κΈ°μ‘΄ ν† μŠ€νŠΈ 제거 + const existing = document.querySelector('.toast'); + if (existing) existing.remove(); + + const toast = document.createElement('div'); + toast.className = 'toast'; + toast.style.cssText = ` + position: fixed; + bottom: 24px; + left: 50%; + transform: translateX(-50%); + padding: 14px 28px; + border-radius: 12px; + font-weight: 600; + font-size: 15px; + z-index: 9999; + animation: toastIn 0.3s ease; + ${type === 'success' + ? 'background: #10b981; color: white;' + : type === 'error' + ? 'background: #ef4444; color: white;' + : 'background: #1e293b; color: white;'} + `; + toast.textContent = message; + document.body.appendChild(toast); + + setTimeout(() => { + toast.style.animation = 'toastOut 0.3s ease'; + setTimeout(() => toast.remove(), 300); + }, 3000); + } + + // ν† μŠ€νŠΈ μ• λ‹ˆλ©”μ΄μ…˜ μΆ”κ°€ + const toastStyle = document.createElement('style'); + toastStyle.textContent = ` + @keyframes toastIn { + from { opacity: 0; transform: translateX(-50%) translateY(20px); } + to { opacity: 1; transform: translateX(-50%) translateY(0); } + } + @keyframes toastOut { + from { opacity: 1; transform: translateX(-50%) translateY(0); } + to { opacity: 0; transform: translateX(-50%) translateY(20px); } + } + `; + document.head.appendChild(toastStyle);