1282 lines
49 KiB
HTML
1282 lines
49 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>회원 검색 - 청춘약국 CRM</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
|
||
background: #f8fafc;
|
||
-webkit-font-smoothing: antialiased;
|
||
color: #1e293b;
|
||
}
|
||
|
||
/* ── 헤더 ── */
|
||
.header {
|
||
background: linear-gradient(135deg, #059669 0%, #10b981 50%, #34d399 100%);
|
||
padding: 28px 32px 24px;
|
||
color: #fff;
|
||
}
|
||
.header-nav {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
.header-nav a {
|
||
color: rgba(255,255,255,0.8);
|
||
text-decoration: none;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
.header-nav a:hover { color: #fff; }
|
||
.header h1 {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
letter-spacing: -0.5px;
|
||
margin-bottom: 6px;
|
||
}
|
||
.header p {
|
||
font-size: 14px;
|
||
opacity: 0.85;
|
||
}
|
||
|
||
/* ── 컨텐츠 ── */
|
||
.content {
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 24px 20px 60px;
|
||
}
|
||
|
||
/* ── 검색 영역 ── */
|
||
.search-section {
|
||
background: #fff;
|
||
border-radius: 14px;
|
||
padding: 24px;
|
||
margin-bottom: 20px;
|
||
border: 1px solid #e2e8f0;
|
||
}
|
||
.search-box {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
.search-input {
|
||
flex: 1;
|
||
padding: 14px 18px;
|
||
border: 2px solid #e2e8f0;
|
||
border-radius: 12px;
|
||
font-size: 16px;
|
||
font-family: inherit;
|
||
transition: all 0.2s;
|
||
}
|
||
.search-input:focus {
|
||
outline: none;
|
||
border-color: #10b981;
|
||
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.1);
|
||
}
|
||
.search-btn {
|
||
background: #10b981;
|
||
color: #fff;
|
||
border: none;
|
||
padding: 14px 32px;
|
||
border-radius: 12px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.search-btn:hover { background: #059669; }
|
||
.search-hint {
|
||
margin-top: 12px;
|
||
font-size: 13px;
|
||
color: #94a3b8;
|
||
}
|
||
|
||
/* ── 결과 카운트 ── */
|
||
.result-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
.result-count {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
}
|
||
.result-count strong {
|
||
color: #10b981;
|
||
font-weight: 700;
|
||
}
|
||
.send-selected-btn {
|
||
background: #6366f1;
|
||
color: #fff;
|
||
border: none;
|
||
padding: 10px 20px;
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
display: none;
|
||
}
|
||
.send-selected-btn:hover { background: #4f46e5; }
|
||
.send-selected-btn.active { display: inline-flex; align-items: center; gap: 6px; }
|
||
|
||
/* ── 테이블 ── */
|
||
.table-wrap {
|
||
background: #fff;
|
||
border-radius: 14px;
|
||
border: 1px solid #e2e8f0;
|
||
overflow: hidden;
|
||
}
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
thead th {
|
||
background: #f8fafc;
|
||
padding: 14px 16px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #64748b;
|
||
text-align: left;
|
||
border-bottom: 1px solid #e2e8f0;
|
||
white-space: nowrap;
|
||
}
|
||
tbody td {
|
||
padding: 14px 16px;
|
||
font-size: 14px;
|
||
color: #334155;
|
||
border-bottom: 1px solid #f1f5f9;
|
||
vertical-align: middle;
|
||
}
|
||
tbody tr:hover { background: #f0fdf4; }
|
||
tbody tr.selected { background: #dcfce7; }
|
||
|
||
.member-name {
|
||
font-weight: 600;
|
||
color: #1e293b;
|
||
}
|
||
.member-phone {
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 13px;
|
||
color: #059669;
|
||
}
|
||
.member-memo {
|
||
font-size: 12px;
|
||
color: #94a3b8;
|
||
max-width: 200px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.sms-stop {
|
||
background: #fef2f2;
|
||
color: #dc2626;
|
||
font-size: 11px;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* ── 버튼 ── */
|
||
.btn-send {
|
||
background: #6366f1;
|
||
color: #fff;
|
||
border: none;
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
.btn-send:hover { background: #4f46e5; }
|
||
.btn-detail {
|
||
background: #f1f5f9;
|
||
color: #64748b;
|
||
border: none;
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
margin-right: 6px;
|
||
}
|
||
.btn-detail:hover { background: #e2e8f0; }
|
||
|
||
/* ── 체크박스 ── */
|
||
.checkbox {
|
||
width: 18px;
|
||
height: 18px;
|
||
cursor: pointer;
|
||
accent-color: #10b981;
|
||
}
|
||
|
||
/* ── 빈 상태 ── */
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: #94a3b8;
|
||
}
|
||
.empty-state .icon {
|
||
font-size: 48px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
/* ── 모달 ── */
|
||
.modal-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
z-index: 1000;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
.modal-overlay.active { display: flex; }
|
||
.modal-box {
|
||
background: #fff;
|
||
border-radius: 16px;
|
||
padding: 24px;
|
||
max-width: 500px;
|
||
width: 90%;
|
||
}
|
||
.modal-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
margin-bottom: 16px;
|
||
}
|
||
.modal-recipient {
|
||
background: #f8fafc;
|
||
padding: 12px 16px;
|
||
border-radius: 8px;
|
||
margin-bottom: 16px;
|
||
font-size: 14px;
|
||
}
|
||
.modal-recipient strong {
|
||
color: #10b981;
|
||
}
|
||
.modal-textarea {
|
||
width: 100%;
|
||
min-height: 150px;
|
||
padding: 14px;
|
||
border: 2px solid #e2e8f0;
|
||
border-radius: 10px;
|
||
font-size: 14px;
|
||
font-family: inherit;
|
||
resize: vertical;
|
||
margin-bottom: 12px;
|
||
}
|
||
.modal-textarea:focus {
|
||
outline: none;
|
||
border-color: #6366f1;
|
||
}
|
||
.char-count {
|
||
text-align: right;
|
||
font-size: 12px;
|
||
color: #94a3b8;
|
||
margin-bottom: 16px;
|
||
}
|
||
.msg-type-toggle {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.msg-type-btn {
|
||
flex: 1;
|
||
padding: 10px;
|
||
border: 2px solid #e2e8f0;
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.msg-type-btn.active {
|
||
border-color: #6366f1;
|
||
background: #eef2ff;
|
||
color: #6366f1;
|
||
}
|
||
.modal-btns {
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: flex-end;
|
||
}
|
||
.modal-btn {
|
||
padding: 10px 24px;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
.modal-btn.cancel { background: #f1f5f9; color: #64748b; }
|
||
.modal-btn.confirm { background: #6366f1; color: #fff; }
|
||
.modal-btn.confirm:hover { background: #4f46e5; }
|
||
|
||
/* ── 회원 상세 모달 ── */
|
||
.detail-modal {
|
||
max-width: 600px;
|
||
max-height: 85vh;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.detail-header {
|
||
background: linear-gradient(135deg, #059669, #10b981);
|
||
margin: -24px -24px 0;
|
||
padding: 24px;
|
||
border-radius: 16px 16px 0 0;
|
||
color: #fff;
|
||
}
|
||
.detail-name {
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
margin-bottom: 6px;
|
||
}
|
||
.detail-phone {
|
||
font-size: 15px;
|
||
opacity: 0.9;
|
||
}
|
||
.detail-balance {
|
||
margin-top: 12px;
|
||
padding: 12px 16px;
|
||
background: rgba(255,255,255,0.15);
|
||
border-radius: 10px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.detail-balance-label {
|
||
font-size: 13px;
|
||
opacity: 0.9;
|
||
}
|
||
.detail-balance-value {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
}
|
||
.detail-tabs {
|
||
display: flex;
|
||
border-bottom: 2px solid #e2e8f0;
|
||
margin: 20px -24px 0;
|
||
padding: 0 24px;
|
||
}
|
||
.detail-tab {
|
||
padding: 12px 20px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #64748b;
|
||
cursor: pointer;
|
||
border-bottom: 2px solid transparent;
|
||
margin-bottom: -2px;
|
||
transition: all 0.2s;
|
||
}
|
||
.detail-tab:hover { color: #10b981; }
|
||
.detail-tab.active {
|
||
color: #10b981;
|
||
border-bottom-color: #10b981;
|
||
}
|
||
.detail-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 20px 0;
|
||
max-height: 400px;
|
||
}
|
||
.detail-empty {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #94a3b8;
|
||
}
|
||
.detail-loading {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #64748b;
|
||
}
|
||
|
||
/* 거래 카드 */
|
||
.tx-card {
|
||
background: #f8fafc;
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
margin-bottom: 12px;
|
||
border-left: 4px solid #10b981;
|
||
}
|
||
.tx-card.negative { border-left-color: #f59e0b; }
|
||
.tx-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 8px;
|
||
}
|
||
.tx-date {
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
}
|
||
.tx-points {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #10b981;
|
||
}
|
||
.tx-points.negative { color: #f59e0b; }
|
||
.tx-desc {
|
||
font-size: 13px;
|
||
color: #475569;
|
||
}
|
||
.tx-items {
|
||
margin-top: 10px;
|
||
padding-top: 10px;
|
||
border-top: 1px dashed #e2e8f0;
|
||
}
|
||
.tx-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
padding: 4px 0;
|
||
}
|
||
.tx-item-name {
|
||
flex: 1;
|
||
}
|
||
.tx-item-qty {
|
||
color: #94a3b8;
|
||
margin: 0 12px;
|
||
}
|
||
.tx-item-price {
|
||
font-weight: 500;
|
||
color: #475569;
|
||
}
|
||
|
||
/* 구매 카드 */
|
||
.purchase-card {
|
||
background: #fff;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.purchase-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
.purchase-date {
|
||
font-size: 13px;
|
||
color: #64748b;
|
||
}
|
||
.purchase-total {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #1e293b;
|
||
}
|
||
.purchase-items {
|
||
border-top: 1px solid #f1f5f9;
|
||
padding-top: 12px;
|
||
}
|
||
.purchase-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 6px 0;
|
||
font-size: 13px;
|
||
}
|
||
.purchase-item-name {
|
||
flex: 1;
|
||
color: #334155;
|
||
}
|
||
.purchase-item-qty {
|
||
color: #94a3b8;
|
||
margin: 0 16px;
|
||
font-size: 12px;
|
||
}
|
||
.purchase-item-price {
|
||
color: #64748b;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.detail-footer {
|
||
padding-top: 16px;
|
||
border-top: 1px solid #e2e8f0;
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
/* ── 반응형 ── */
|
||
@media (max-width: 768px) {
|
||
.search-box { flex-direction: column; }
|
||
.table-wrap { overflow-x: auto; }
|
||
table { min-width: 700px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<div class="header-nav">
|
||
<a href="/admin">← 관리자 홈</a>
|
||
<div>
|
||
<a href="/admin/ai-crm" style="margin-right: 16px;">AI 업셀링</a>
|
||
<a href="/admin/alimtalk">알림톡</a>
|
||
</div>
|
||
</div>
|
||
<h1>👥 회원 검색</h1>
|
||
<p>팜IT3000 회원 검색 · 알림톡/SMS 발송</p>
|
||
</div>
|
||
|
||
<div class="content">
|
||
<!-- 검색 -->
|
||
<div class="search-section">
|
||
<div class="search-box">
|
||
<input type="text" class="search-input" id="searchInput"
|
||
placeholder="이름 또는 전화번호로 검색..."
|
||
onkeypress="if(event.key==='Enter')searchMembers()">
|
||
<button class="search-btn" onclick="searchMembers()">🔍 검색</button>
|
||
</div>
|
||
<div class="search-hint">
|
||
이름(예: 홍길동) 또는 전화번호(예: 01012345678) 입력
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 결과 헤더 -->
|
||
<div class="result-header" id="resultHeader" style="display:none;">
|
||
<div class="result-count">
|
||
검색 결과: <strong id="resultNum">0</strong>명
|
||
</div>
|
||
<button class="send-selected-btn" id="sendSelectedBtn" onclick="openBulkSendModal()">
|
||
📨 선택 발송 (<span id="selectedCount">0</span>명)
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 테이블 -->
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th><input type="checkbox" class="checkbox" id="selectAll" onchange="toggleSelectAll()"></th>
|
||
<th>이름</th>
|
||
<th>전화번호</th>
|
||
<th>메모</th>
|
||
<th>상태</th>
|
||
<th>액션</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="membersTableBody">
|
||
<tr>
|
||
<td colspan="6" class="empty-state">
|
||
<div class="icon">👥</div>
|
||
<p>이름 또는 전화번호로 회원을 검색하세요</p>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 발송 모달 -->
|
||
<div class="modal-overlay" id="sendModal" onclick="if(event.target===this)closeSendModal()">
|
||
<div class="modal-box">
|
||
<div class="modal-title">📨 메시지 발송</div>
|
||
<div class="modal-recipient" id="modalRecipient">
|
||
수신자: <strong>홍길동</strong> (010-1234-5678)
|
||
</div>
|
||
<div class="msg-type-toggle">
|
||
<button class="msg-type-btn active" data-type="sms" onclick="setMsgType('sms')">📱 SMS</button>
|
||
<button class="msg-type-btn" data-type="alimtalk" onclick="setMsgType('alimtalk')">💬 알림톡</button>
|
||
</div>
|
||
<textarea class="modal-textarea" id="messageInput" placeholder="메시지를 입력하세요..." oninput="updateCharCount()"></textarea>
|
||
<div class="char-count"><span id="charCount">0</span>/90자 (SMS 기준)</div>
|
||
<div class="modal-btns">
|
||
<button class="modal-btn cancel" onclick="closeSendModal()">취소</button>
|
||
<button class="modal-btn confirm" onclick="sendMessage()" id="sendBtn">발송</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 회원 상세 모달 -->
|
||
<div class="modal-overlay" id="detailModal" onclick="if(event.target===this)closeDetailModal()">
|
||
<div class="modal-box detail-modal">
|
||
<div class="detail-header">
|
||
<div class="detail-name" id="detailName">-</div>
|
||
<div class="detail-phone" id="detailPhone">-</div>
|
||
<div class="detail-balance">
|
||
<span class="detail-balance-label">💰 적립 포인트</span>
|
||
<span class="detail-balance-value" id="detailBalance">0P</span>
|
||
</div>
|
||
</div>
|
||
<div class="detail-tabs">
|
||
<div class="detail-tab active" data-tab="mileage" onclick="switchDetailTab('mileage')">📊 적립</div>
|
||
<div class="detail-tab" data-tab="purchase" onclick="switchDetailTab('purchase')">🛒 구매</div>
|
||
<div class="detail-tab" data-tab="prescription" onclick="switchDetailTab('prescription')">💊 조제</div>
|
||
<div class="detail-tab" data-tab="interest" onclick="switchDetailTab('interest')">💝 관심</div>
|
||
</div>
|
||
<div class="detail-content" id="detailContent">
|
||
<div class="detail-loading">데이터를 불러오는 중...</div>
|
||
</div>
|
||
<div class="detail-footer">
|
||
<button class="modal-btn cancel" onclick="closeDetailModal()">닫기</button>
|
||
<button class="modal-btn confirm" onclick="openSendFromDetail()">📨 메시지 발송</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let membersData = [];
|
||
let selectedMembers = new Set();
|
||
let currentMsgType = 'sms';
|
||
let sendTargets = [];
|
||
|
||
function searchMembers() {
|
||
const search = document.getElementById('searchInput').value.trim();
|
||
if (!search) {
|
||
alert('검색어를 입력하세요');
|
||
return;
|
||
}
|
||
if (search.length < 2) {
|
||
alert('2글자 이상 입력하세요');
|
||
return;
|
||
}
|
||
|
||
const tbody = document.getElementById('membersTableBody');
|
||
tbody.innerHTML = '<tr><td colspan="6" class="empty-state"><p>검색 중...</p></td></tr>';
|
||
|
||
fetch(`/api/members/search?q=${encodeURIComponent(search)}`)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
membersData = data.items;
|
||
selectedMembers.clear();
|
||
updateSelectedUI();
|
||
document.getElementById('resultHeader').style.display = 'flex';
|
||
document.getElementById('resultNum').textContent = membersData.length;
|
||
renderTable();
|
||
} else {
|
||
tbody.innerHTML = `<tr><td colspan="6" class="empty-state"><p>오류: ${data.error}</p></td></tr>`;
|
||
}
|
||
})
|
||
.catch(err => {
|
||
tbody.innerHTML = '<tr><td colspan="6" class="empty-state"><p>검색 실패</p></td></tr>';
|
||
});
|
||
}
|
||
|
||
function formatPhone(phone) {
|
||
if (!phone) return '-';
|
||
const p = phone.replace(/[^0-9]/g, '');
|
||
if (p.length === 11) {
|
||
return `${p.slice(0,3)}-${p.slice(3,7)}-${p.slice(7)}`;
|
||
} else if (p.length === 10) {
|
||
return `${p.slice(0,3)}-${p.slice(3,6)}-${p.slice(6)}`;
|
||
}
|
||
return phone;
|
||
}
|
||
|
||
function renderTable() {
|
||
const tbody = document.getElementById('membersTableBody');
|
||
|
||
if (membersData.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="6" class="empty-state"><div class="icon">📭</div><p>검색 결과가 없습니다</p></td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = membersData.map((m, idx) => `
|
||
<tr class="${selectedMembers.has(idx) ? 'selected' : ''}" data-idx="${idx}">
|
||
<td><input type="checkbox" class="checkbox" ${selectedMembers.has(idx) ? 'checked' : ''} onchange="toggleSelect(${idx})"></td>
|
||
<td class="member-name">${escapeHtml(m.name)}</td>
|
||
<td class="member-phone">${formatPhone(m.phone)}</td>
|
||
<td class="member-memo" title="${escapeHtml(m.memo)}">${escapeHtml(m.memo) || '-'}</td>
|
||
<td>${m.sms_stop ? '<span class="sms-stop">수신거부</span>' : '<span style="color:#10b981;">정상</span>'}</td>
|
||
<td>
|
||
<button class="btn-detail" onclick="viewDetail(${idx})">상세</button>
|
||
<button class="btn-send" onclick="openSendModal(${idx})" ${m.sms_stop ? 'disabled style="opacity:0.5"' : ''}>발송</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
if (!str) return '';
|
||
return str.replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));
|
||
}
|
||
|
||
function toggleSelect(idx) {
|
||
if (selectedMembers.has(idx)) {
|
||
selectedMembers.delete(idx);
|
||
} else {
|
||
selectedMembers.add(idx);
|
||
}
|
||
updateSelectedUI();
|
||
renderTable();
|
||
}
|
||
|
||
function toggleSelectAll() {
|
||
const allChecked = document.getElementById('selectAll').checked;
|
||
if (allChecked) {
|
||
membersData.forEach((m, idx) => {
|
||
if (!m.sms_stop) selectedMembers.add(idx);
|
||
});
|
||
} else {
|
||
selectedMembers.clear();
|
||
}
|
||
updateSelectedUI();
|
||
renderTable();
|
||
}
|
||
|
||
function updateSelectedUI() {
|
||
const count = selectedMembers.size;
|
||
document.getElementById('selectedCount').textContent = count;
|
||
const btn = document.getElementById('sendSelectedBtn');
|
||
btn.classList.toggle('active', count > 0);
|
||
}
|
||
|
||
function openSendModal(idx) {
|
||
const m = membersData[idx];
|
||
sendTargets = [m];
|
||
document.getElementById('modalRecipient').innerHTML =
|
||
`수신자: <strong>${escapeHtml(m.name)}</strong> (${formatPhone(m.phone)})`;
|
||
document.getElementById('messageInput').value = '';
|
||
updateCharCount();
|
||
document.getElementById('sendModal').classList.add('active');
|
||
}
|
||
|
||
function openBulkSendModal() {
|
||
if (selectedMembers.size === 0) return;
|
||
|
||
sendTargets = Array.from(selectedMembers).map(idx => membersData[idx]);
|
||
const names = sendTargets.slice(0, 3).map(m => m.name).join(', ');
|
||
const more = sendTargets.length > 3 ? ` 외 ${sendTargets.length - 3}명` : '';
|
||
|
||
document.getElementById('modalRecipient').innerHTML =
|
||
`수신자: <strong>${escapeHtml(names)}${more}</strong> (${sendTargets.length}명)`;
|
||
document.getElementById('messageInput').value = '';
|
||
updateCharCount();
|
||
document.getElementById('sendModal').classList.add('active');
|
||
}
|
||
|
||
function closeSendModal() {
|
||
document.getElementById('sendModal').classList.remove('active');
|
||
sendTargets = [];
|
||
}
|
||
|
||
function setMsgType(type) {
|
||
currentMsgType = type;
|
||
document.querySelectorAll('.msg-type-btn').forEach(btn => {
|
||
btn.classList.toggle('active', btn.dataset.type === type);
|
||
});
|
||
}
|
||
|
||
function updateCharCount() {
|
||
const len = document.getElementById('messageInput').value.length;
|
||
document.getElementById('charCount').textContent = len;
|
||
}
|
||
|
||
function sendMessage() {
|
||
const message = document.getElementById('messageInput').value.trim();
|
||
if (!message) {
|
||
alert('메시지를 입력하세요');
|
||
return;
|
||
}
|
||
if (sendTargets.length === 0) {
|
||
alert('수신자가 없습니다');
|
||
return;
|
||
}
|
||
|
||
const btn = document.getElementById('sendBtn');
|
||
btn.textContent = '발송 중...';
|
||
btn.disabled = true;
|
||
|
||
const recipients = sendTargets.map(m => ({
|
||
cuscode: m.cuscode,
|
||
name: m.name,
|
||
phone: m.phone
|
||
}));
|
||
|
||
fetch('/api/message/send', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({
|
||
recipients: recipients,
|
||
message: message,
|
||
type: currentMsgType
|
||
})
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
alert(`✅ ${data.message}`);
|
||
closeSendModal();
|
||
} else {
|
||
alert('❌ 발송 실패: ' + (data.error || '알 수 없는 오류'));
|
||
}
|
||
})
|
||
.catch(err => {
|
||
alert('❌ 오류: ' + err.message);
|
||
})
|
||
.finally(() => {
|
||
btn.textContent = '발송';
|
||
btn.disabled = false;
|
||
});
|
||
}
|
||
|
||
// ── 회원 상세 모달 ──
|
||
let detailData = null;
|
||
let currentDetailTab = 'mileage';
|
||
let currentDetailMember = null;
|
||
|
||
function viewDetail(idx) {
|
||
currentDetailMember = membersData[idx];
|
||
|
||
// 전화번호 우선순위: phone > phone1 > tel_no > phone2
|
||
let phone = (currentDetailMember.phone || '').replace(/-/g, '').replace(/ /g, '');
|
||
if (!phone) phone = (currentDetailMember.phone1 || '').replace(/-/g, '').replace(/ /g, '');
|
||
if (!phone) phone = (currentDetailMember.tel_no || '').replace(/-/g, '').replace(/ /g, '');
|
||
if (!phone) phone = (currentDetailMember.phone2 || '').replace(/-/g, '').replace(/ /g, '');
|
||
|
||
const displayPhone = currentDetailMember.phone || currentDetailMember.phone1 || currentDetailMember.tel_no || currentDetailMember.phone2 || '';
|
||
|
||
// 모달 열기
|
||
document.getElementById('detailModal').classList.add('active');
|
||
document.getElementById('detailName').textContent = currentDetailMember.name || '이름 없음';
|
||
document.getElementById('detailPhone').textContent = formatPhone(displayPhone) || '전화번호 없음';
|
||
document.getElementById('detailBalance').textContent = '로딩...';
|
||
document.getElementById('detailContent').innerHTML = '<div class="detail-loading">데이터를 불러오는 중...</div>';
|
||
|
||
// 전화번호 없으면 바로 안내
|
||
if (!phone) {
|
||
document.getElementById('detailBalance').textContent = '-';
|
||
document.getElementById('detailContent').innerHTML =
|
||
'<div class="detail-empty">📵 전화번호가 등록되지 않은 회원입니다<br><small style="color:#94a3b8;">POS에 전화번호를 등록하면 조회 가능합니다</small></div>';
|
||
detailData = { mileage: null, purchases: [], prescriptions: [], interests: [] };
|
||
return;
|
||
}
|
||
|
||
// 데이터 로드
|
||
fetch(`/api/members/history/${phone}`)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
detailData = data;
|
||
|
||
// 잔액 표시
|
||
if (data.mileage) {
|
||
document.getElementById('detailBalance').textContent =
|
||
data.mileage.balance.toLocaleString() + 'P';
|
||
} else {
|
||
document.getElementById('detailBalance').textContent = '미가입';
|
||
}
|
||
|
||
// 탭 콘텐츠 렌더링
|
||
renderDetailTab();
|
||
} else {
|
||
document.getElementById('detailContent').innerHTML =
|
||
`<div class="detail-empty">데이터 조회 실패: ${data.error}</div>`;
|
||
}
|
||
})
|
||
.catch(err => {
|
||
document.getElementById('detailContent').innerHTML =
|
||
`<div class="detail-empty">오류: ${err.message}</div>`;
|
||
});
|
||
}
|
||
|
||
function closeDetailModal() {
|
||
document.getElementById('detailModal').classList.remove('active');
|
||
detailData = null;
|
||
currentDetailMember = null;
|
||
}
|
||
|
||
function switchDetailTab(tab) {
|
||
currentDetailTab = tab;
|
||
document.querySelectorAll('.detail-tab').forEach(t => {
|
||
t.classList.toggle('active', t.dataset.tab === tab);
|
||
});
|
||
renderDetailTab();
|
||
}
|
||
|
||
function renderDetailTab() {
|
||
const content = document.getElementById('detailContent');
|
||
|
||
if (!detailData) {
|
||
content.innerHTML = '<div class="detail-empty">데이터가 없습니다</div>';
|
||
return;
|
||
}
|
||
|
||
if (currentDetailTab === 'mileage') {
|
||
renderMileageTab(content);
|
||
} else if (currentDetailTab === 'purchase') {
|
||
renderPurchaseTab(content);
|
||
} else if (currentDetailTab === 'prescription') {
|
||
renderPrescriptionTab(content);
|
||
} else if (currentDetailTab === 'interest') {
|
||
renderInterestTab(content);
|
||
}
|
||
}
|
||
|
||
function renderMileageTab(container) {
|
||
if (!detailData.mileage || !detailData.mileage.transactions || detailData.mileage.transactions.length === 0) {
|
||
container.innerHTML = '<div class="detail-empty">📭 적립 내역이 없습니다</div>';
|
||
return;
|
||
}
|
||
|
||
const txs = detailData.mileage.transactions;
|
||
container.innerHTML = txs.map(tx => {
|
||
const isPositive = tx.points > 0;
|
||
// DB는 UTC로 저장 → 'Z' 붙여서 UTC로 해석 → KST로 표시
|
||
const date = tx.created_at ? new Date(tx.created_at + 'Z').toLocaleString('ko-KR', {
|
||
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
|
||
}) : '';
|
||
|
||
// 품목 렌더링
|
||
let itemsHtml = '';
|
||
if (tx.items && tx.items.length > 0) {
|
||
itemsHtml = `
|
||
<div class="tx-items">
|
||
${tx.items.map(item => `
|
||
<div class="tx-item">
|
||
<span class="tx-item-name">${escapeHtml(item.name)}</span>
|
||
<span class="tx-item-qty">x${item.quantity}</span>
|
||
<span class="tx-item-price">${item.price.toLocaleString()}원</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 금액 표시
|
||
const amountText = tx.total_amount ? ` (${tx.total_amount.toLocaleString()}원 구매)` : '';
|
||
|
||
return `
|
||
<div class="tx-card ${isPositive ? '' : 'negative'}">
|
||
<div class="tx-header">
|
||
<div class="tx-date">📅 ${date}</div>
|
||
<div class="tx-points ${isPositive ? '' : 'negative'}">
|
||
${isPositive ? '+' : ''}${tx.points.toLocaleString()}P
|
||
</div>
|
||
</div>
|
||
<div class="tx-desc">${escapeHtml(tx.description || tx.reason || '')}${amountText}</div>
|
||
${itemsHtml}
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function renderPurchaseTab(container) {
|
||
// POS 전체 구매 이력 (고객코드 기준)
|
||
if (!detailData.purchases || detailData.purchases.length === 0) {
|
||
if (!detailData.pos_customer) {
|
||
container.innerHTML = '<div class="detail-empty">📭 POS 회원으로 등록되지 않았습니다<br><small style="color:#94a3b8;">전화번호가 POS에 등록되면 구매 이력이 표시됩니다</small></div>';
|
||
} else {
|
||
container.innerHTML = '<div class="detail-empty">📭 구매 이력이 없습니다</div>';
|
||
}
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = detailData.purchases.map(p => {
|
||
// 날짜 포맷
|
||
const dateStr = p.date || '';
|
||
let formattedDate = dateStr;
|
||
if (dateStr.length === 8) {
|
||
formattedDate = `${dateStr.slice(0,4)}.${dateStr.slice(4,6)}.${dateStr.slice(6,8)}`;
|
||
}
|
||
|
||
// 품목 렌더링
|
||
const itemsHtml = (p.items || []).map(item => `
|
||
<div class="purchase-item">
|
||
<span class="purchase-item-name">${escapeHtml(item.name)}</span>
|
||
<span class="purchase-item-qty">x${item.quantity}</span>
|
||
<span class="purchase-item-price">${item.price.toLocaleString()}원</span>
|
||
</div>
|
||
`).join('');
|
||
|
||
return `
|
||
<div class="purchase-card">
|
||
<div class="purchase-header">
|
||
<span class="purchase-date">📅 ${formattedDate}</span>
|
||
<span class="purchase-total">${(p.total || 0).toLocaleString()}원</span>
|
||
</div>
|
||
${p.items && p.items.length > 0 ? `
|
||
<div class="purchase-items">${itemsHtml}</div>
|
||
` : ''}
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function renderPrescriptionTab(container) {
|
||
// 조제 이력 (고객코드 기준)
|
||
if (!detailData.prescriptions || detailData.prescriptions.length === 0) {
|
||
if (!detailData.pos_customer) {
|
||
container.innerHTML = '<div class="detail-empty">📭 POS 회원으로 등록되지 않았습니다<br><small style="color:#94a3b8;">전화번호가 POS에 등록되면 조제 이력이 표시됩니다</small></div>';
|
||
} else {
|
||
container.innerHTML = '<div class="detail-empty">📭 조제 이력이 없습니다</div>';
|
||
}
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = detailData.prescriptions.map(rx => {
|
||
// 날짜 포맷
|
||
const dateStr = rx.date || '';
|
||
let formattedDate = dateStr;
|
||
if (dateStr.length === 8) {
|
||
formattedDate = `${dateStr.slice(0,4)}.${dateStr.slice(4,6)}.${dateStr.slice(6,8)}`;
|
||
}
|
||
|
||
// 처방 품목 렌더링 (투약량 x 횟수 x 일수)
|
||
const itemsHtml = (rx.items || []).map(item => {
|
||
const dosage = item.quantity || 1; // 1회 투약량
|
||
const freq = item.times_per_day || 1; // 1일 투약횟수
|
||
const days = item.days || 0; // 투약일수
|
||
return `
|
||
<div class="purchase-item">
|
||
<span class="purchase-item-name">${escapeHtml(item.name)}</span>
|
||
<span class="purchase-item-qty" style="min-width:100px;text-align:right;color:#6366f1;">
|
||
${dosage}정 × ${freq}회 × ${days}일
|
||
</span>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
// 약품 코드 배열 (상호작용 체크용)
|
||
const drugCodes = (rx.items || []).map(item => item.drug_code).filter(c => c);
|
||
const drugCodesJson = JSON.stringify(drugCodes);
|
||
|
||
return `
|
||
<div class="purchase-card" style="border-left: 3px solid #6366f1;">
|
||
<div class="purchase-header">
|
||
<span class="purchase-date">📅 ${formattedDate}</span>
|
||
<span style="font-size:12px;color:#6366f1;">${rx.total_days || ''}일분</span>
|
||
</div>
|
||
<div style="font-size:12px;color:#64748b;margin-bottom:8px;">
|
||
🏥 ${escapeHtml(rx.hospital || '')} · ${escapeHtml(rx.doctor || '')}
|
||
</div>
|
||
${rx.items && rx.items.length > 0 ? `
|
||
<div class="purchase-items">${itemsHtml}</div>
|
||
` : ''}
|
||
${drugCodes.length >= 2 ? `
|
||
<div style="margin-top:10px;text-align:right;">
|
||
<button onclick='checkDrugInteraction(${drugCodesJson}, "${rx.pre_serial || ""}")'
|
||
style="background:linear-gradient(135deg,#8b5cf6,#6366f1);color:#fff;border:none;padding:8px 14px;border-radius:8px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;">
|
||
🔬 AI 상호작용 체크
|
||
</button>
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function renderInterestTab(container) {
|
||
// AI 업셀링에서 '관심있어요' 표시한 상품
|
||
if (!detailData.interests || detailData.interests.length === 0) {
|
||
container.innerHTML = '<div class="detail-empty">💝 관심 상품이 없습니다<br><small style="color:#94a3b8;">마일리지 적립 시 AI 추천에서 "관심있어요"를 누르면<br>여기에 표시됩니다</small></div>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = detailData.interests.map(item => {
|
||
// 날짜 포맷 (DB는 UTC → KST 변환)
|
||
const date = item.created_at ? new Date(item.created_at + 'Z').toLocaleString('ko-KR', {
|
||
month: 'short', day: 'numeric'
|
||
}) : '';
|
||
|
||
// 트리거 상품 (JSON 파싱)
|
||
let triggerText = '';
|
||
try {
|
||
const triggers = JSON.parse(item.trigger_products || '[]');
|
||
if (triggers.length > 0) {
|
||
triggerText = triggers.join(', ');
|
||
}
|
||
} catch(e) {}
|
||
|
||
return `
|
||
<div class="purchase-card" style="border-left: 3px solid #ec4899;">
|
||
<div class="purchase-header">
|
||
<span style="font-size:15px;font-weight:700;color:#ec4899;">💝 ${escapeHtml(item.product)}</span>
|
||
<span class="purchase-date">${date}</span>
|
||
</div>
|
||
<div style="font-size:13px;color:#475569;margin:8px 0;line-height:1.5;">
|
||
${escapeHtml(item.message || '')}
|
||
</div>
|
||
${triggerText ? `
|
||
<div style="font-size:11px;color:#94a3b8;margin-top:8px;">
|
||
🛒 구매 상품: ${escapeHtml(triggerText)}
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function openSendFromDetail() {
|
||
if (!currentDetailMember) return;
|
||
closeDetailModal();
|
||
|
||
// 발송 모달 열기
|
||
sendTargets = [currentDetailMember];
|
||
document.getElementById('modalRecipient').innerHTML =
|
||
`수신자: <strong>${escapeHtml(currentDetailMember.name)}</strong> (${formatPhone(currentDetailMember.phone)})`;
|
||
document.getElementById('messageInput').value = '';
|
||
updateCharCount();
|
||
document.getElementById('sendModal').classList.add('active');
|
||
}
|
||
|
||
// 페이지 로드 시 검색창 포커스
|
||
document.getElementById('searchInput').focus();
|
||
|
||
// ═══════════════════════════════════════════════════
|
||
// KIMS 약물 상호작용 체크
|
||
// ═══════════════════════════════════════════════════
|
||
|
||
async function checkDrugInteraction(drugCodes, preSerial) {
|
||
// 로딩 모달 표시
|
||
showInteractionModal('loading');
|
||
|
||
try {
|
||
const response = await fetch('/api/kims/interaction-check', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({
|
||
drug_codes: drugCodes,
|
||
pre_serial: preSerial
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
showInteractionModal('result', data);
|
||
} else {
|
||
showInteractionModal('error', data.error || '알 수 없는 오류');
|
||
}
|
||
} catch (err) {
|
||
showInteractionModal('error', '서버 연결 실패: ' + err.message);
|
||
}
|
||
}
|
||
|
||
function showInteractionModal(type, data) {
|
||
let modal = document.getElementById('interactionModal');
|
||
if (!modal) {
|
||
// 모달 생성
|
||
modal = document.createElement('div');
|
||
modal.id = 'interactionModal';
|
||
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999;';
|
||
modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
|
||
document.body.appendChild(modal);
|
||
}
|
||
|
||
let content = '';
|
||
|
||
if (type === 'loading') {
|
||
content = `
|
||
<div style="background:#fff;border-radius:16px;padding:40px;text-align:center;max-width:400px;">
|
||
<div style="font-size:48px;margin-bottom:16px;">🔬</div>
|
||
<div style="font-size:18px;font-weight:600;color:#334155;">상호작용 분석 중...</div>
|
||
<div style="font-size:14px;color:#64748b;margin-top:8px;">KIMS 데이터베이스 조회 중</div>
|
||
</div>
|
||
`;
|
||
} else if (type === 'error') {
|
||
content = `
|
||
<div style="background:#fff;border-radius:16px;padding:30px;max-width:400px;">
|
||
<div style="font-size:40px;text-align:center;margin-bottom:16px;">⚠️</div>
|
||
<div style="font-size:16px;font-weight:600;color:#dc2626;text-align:center;">분석 실패</div>
|
||
<div style="font-size:14px;color:#64748b;margin-top:12px;text-align:center;">${escapeHtml(data)}</div>
|
||
<div style="text-align:center;margin-top:20px;">
|
||
<button onclick="document.getElementById('interactionModal').remove()"
|
||
style="background:#6366f1;color:#fff;border:none;padding:10px 24px;border-radius:8px;cursor:pointer;">
|
||
닫기
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else if (type === 'result') {
|
||
const interactions = data.interactions || [];
|
||
const drugsChecked = data.drugs_checked || [];
|
||
|
||
// 약품 목록 (상호작용 있는 약품은 빨간색/주황색 배경)
|
||
const drugsHtml = drugsChecked.map(d => {
|
||
const hasInteraction = d.has_interaction;
|
||
const bgColor = hasInteraction ? '#fef2f2' : '#f1f5f9'; // 연한 빨강 vs 회색
|
||
const borderColor = hasInteraction ? '#fca5a5' : '#e2e8f0';
|
||
const textColor = hasInteraction ? '#dc2626' : '#334155';
|
||
const icon = hasInteraction ? '⚠️ ' : '';
|
||
return `<span style="display:inline-block;background:${bgColor};border:1px solid ${borderColor};color:${textColor};padding:4px 8px;border-radius:4px;margin:2px;font-size:12px;">${icon}${escapeHtml(d.name.slice(0,20))}</span>`;
|
||
}).join('');
|
||
|
||
// 상호작용 목록
|
||
let interactionsHtml = '';
|
||
if (interactions.length === 0) {
|
||
interactionsHtml = `
|
||
<div style="text-align:center;padding:30px;">
|
||
<div style="font-size:48px;margin-bottom:12px;">✅</div>
|
||
<div style="font-size:16px;font-weight:600;color:#10b981;">상호작용 없음</div>
|
||
<div style="font-size:13px;color:#64748b;margin-top:8px;">
|
||
${data.total_pairs}개 약품 조합을 검사했습니다.<br>
|
||
주의가 필요한 상호작용이 발견되지 않았습니다.
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
interactionsHtml = interactions.map(item => `
|
||
<div style="background:#fff;border:1px solid ${item.severity_color};border-radius:12px;padding:16px;margin-bottom:12px;">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||
<span style="font-weight:600;color:#334155;">
|
||
${escapeHtml(item.drug1_name?.slice(0,20) || '')} ↔ ${escapeHtml(item.drug2_name?.slice(0,20) || '')}
|
||
</span>
|
||
<span style="background:${item.severity_color};color:#fff;padding:4px 10px;border-radius:12px;font-size:12px;font-weight:500;">
|
||
${item.severity_text}
|
||
</span>
|
||
</div>
|
||
${item.description ? `
|
||
<div style="font-size:13px;color:#475569;margin-bottom:8px;line-height:1.5;">
|
||
📋 ${escapeHtml(item.description)}
|
||
</div>
|
||
` : ''}
|
||
${item.management ? `
|
||
<div style="font-size:12px;color:#059669;background:#ecfdf5;padding:8px 12px;border-radius:6px;">
|
||
💡 ${escapeHtml(item.management)}
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
content = `
|
||
<div style="background:#f8fafc;border-radius:20px;max-width:500px;max-height:80vh;overflow:hidden;display:flex;flex-direction:column;">
|
||
<div style="background:linear-gradient(135deg,#8b5cf6,#6366f1);padding:20px 24px;color:#fff;">
|
||
<div style="font-size:18px;font-weight:700;display:flex;align-items:center;gap:10px;">
|
||
🔬 약물 상호작용 분석
|
||
</div>
|
||
<div style="font-size:13px;opacity:0.9;margin-top:6px;">
|
||
${drugsChecked.length}개 약품 · ${data.total_pairs}개 조합 검사
|
||
</div>
|
||
</div>
|
||
<div style="padding:16px 20px;border-bottom:1px solid #e2e8f0;">
|
||
<div style="font-size:12px;color:#64748b;margin-bottom:6px;">분석 약품</div>
|
||
${drugsHtml}
|
||
</div>
|
||
<div style="flex:1;overflow-y:auto;padding:16px 20px;">
|
||
${interactions.length > 0 ? `
|
||
<div style="font-size:13px;color:#dc2626;font-weight:600;margin-bottom:12px;">
|
||
⚠️ ${interactions.length}건의 상호작용 발견
|
||
</div>
|
||
` : ''}
|
||
${interactionsHtml}
|
||
</div>
|
||
<div style="padding:16px 20px;border-top:1px solid #e2e8f0;text-align:center;">
|
||
<button onclick="document.getElementById('interactionModal').remove()"
|
||
style="background:#6366f1;color:#fff;border:none;padding:12px 32px;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;">
|
||
닫기
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
modal.innerHTML = content;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|