pharmacy-pos-qr-system/backend/templates/admin_members.html
thug0bin d44aed16be fix: 회원 상세 조회 시 모든 전화번호 컬럼 시도
- phone, phone1, tel_no, phone2 순서로 시도
- 전화번호 없는 회원 에러 방지 강화
2026-02-27 15:40:06 +09:00

1117 lines
39 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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;
const date = tx.created_at ? new Date(tx.created_at).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('');
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>
` : ''}
</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 => {
// 날짜 포맷
const date = item.created_at ? new Date(item.created_at).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();
</script>
</body>
</html>