diff --git a/backend/templates/admin_pos_live.html b/backend/templates/admin_pos_live.html
index ddc27db..08df6aa 100644
--- a/backend/templates/admin_pos_live.html
+++ b/backend/templates/admin_pos_live.html
@@ -236,6 +236,18 @@
tr.selected {
background: #ede9fe;
}
+ .row-checkbox {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ accent-color: #8b5cf6;
+ }
+ #selectAll {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ accent-color: #8b5cf6;
+ }
.order-no {
font-family: 'JetBrains Mono', monospace;
@@ -648,6 +660,7 @@
+ |
시간 |
금액 |
고객 |
@@ -759,15 +772,19 @@
}
function renderTable(data) {
+ // 전체 선택 체크박스 초기화
+ document.getElementById('selectAll').checked = false;
+
if (data.sales.length === 0) {
document.getElementById('salesTable').innerHTML = `
-
|
+ |
|
|
`;
+ updateSelectedCount();
return;
}
@@ -787,19 +804,23 @@
}
return `
-
- | ${sale.time} |
- ₩${Math.floor(sale.amount).toLocaleString()} |
- ${sale.customer} |
- ${payBadge} |
- ${sale.item_count} |
- ${qrIcon} |
- ${claimedHtml} |
+
+ |
+
+ |
+ ${sale.time} |
+ ₩${Math.floor(sale.amount).toLocaleString()} |
+ ${sale.customer} |
+ ${payBadge} |
+ ${sale.item_count} |
+ ${qrIcon} |
+ ${claimedHtml} |
`;
}).join('');
document.getElementById('salesTable').innerHTML = rows;
+ updateSelectedCount();
}
function getPayBadge(method) {
@@ -836,29 +857,65 @@
});
// ═══════════════════════════════════════════════════════════════
- // 키오스크 & 라벨 출력 기능
+ // 체크박스 선택 기능
// ═══════════════════════════════════════════════════════════════
- let selectedSale = null;
- let selectedIdx = -1;
+ let selectedSales = []; // 선택된 판매 건들
- // 테이블 행 선택 시 버튼 활성화
+ function toggleSelectAll() {
+ const selectAll = document.getElementById('selectAll').checked;
+ document.querySelectorAll('.row-checkbox').forEach(cb => {
+ cb.checked = selectAll;
+ });
+ updateSelectedCount();
+ }
+
+ function onRowSelect(idx) {
+ updateSelectedCount();
+ }
+
+ function getSelectedSales() {
+ const selected = [];
+ document.querySelectorAll('.row-checkbox:checked').forEach(cb => {
+ const idx = parseInt(cb.dataset.idx);
+ if (salesData[idx]) {
+ selected.push(salesData[idx]);
+ }
+ });
+ return selected;
+ }
+
+ function updateSelectedCount() {
+ selectedSales = getSelectedSales();
+ const count = selectedSales.length;
+
+ const kioskBtn = document.getElementById('kioskBtn');
+ const qrBtn = document.getElementById('qrBtn');
+
+ if (count > 0) {
+ kioskBtn.disabled = false;
+ qrBtn.disabled = false;
+ kioskBtn.textContent = `📺 키오스크 (${count}건)`;
+ qrBtn.textContent = `🏷️ 라벨출력 (${count}건)`;
+ } else {
+ kioskBtn.disabled = true;
+ qrBtn.disabled = true;
+ kioskBtn.textContent = '📺 키오스크';
+ qrBtn.textContent = '🏷️ 라벨출력';
+ }
+ }
+
+ // ═══════════════════════════════════════════════════════════════
+ // 상세 패널 (별도 기능)
+ // ═══════════════════════════════════════════════════════════════
+
+ // 행 클릭 시 상세 패널 표시 (체크박스와 별개)
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');
const sale = salesData[idx];
- selectedSale = sale;
-
- // 버튼 활성화
- document.getElementById('kioskBtn').disabled = false;
- document.getElementById('qrBtn').disabled = false;
-
- // QR 발행 여부에 따라 버튼 텍스트 변경
- const qrBtn = document.getElementById('qrBtn');
- qrBtn.textContent = sale.qr_issued ? '🔄 재출력' : '🏷️ 라벨출력';
// 패널 열기
document.getElementById('overlay').classList.add('visible');
@@ -925,14 +982,20 @@
}
// ═══════════════════════════════════════════════════════════════
- // 키오스크 전송
+ // 키오스크 전송 (선택된 건 중 첫 번째만 - 키오스크는 1건씩)
// ═══════════════════════════════════════════════════════════════
async function triggerKiosk() {
- if (!selectedSale) {
- alert('먼저 판매 건을 선택해주세요.');
+ if (selectedSales.length === 0) {
+ showToast('먼저 판매 건을 선택해주세요.', 'error');
return;
}
+ // 키오스크는 1건만 전송 (여러 건 선택 시 첫 번째)
+ const sale = selectedSales[0];
+ if (selectedSales.length > 1) {
+ showToast('키오스크는 1건씩 전송됩니다. 첫 번째 건을 전송합니다.', 'info');
+ }
+
const btn = document.getElementById('kioskBtn');
const originalText = btn.textContent;
btn.disabled = true;
@@ -943,8 +1006,8 @@
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
- transaction_id: selectedSale.order_no,
- amount: selectedSale.amount
+ transaction_id: sale.order_no,
+ amount: sale.amount
})
});
@@ -953,17 +1016,11 @@
if (data.success) {
btn.textContent = `✓ ${data.points}P`;
btn.style.background = '#10b981';
-
- // 토스트 메시지
showToast(`키오스크 전송 완료! (${data.points}P)`, 'success');
- // 테이블 새로고침
setTimeout(() => {
loadSales();
- btn.textContent = originalText;
- btn.style.background = '';
- btn.disabled = false;
- }, 2000);
+ }, 1500);
} else {
showToast(data.message || '전송 실패', 'error');
btn.textContent = originalText;
@@ -977,70 +1034,84 @@
}
// ═══════════════════════════════════════════════════════════════
- // 라벨 출력 (Brother QL-810W)
+ // 라벨 출력 (Brother QL-810W) - 여러 건 순차 출력
// ═══════════════════════════════════════════════════════════════
async function triggerQrPrint() {
- if (!selectedSale) {
- alert('먼저 판매 건을 선택해주세요.');
+ if (selectedSales.length === 0) {
+ showToast('먼저 판매 건을 선택해주세요.', 'error');
return;
}
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', {
+ let successCount = 0;
+ let errorCount = 0;
+
+ for (let i = 0; i < selectedSales.length; i++) {
+ const sale = selectedSales[i];
+ btn.textContent = `출력 중... (${i + 1}/${selectedSales.length})`;
+
+ try {
+ // 1. QR 미발행이면 먼저 생성
+ if (!sale.qr_issued) {
+ const genRes = await fetch('/api/admin/qr/generate', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ order_no: sale.order_no,
+ amount: sale.amount,
+ preview: false
+ })
+ });
+ const genData = await genRes.json();
+ if (!genData.success) {
+ errorCount++;
+ continue;
+ }
+ }
+
+ // 2. 프린터 출력
+ const res = await fetch('/api/admin/qr/print', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
- order_no: selectedSale.order_no,
- amount: selectedSale.amount,
- preview: false
+ order_no: sale.order_no,
+ printer: 'brother'
})
});
- 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: 'brother'
- })
- });
-
- const data = await res.json();
-
- if (data.success) {
- btn.textContent = '✓ 출력완료';
- btn.style.background = '#10b981';
- showToast(data.message, 'success');
- setTimeout(() => {
- loadSales();
- btn.textContent = originalText;
- btn.style.background = '';
- btn.disabled = false;
- }, 2000);
- } else {
- showToast(data.error || '출력 실패', 'error');
- btn.textContent = originalText;
- btn.disabled = false;
+ const data = await res.json();
+ if (data.success) {
+ successCount++;
+ } else {
+ errorCount++;
+ }
+
+ // 프린터 간격 (연속 출력 시 0.5초 대기)
+ if (i < selectedSales.length - 1) {
+ await new Promise(r => setTimeout(r, 500));
+ }
+
+ } catch (err) {
+ errorCount++;
}
- } catch (err) {
- showToast(`오류: ${err.message}`, 'error');
- btn.textContent = originalText;
- btn.disabled = false;
}
+
+ // 결과 표시
+ if (successCount > 0) {
+ btn.textContent = `✓ ${successCount}건 완료`;
+ btn.style.background = '#10b981';
+ showToast(`라벨 출력 완료! (${successCount}건)`, 'success');
+ }
+ if (errorCount > 0) {
+ showToast(`${errorCount}건 실패`, 'error');
+ }
+
+ setTimeout(() => {
+ loadSales();
+ }, 1500);
}
// ═══════════════════════════════════════════════════════════════