feat: 상세 패널에 키오스크/라벨출력 액션 버튼 추가
- 상세 패널 상단에 2열 액션 버튼 배치 - 📺 키오스크: 해당 건 즉시 전송 - 🏷️ 라벨출력: QR 생성 + Brother QL 출력 - 버튼에 예상 적립 포인트 표시 - 호버 효과 + 로딩 상태 표시 - QR 발행 여부, 적립 완료 정보 표시
This commit is contained in:
parent
f1e609ba9f
commit
695c1f707f
@ -582,6 +582,61 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── 상세 패널 액션 버튼 ── */
|
||||
.detail-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.detail-action-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 12px;
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.detail-action-btn.kiosk {
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
color: white;
|
||||
}
|
||||
.detail-action-btn.kiosk:hover {
|
||||
background: linear-gradient(135deg, #4f46e5, #4338ca);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
.detail-action-btn.qr {
|
||||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||
color: white;
|
||||
}
|
||||
.detail-action-btn.qr:hover {
|
||||
background: linear-gradient(135deg, #d97706, #b45309);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(245, 158, 11, 0.4);
|
||||
}
|
||||
.detail-action-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.action-icon {
|
||||
font-size: 28px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.action-text {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.action-sub {
|
||||
font-size: 12px;
|
||||
opacity: 0.85;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ── 반응형 ── */
|
||||
@media (max-width: 768px) {
|
||||
.control-section {
|
||||
@ -921,8 +976,32 @@
|
||||
document.getElementById('overlay').classList.add('visible');
|
||||
document.getElementById('detailPanel').classList.add('open');
|
||||
|
||||
const expectedPoints = Math.floor(sale.amount * 0.03);
|
||||
const qrStatus = sale.qr_issued
|
||||
? `<span style="color:#10b981">✓ QR 발행됨</span>`
|
||||
: `<span style="color:#94a3b8">미발행</span>`;
|
||||
const claimedInfo = sale.claimed_name
|
||||
? `<div style="margin-top:8px; padding:10px; background:#dcfce7; border-radius:8px; font-size:13px;">
|
||||
✓ <strong>${sale.claimed_name}</strong>님 적립 완료
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
// 기본 정보 표시
|
||||
document.getElementById('detailContent').innerHTML = `
|
||||
<!-- 액션 버튼 -->
|
||||
<div class="detail-actions">
|
||||
<button class="detail-action-btn kiosk" onclick="triggerKioskSingle('${orderNo}', ${sale.amount})">
|
||||
<span class="action-icon">📺</span>
|
||||
<span class="action-text">키오스크</span>
|
||||
<span class="action-sub">${expectedPoints}P</span>
|
||||
</button>
|
||||
<button class="detail-action-btn qr" onclick="triggerQrPrintSingle('${orderNo}', ${sale.amount}, ${sale.qr_issued})">
|
||||
<span class="action-icon">🏷️</span>
|
||||
<span class="action-text">${sale.qr_issued ? '재출력' : '라벨출력'}</span>
|
||||
<span class="action-sub">${expectedPoints}P</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="detail-info">
|
||||
<div class="detail-info-item">
|
||||
<div class="detail-info-label">거래번호</div>
|
||||
@ -942,13 +1021,14 @@
|
||||
</div>
|
||||
<div class="detail-info-item">
|
||||
<div class="detail-info-label">판매금액</div>
|
||||
<div class="detail-info-value">₩${Math.floor(sale.amount).toLocaleString()}</div>
|
||||
<div class="detail-info-value" style="font-weight:700; color:#8b5cf6">₩${Math.floor(sale.amount).toLocaleString()}</div>
|
||||
</div>
|
||||
<div class="detail-info-item">
|
||||
<div class="detail-info-label">할인</div>
|
||||
<div class="detail-info-value">₩${Math.floor(sale.discount).toLocaleString()}</div>
|
||||
<div class="detail-info-label">QR 상태</div>
|
||||
<div class="detail-info-value">${qrStatus}</div>
|
||||
</div>
|
||||
</div>
|
||||
${claimedInfo}
|
||||
<div class="detail-items-title">📦 품목 목록</div>
|
||||
<div id="itemsList">
|
||||
<div class="loading"><div class="spinner"></div>품목 로딩 중...</div>
|
||||
@ -981,6 +1061,81 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 상세 패널에서 단일 건 처리
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
async function triggerKioskSingle(orderNo, amount) {
|
||||
const btn = event.target.closest('.detail-action-btn');
|
||||
const originalHtml = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="action-icon">⏳</span><span class="action-text">전송 중...</span>';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/kiosk/trigger', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ transaction_id: orderNo, amount: amount })
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
btn.innerHTML = `<span class="action-icon">✅</span><span class="action-text">전송 완료!</span><span class="action-sub">${data.points}P</span>`;
|
||||
showToast(`키오스크 전송 완료! (${data.points}P)`, 'success');
|
||||
setTimeout(() => loadSales(), 1500);
|
||||
} else {
|
||||
btn.innerHTML = originalHtml;
|
||||
btn.disabled = false;
|
||||
showToast(data.message || '전송 실패', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
btn.innerHTML = originalHtml;
|
||||
btn.disabled = false;
|
||||
showToast(`오류: ${err.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function triggerQrPrintSingle(orderNo, amount, isReprint) {
|
||||
const btn = event.target.closest('.detail-action-btn');
|
||||
const originalHtml = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="action-icon">⏳</span><span class="action-text">출력 중...</span>';
|
||||
|
||||
try {
|
||||
// QR 미발행이면 먼저 생성
|
||||
if (!isReprint) {
|
||||
const genRes = await fetch('/api/admin/qr/generate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ order_no: orderNo, amount: amount, preview: false })
|
||||
});
|
||||
const genData = await genRes.json();
|
||||
if (!genData.success) throw new Error(genData.error);
|
||||
}
|
||||
|
||||
// 프린터 출력
|
||||
const res = await fetch('/api/admin/qr/print', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ order_no: orderNo, printer: 'brother' })
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
btn.innerHTML = '<span class="action-icon">✅</span><span class="action-text">출력 완료!</span>';
|
||||
showToast(data.message, 'success');
|
||||
setTimeout(() => loadSales(), 1500);
|
||||
} else {
|
||||
btn.innerHTML = originalHtml;
|
||||
btn.disabled = false;
|
||||
showToast(data.error || '출력 실패', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
btn.innerHTML = originalHtml;
|
||||
btn.disabled = false;
|
||||
showToast(`오류: ${err.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 키오스크 전송 (선택된 건 중 첫 번째만 - 키오스크는 1건씩)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
Loading…
Reference in New Issue
Block a user