feat: PMR OTC 구매 이력 기능

- /pmr/api/patient/<cus_code>/otc: OTC 구매 이력 API
- SALE_MAIN + SALE_SUB (PRESERIAL='V' = OTC)
- 💊 OTC 뱃지 클릭 → 모달로 구매 이력 표시
- 자주 구매하는 품목 요약
- 방문/금액 통계
This commit is contained in:
thug0bin
2026-03-04 23:55:54 +09:00
parent 41428646ab
commit ebf2e8a016
2 changed files with 350 additions and 0 deletions

View File

@@ -150,6 +150,125 @@
color: #92400e !important;
margin-left: 5px;
}
.detail-header .rx-info .otc-badge {
background: #dbeafe !important;
color: #1e40af !important;
cursor: pointer;
transition: all 0.2s;
}
.detail-header .rx-info .otc-badge:hover {
background: #bfdbfe !important;
transform: scale(1.05);
}
/* OTC 모달 */
.otc-modal {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6);
z-index: 1000;
overflow-y: auto;
}
.otc-modal-content {
max-width: 600px;
margin: 40px auto;
background: #fff;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
.otc-modal-header {
background: linear-gradient(135deg, #3b82f6, #60a5fa);
color: #fff;
padding: 20px 25px;
display: flex;
justify-content: space-between;
align-items: center;
}
.otc-modal-header h3 { margin: 0; font-size: 1.2rem; }
.otc-modal-close {
background: none;
border: none;
color: #fff;
font-size: 1.5rem;
cursor: pointer;
opacity: 0.8;
}
.otc-modal-close:hover { opacity: 1; }
.otc-summary {
display: flex;
gap: 20px;
padding: 15px 25px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
}
.otc-summary-item {
text-align: center;
}
.otc-summary-item .num {
font-size: 1.5rem;
font-weight: 700;
color: #1e40af;
}
.otc-summary-item .label {
font-size: 0.75rem;
color: #64748b;
}
.otc-frequent {
padding: 15px 25px;
border-bottom: 1px solid #e2e8f0;
}
.otc-frequent h4 {
margin: 0 0 10px 0;
font-size: 0.9rem;
color: #475569;
}
.otc-frequent-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.otc-frequent-item {
background: #eff6ff;
color: #1e40af;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8rem;
}
.otc-purchases {
max-height: 300px;
overflow-y: auto;
padding: 15px 25px;
}
.otc-purchase {
border: 1px solid #e2e8f0;
border-radius: 10px;
margin-bottom: 12px;
overflow: hidden;
}
.otc-purchase-header {
background: #f1f5f9;
padding: 10px 15px;
display: flex;
justify-content: space-between;
font-size: 0.85rem;
}
.otc-purchase-header .date { color: #475569; font-weight: 600; }
.otc-purchase-header .amount { color: #1e40af; font-weight: 600; }
.otc-purchase-items {
padding: 10px 15px;
}
.otc-purchase-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
font-size: 0.85rem;
border-bottom: 1px solid #f1f5f9;
}
.otc-purchase-item:last-child { border-bottom: none; }
.otc-purchase-item .name { color: #1e293b; }
.otc-purchase-item .qty { color: #64748b; }
/* 약품 목록 */
.medication-list {
@@ -443,6 +562,19 @@
<button class="btn btn-primary" onclick="printLabels()">🖨️ 라벨 인쇄</button>
</div>
<!-- OTC 구매 이력 모달 -->
<div class="otc-modal" id="otcModal">
<div class="otc-modal-content">
<div class="otc-modal-header">
<h3>💊 OTC 구매 이력</h3>
<button class="otc-modal-close" onclick="closeOtcModal()">×</button>
</div>
<div class="otc-summary" id="otcSummary"></div>
<div class="otc-frequent" id="otcFrequent"></div>
<div class="otc-purchases" id="otcPurchases"></div>
</div>
</div>
<!-- 미리보기 모달 -->
<div id="previewModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:1000;overflow-y:auto;">
<div style="max-width:400px;margin:50px auto;background:#fff;border-radius:12px;padding:20px;">
@@ -480,6 +612,7 @@
let historyData = [];
let historyIndex = 0;
let compareMode = false;
let otcData = null;
// HTML 이스케이프
function escapeHtml(text) {
@@ -644,6 +777,7 @@
currentPatientCode = data.patient.code;
if (currentPatientCode) {
loadPatientHistory(currentPatientCode, prescriptionId);
checkOtcHistory(currentPatientCode);
}
}
} catch (err) {
@@ -917,6 +1051,89 @@
`;
}
// OTC 구매 이력 체크
async function checkOtcHistory(cusCode) {
try {
const res = await fetch(`/pmr/api/patient/${cusCode}/otc?limit=20`);
const data = await res.json();
if (data.success && data.count > 0) {
otcData = data;
// OTC 뱃지 추가 (질병 뱃지 앞에)
const rxInfo = document.getElementById('rxInfo');
const otcBadge = `<span class="otc-badge" onclick="showOtcModal()">💊 OTC ${data.count}</span>`;
rxInfo.innerHTML = otcBadge + rxInfo.innerHTML;
} else {
otcData = null;
}
} catch (err) {
console.error('OTC check error:', err);
otcData = null;
}
}
// OTC 모달 표시
function showOtcModal() {
if (!otcData) return;
const modal = document.getElementById('otcModal');
const summary = document.getElementById('otcSummary');
const frequent = document.getElementById('otcFrequent');
const purchases = document.getElementById('otcPurchases');
// 요약
summary.innerHTML = `
<div class="otc-summary-item">
<div class="num">${otcData.summary.total_visits}</div>
<div class="label">방문</div>
</div>
<div class="otc-summary-item">
<div class="num">${(otcData.summary.total_amount / 10000).toFixed(1)}만</div>
<div class="label">총 구매액</div>
</div>
`;
// 자주 구매하는 품목
if (otcData.summary.frequent_items && otcData.summary.frequent_items.length > 0) {
frequent.innerHTML = `
<h4>🔥 자주 구매하는 품목</h4>
<div class="otc-frequent-list">
${otcData.summary.frequent_items.map(item =>
`<span class="otc-frequent-item">${item.name} (${item.count}회)</span>`
).join('')}
</div>
`;
frequent.style.display = 'block';
} else {
frequent.style.display = 'none';
}
// 구매 이력
purchases.innerHTML = otcData.purchases.map(p => `
<div class="otc-purchase">
<div class="otc-purchase-header">
<span class="date">📅 ${p.date?.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3') || p.datetime}</span>
<span class="amount">${p.amount.toLocaleString()}원</span>
</div>
<div class="otc-purchase-items">
${p.items.map(item => `
<div class="otc-purchase-item">
<span class="name">${item.name}</span>
<span class="qty">${item.quantity}개 / ${item.price.toLocaleString()}원</span>
</div>
`).join('')}
</div>
</div>
`).join('');
modal.style.display = 'block';
}
// OTC 모달 닫기
function closeOtcModal() {
document.getElementById('otcModal').style.display = 'none';
}
// 상세 초기화
function clearDetail() {
document.getElementById('detailHeader').style.display = 'none';
@@ -925,6 +1142,7 @@
document.getElementById('compareToggle').style.display = 'none';
document.getElementById('compareMode').checked = false;
document.getElementById('compareLegend').style.display = 'none';
document.getElementById('otcModal').style.display = 'none';
document.getElementById('medicationList').innerHTML = `
<div class="empty-state">
<div class="icon">👈</div>
@@ -937,6 +1155,7 @@
historyData = [];
historyIndex = 0;
compareMode = false;
otcData = null;
}
// 전체 선택 토글