feat(반품관리): 약품 더블클릭 시 입고이력 모달 추가

- admin_return_management.html 업데이트:
  - 입고이력 모달 스타일/HTML 추가 (다크테마 적용)
  - tr ondblclick → openPurchaseModal()
  - 도매상 전화번호 클릭 시 복사 기능
  - 테이블 위에 더블클릭 힌트 추가
  - 상태변경 버튼에 event.stopPropagation() 추가
This commit is contained in:
thug0bin 2026-03-08 10:35:48 +09:00
parent 91f8dea5b4
commit c71c9ad678

View File

@ -544,6 +544,138 @@
.filter-group select, .filter-group input { width: 100%; }
.urgency-tabs { flex-wrap: wrap; }
}
/* ══════════════════ 입고이력 모달 ══════════════════ */
.purchase-modal {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.8);
display: none;
align-items: center;
justify-content: center;
z-index: 1100;
backdrop-filter: blur(4px);
}
.purchase-modal.open { display: flex; }
.purchase-modal-content {
background: var(--bg-card);
border-radius: 20px;
width: 95%;
max-width: 650px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
border: 1px solid var(--border);
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}
.purchase-modal-header {
padding: 20px 24px;
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
color: #fff;
}
.purchase-modal-header h3 {
margin: 0 0 6px 0;
font-size: 18px;
display: flex;
align-items: center;
gap: 8px;
}
.purchase-modal-header .drug-name {
font-size: 14px;
opacity: 0.9;
}
.purchase-modal-body {
padding: 16px 24px 24px;
overflow-y: auto;
flex: 1;
}
.purchase-history-table {
width: 100%;
border-collapse: collapse;
}
.purchase-history-table th {
background: var(--bg-secondary);
padding: 12px 10px;
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
text-align: left;
border-bottom: 2px solid var(--border);
position: sticky;
top: 0;
}
.purchase-history-table td {
padding: 14px 10px;
font-size: 13px;
border-bottom: 1px solid var(--border);
color: var(--text-primary);
}
.purchase-history-table tr:hover td {
background: var(--bg-card-hover);
}
.supplier-name {
font-weight: 600;
color: var(--accent-cyan);
}
.supplier-tel {
font-size: 12px;
color: var(--accent-blue);
cursor: pointer;
}
.supplier-tel:hover {
text-decoration: underline;
}
.purchase-date {
color: var(--text-secondary);
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
}
.purchase-qty {
font-weight: 600;
color: var(--accent-emerald);
}
.purchase-price {
color: var(--accent-gold);
font-family: 'JetBrains Mono', monospace;
}
.purchase-empty {
text-align: center;
padding: 40px 20px;
color: var(--text-muted);
}
.purchase-empty .icon {
font-size: 40px;
margin-bottom: 12px;
}
.purchase-modal-footer {
padding: 16px 24px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
}
.purchase-modal-btn {
padding: 10px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
font-size: 14px;
background: var(--bg-secondary);
color: var(--text-secondary);
transition: all 0.15s;
}
.purchase-modal-btn:hover {
background: var(--bg-card-hover);
color: var(--text-primary);
}
/* 테이블 행 더블클릭 가능 표시 */
#dataTableBody tr {
cursor: pointer;
}
#dataTableBody tr:active {
background: var(--bg-card-hover) !important;
}
</style>
</head>
<body>
@ -673,6 +805,9 @@
</div>
<!-- 데이터 테이블 -->
<div style="margin-bottom: 8px; font-size: 12px; color: var(--text-muted);">
💡 행 더블클릭 → 입고이력 확인 (도매상 연락처)
</div>
<div class="table-wrap">
<table class="data-table">
<thead>
@ -742,6 +877,34 @@
<!-- 토스트 -->
<div class="toast" id="toast"></div>
<!-- 입고이력 모달 -->
<div class="purchase-modal" id="purchaseModal">
<div class="purchase-modal-content">
<div class="purchase-modal-header">
<h3>📦 입고 이력</h3>
<div class="drug-name" id="purchaseDrugName">-</div>
</div>
<div class="purchase-modal-body">
<table class="purchase-history-table">
<thead>
<tr>
<th>도매상</th>
<th>입고일</th>
<th>수량</th>
<th>단가</th>
</tr>
</thead>
<tbody id="purchaseHistoryBody">
<tr><td colspan="4" class="purchase-empty"><div class="icon">📭</div><p>로딩 중...</p></td></tr>
</tbody>
</table>
</div>
<div class="purchase-modal-footer">
<button class="purchase-modal-btn" onclick="closePurchaseModal()">닫기</button>
</div>
</div>
</div>
<script>
let allData = [];
let currentPage = 1;
@ -869,7 +1032,7 @@
const hasAmount = item.recoverable_amount && item.recoverable_amount > 0;
return `
<tr class="${urgencyClass}">
<tr class="${urgencyClass}" ondblclick="openPurchaseModal('${item.drug_code}', '${escapeHtml(item.drug_name).replace(/'/g, "\\'")}')">
<td>
<span class="urgency-badge ${urgency}">
${urgency === 'critical' ? '🔴' : urgency === 'warning' ? '🟠' : '⚪'}
@ -891,7 +1054,7 @@
<td class="date-cell">${formatDate(item.last_purchase_date) || '-'}</td>
<td><span class="status-badge ${item.status}">${getStatusLabel(item.status)}</span></td>
<td>
<button class="action-btn primary" onclick="openStatusModal(${item.id})">상태 변경</button>
<button class="action-btn primary" onclick="event.stopPropagation();openStatusModal(${item.id})">상태 변경</button>
</td>
</tr>
`;
@ -1044,6 +1207,67 @@
toast.className = 'toast show ' + type;
setTimeout(() => { toast.classList.remove('show'); }, 3000);
}
// ══════════════════ 입고이력 모달 ══════════════════
async function openPurchaseModal(drugCode, drugName) {
const modal = document.getElementById('purchaseModal');
const nameEl = document.getElementById('purchaseDrugName');
const tbody = document.getElementById('purchaseHistoryBody');
nameEl.textContent = drugName || drugCode;
tbody.innerHTML = '<tr><td colspan="4" class="purchase-empty"><div class="icon"></div><p>입고이력 조회 중...</p></td></tr>';
modal.classList.add('open');
try {
const res = await fetch(`/api/drugs/${drugCode}/purchase-history`);
const data = await res.json();
if (data.success) {
if (data.history.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="purchase-empty"><div class="icon">📭</div><p>입고 이력이 없습니다</p></td></tr>';
} else {
tbody.innerHTML = data.history.map(h => `
<tr>
<td>
<div class="supplier-name">${escapeHtml(h.supplier)}</div>
${h.supplier_tel ? `<div class="supplier-tel" onclick="event.stopPropagation();copyToClipboard('${h.supplier_tel}')" title="클릭하여 복사">📞 ${h.supplier_tel}</div>` : ''}
</td>
<td class="purchase-date">${h.date}</td>
<td class="purchase-qty">${h.quantity.toLocaleString()}</td>
<td class="purchase-price">${h.unit_price ? formatPrice(h.unit_price) : '-'}</td>
</tr>
`).join('');
}
} else {
tbody.innerHTML = `<tr><td colspan="4" class="purchase-empty"><div class="icon">⚠️</div><p>조회 실패: ${data.error}</p></td></tr>`;
}
} catch (err) {
tbody.innerHTML = `<tr><td colspan="4" class="purchase-empty"><div class="icon"></div><p>오류: ${err.message}</p></td></tr>`;
}
}
function closePurchaseModal() {
document.getElementById('purchaseModal').classList.remove('open');
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
showToast(`📋 ${text} 복사됨`, 'success');
}).catch(() => {
const input = document.createElement('input');
input.value = text;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
showToast(`📋 ${text} 복사됨`, 'success');
});
}
// 모달 외부 클릭 시 닫기
document.getElementById('purchaseModal').addEventListener('click', function(e) {
if (e.target === this) closePurchaseModal();
});
</script>
</body>
</html>