feat(admin): 제품 사용이력 + 환자 최근처방 모달 기능
- GET /api/products/<drug_code>/usage-history - 제품별 처방 이력 조회 (환자명, 수량, 횟수, 일수, 총투약량) - 페이지네이션 + 기간 필터 지원 - GET /api/patients/<cus_code>/recent-prescriptions - 환자 최근 6개월 처방 내역 - 약품별 분류(CD_MC.PRINT_TYPE) 표시 (당뇨치료, 고지혈치료 등) - admin_products.html 모달 2개 추가 - 제품명 클릭 → 사용이력 모달 (횟수 포함 정확한 총투약량) - 환자명 클릭 → 최근처방 모달 (z-index 2100으로 위에 표시) DB 조인: - PS_main.PreSerial = PS_sub_pharm.PreSerial - CD_GOODS + CD_MC (PRINT_TYPE 분류)
This commit is contained in:
parent
83ecf88bd4
commit
688bdb40f2
232
backend/app.py
232
backend/app.py
@ -3872,6 +3872,238 @@ def api_drug_purchase_history(drug_code):
|
|||||||
return jsonify({'success': False, 'error': str(e)}), 500
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 처방 사용이력 API ====================
|
||||||
|
|
||||||
|
@app.route('/api/products/<drug_code>/usage-history')
|
||||||
|
def api_product_usage_history(drug_code):
|
||||||
|
"""
|
||||||
|
제품 처방 사용이력 조회 API
|
||||||
|
- PS_main + PS_sub_pharm JOIN
|
||||||
|
- 페이지네이션, 기간 필터 지원
|
||||||
|
- 환자명 마스킹 처리
|
||||||
|
"""
|
||||||
|
page = int(request.args.get('page', 1))
|
||||||
|
per_page = int(request.args.get('per_page', 20))
|
||||||
|
months = int(request.args.get('months', 12))
|
||||||
|
|
||||||
|
offset = (page - 1) * per_page
|
||||||
|
|
||||||
|
try:
|
||||||
|
pres_session = db_manager.get_session('PM_PRES')
|
||||||
|
|
||||||
|
# 기간 계산 (N개월 전부터)
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
start_date = (datetime.now() - timedelta(days=months * 30)).strftime('%Y%m%d')
|
||||||
|
|
||||||
|
# 총 건수 조회 (COUNT)
|
||||||
|
count_result = pres_session.execute(text("""
|
||||||
|
SELECT COUNT(*) as total_count
|
||||||
|
FROM PS_sub_pharm sp
|
||||||
|
JOIN PS_main pm ON pm.PreSerial = sp.PreSerial
|
||||||
|
WHERE sp.DrugCode = :drug_code
|
||||||
|
AND pm.Indate >= :start_date
|
||||||
|
AND (sp.PS_Type IS NULL OR sp.PS_Type != '9')
|
||||||
|
"""), {'drug_code': drug_code, 'start_date': start_date})
|
||||||
|
total_count = count_result.fetchone()[0]
|
||||||
|
|
||||||
|
# 데이터 조회 (페이지네이션)
|
||||||
|
data_result = pres_session.execute(text("""
|
||||||
|
SELECT
|
||||||
|
pm.Paname as patient_name,
|
||||||
|
pm.CusCode as cus_code,
|
||||||
|
pm.Indate as rx_date,
|
||||||
|
sp.QUAN as quantity,
|
||||||
|
sp.QUAN_TIME as times,
|
||||||
|
sp.Days as days
|
||||||
|
FROM PS_sub_pharm sp
|
||||||
|
JOIN PS_main pm ON pm.PreSerial = sp.PreSerial
|
||||||
|
WHERE sp.DrugCode = :drug_code
|
||||||
|
AND pm.Indate >= :start_date
|
||||||
|
AND (sp.PS_Type IS NULL OR sp.PS_Type != '9')
|
||||||
|
ORDER BY pm.Indate DESC
|
||||||
|
OFFSET :offset ROWS
|
||||||
|
FETCH NEXT :per_page ROWS ONLY
|
||||||
|
"""), {
|
||||||
|
'drug_code': drug_code,
|
||||||
|
'start_date': start_date,
|
||||||
|
'offset': offset,
|
||||||
|
'per_page': per_page
|
||||||
|
})
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for row in data_result.fetchall():
|
||||||
|
patient_name = row.patient_name or ''
|
||||||
|
cus_code = row.cus_code or ''
|
||||||
|
|
||||||
|
# 날짜 포맷팅 (YYYYMMDD -> YYYY-MM-DD)
|
||||||
|
rx_date = row.rx_date or ''
|
||||||
|
if len(rx_date) == 8:
|
||||||
|
rx_date = f"{rx_date[:4]}-{rx_date[4:6]}-{rx_date[6:8]}"
|
||||||
|
|
||||||
|
quantity = int(row.quantity) if row.quantity else 1
|
||||||
|
times = int(row.times) if row.times else 1 # 횟수 (QUAN_TIME)
|
||||||
|
days = int(row.days) if row.days else 1
|
||||||
|
total_dose = quantity * times * days # 수량 × 횟수 × 일수
|
||||||
|
|
||||||
|
items.append({
|
||||||
|
'patient_name': patient_name,
|
||||||
|
'cus_code': cus_code,
|
||||||
|
'rx_date': rx_date,
|
||||||
|
'quantity': quantity,
|
||||||
|
'times': times,
|
||||||
|
'days': days,
|
||||||
|
'total_dose': total_dose
|
||||||
|
})
|
||||||
|
|
||||||
|
# 약품명 조회
|
||||||
|
drug_session = db_manager.get_session('PM_DRUG')
|
||||||
|
name_result = drug_session.execute(text("""
|
||||||
|
SELECT GoodsName FROM CD_GOODS WHERE DrugCode = :drug_code
|
||||||
|
"""), {'drug_code': drug_code})
|
||||||
|
name_row = name_result.fetchone()
|
||||||
|
product_name = name_row[0] if name_row else drug_code
|
||||||
|
|
||||||
|
# 총 페이지 수 계산
|
||||||
|
total_pages = (total_count + per_page - 1) // per_page
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'product_name': product_name,
|
||||||
|
'pagination': {
|
||||||
|
'page': page,
|
||||||
|
'per_page': per_page,
|
||||||
|
'total_count': total_count,
|
||||||
|
'total_pages': total_pages
|
||||||
|
},
|
||||||
|
'items': items
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"사용이력 조회 오류: {e}")
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/patients/<cus_code>/recent-prescriptions')
|
||||||
|
def api_patient_recent_prescriptions(cus_code):
|
||||||
|
"""
|
||||||
|
환자 최근 처방 내역 조회 API
|
||||||
|
- 해당 환자가 최근에 어떤 약을 처방받았는지 확인
|
||||||
|
- DB 부담 최소화: 최근 6개월, 최대 30건
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pres_session = db_manager.get_session('PM_PRES')
|
||||||
|
drug_session = db_manager.get_session('PM_DRUG')
|
||||||
|
|
||||||
|
# 최근 6개월
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
start_date = (datetime.now() - timedelta(days=180)).strftime('%Y%m%d')
|
||||||
|
|
||||||
|
# 환자의 최근 처방전 조회 (최대 10건)
|
||||||
|
rx_result = pres_session.execute(text("""
|
||||||
|
SELECT TOP 10
|
||||||
|
pm.PreSerial,
|
||||||
|
pm.Paname as patient_name,
|
||||||
|
pm.Indate as rx_date,
|
||||||
|
pm.Drname as doctor_name,
|
||||||
|
pm.OrderName as hospital_name
|
||||||
|
FROM PS_main pm
|
||||||
|
WHERE pm.CusCode = :cus_code
|
||||||
|
AND pm.Indate >= :start_date
|
||||||
|
ORDER BY pm.Indate DESC
|
||||||
|
"""), {'cus_code': cus_code, 'start_date': start_date})
|
||||||
|
|
||||||
|
prescriptions = []
|
||||||
|
for rx in rx_result.fetchall():
|
||||||
|
# 날짜 포맷팅
|
||||||
|
rx_date = rx.rx_date or ''
|
||||||
|
if len(rx_date) == 8:
|
||||||
|
rx_date = f"{rx_date[:4]}-{rx_date[4:6]}-{rx_date[6:8]}"
|
||||||
|
|
||||||
|
# 해당 처방의 약품 목록 조회
|
||||||
|
items_result = pres_session.execute(text("""
|
||||||
|
SELECT
|
||||||
|
sp.DrugCode,
|
||||||
|
sp.QUAN as quantity,
|
||||||
|
sp.QUAN_TIME as times,
|
||||||
|
sp.Days as days
|
||||||
|
FROM PS_sub_pharm sp
|
||||||
|
WHERE sp.PreSerial = :pre_serial
|
||||||
|
AND (sp.PS_Type IS NULL OR sp.PS_Type != '9')
|
||||||
|
"""), {'pre_serial': rx.PreSerial})
|
||||||
|
|
||||||
|
# 먼저 모든 약품 데이터를 리스트로 가져오기
|
||||||
|
raw_items = items_result.fetchall()
|
||||||
|
drug_codes = [item.DrugCode for item in raw_items]
|
||||||
|
|
||||||
|
# 약품명 + 성분명 + 분류(PRINT_TYPE) 일괄 조회
|
||||||
|
drug_info_map = {}
|
||||||
|
if drug_codes:
|
||||||
|
placeholders = ','.join([f"'{dc}'" for dc in drug_codes])
|
||||||
|
name_result = drug_session.execute(text(f"""
|
||||||
|
SELECT g.DrugCode, g.GoodsName, s.SUNG_HNM, m.PRINT_TYPE
|
||||||
|
FROM CD_GOODS g
|
||||||
|
LEFT JOIN CD_SUNG s ON g.SUNG_CODE = s.SUNG_CODE
|
||||||
|
LEFT JOIN CD_MC m ON g.DrugCode = m.DRUGCODE
|
||||||
|
WHERE g.DrugCode IN ({placeholders})
|
||||||
|
"""))
|
||||||
|
for row in name_result.fetchall():
|
||||||
|
drug_info_map[row[0]] = {
|
||||||
|
'name': row[1],
|
||||||
|
'ingredient': row[2] or '',
|
||||||
|
'category': row[3] or '' # 분류 (알러지질환약 등)
|
||||||
|
}
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for item in raw_items:
|
||||||
|
info = drug_info_map.get(item.DrugCode, {})
|
||||||
|
drug_name = info.get('name', item.DrugCode)
|
||||||
|
ingredient = info.get('ingredient', '')
|
||||||
|
category = info.get('category', '') # 분류 (알러지질환약 등)
|
||||||
|
|
||||||
|
quantity = int(item.quantity) if item.quantity else 1
|
||||||
|
times = int(item.times) if item.times else 1
|
||||||
|
days = int(item.days) if item.days else 1
|
||||||
|
|
||||||
|
items.append({
|
||||||
|
'drug_code': item.DrugCode,
|
||||||
|
'drug_name': drug_name,
|
||||||
|
'category': category, # 분류 추가
|
||||||
|
'quantity': quantity,
|
||||||
|
'times': times,
|
||||||
|
'days': days,
|
||||||
|
'total_dose': quantity * times * days
|
||||||
|
})
|
||||||
|
|
||||||
|
prescriptions.append({
|
||||||
|
'pre_serial': rx.PreSerial,
|
||||||
|
'rx_date': rx_date,
|
||||||
|
'doctor_name': rx.doctor_name or '',
|
||||||
|
'hospital_name': rx.hospital_name or '',
|
||||||
|
'items': items
|
||||||
|
})
|
||||||
|
|
||||||
|
# 환자명
|
||||||
|
patient_name = prescriptions[0]['items'][0]['drug_name'] if prescriptions else ''
|
||||||
|
if prescriptions and pres_session.execute(text("""
|
||||||
|
SELECT TOP 1 Paname FROM PS_main WHERE CusCode = :cus_code
|
||||||
|
"""), {'cus_code': cus_code}).fetchone():
|
||||||
|
patient_name = pres_session.execute(text("""
|
||||||
|
SELECT TOP 1 Paname FROM PS_main WHERE CusCode = :cus_code
|
||||||
|
"""), {'cus_code': cus_code}).fetchone()[0]
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'cus_code': cus_code,
|
||||||
|
'patient_name': patient_name,
|
||||||
|
'prescription_count': len(prescriptions),
|
||||||
|
'prescriptions': prescriptions
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"환자 처방 조회 오류: {e}")
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
# ==================== 위치 정보 API ====================
|
# ==================== 위치 정보 API ====================
|
||||||
|
|
||||||
@app.route('/api/locations')
|
@app.route('/api/locations')
|
||||||
|
|||||||
@ -712,6 +712,57 @@
|
|||||||
.purchase-modal-btn:hover {
|
.purchase-modal-btn:hover {
|
||||||
background: #e2e8f0;
|
background: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 제품명 링크 스타일 ── */
|
||||||
|
.product-name-link {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.15s;
|
||||||
|
}
|
||||||
|
.product-name-link:hover {
|
||||||
|
color: #8b5cf6;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 환자명 링크 스타일 ── */
|
||||||
|
.patient-name-link {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #1e293b;
|
||||||
|
transition: all 0.15s;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.patient-name-link:hover {
|
||||||
|
color: #8b5cf6;
|
||||||
|
background: #f3e8ff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 페이지네이션 버튼 ── */
|
||||||
|
.pagination-btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fff;
|
||||||
|
color: #475569;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.pagination-btn:hover:not(:disabled) {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
.pagination-btn.active {
|
||||||
|
background: #10b981;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #10b981;
|
||||||
|
}
|
||||||
|
.pagination-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
tbody tr {
|
tbody tr {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -1151,6 +1202,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 사용이력 모달 -->
|
||||||
|
<!-- 환자 최근 처방 모달 (z-index 더 높게 - 제품 모달 위에 표시) -->
|
||||||
|
<div class="purchase-modal" id="patientPrescriptionsModal" style="z-index: 2100;" onclick="if(event.target===this)closePatientPrescriptionsModal()">
|
||||||
|
<div class="purchase-modal-content" style="max-width: 850px;">
|
||||||
|
<div class="purchase-modal-header" style="background: linear-gradient(135deg, #8b5cf6, #7c3aed);">
|
||||||
|
<h3>📋 환자 최근 처방</h3>
|
||||||
|
<div class="drug-name" id="patientPrescriptionsName">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="purchase-modal-body" id="patientPrescriptionsBody" style="max-height: 500px; overflow-y: auto;">
|
||||||
|
<div class="purchase-empty"><div class="icon">📭</div><p>로딩 중...</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="purchase-modal-footer">
|
||||||
|
<button class="purchase-modal-btn" onclick="closePatientPrescriptionsModal()">닫기</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="purchase-modal" id="usageHistoryModal" onclick="if(event.target===this)closeUsageHistoryModal()">
|
||||||
|
<div class="purchase-modal-content" style="max-width: 700px;">
|
||||||
|
<div class="purchase-modal-header" style="background: linear-gradient(135deg, #10b981, #059669);">
|
||||||
|
<h3>💊 처방 사용이력</h3>
|
||||||
|
<div class="drug-name" id="usageHistoryDrugName">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="purchase-modal-body">
|
||||||
|
<table class="purchase-history-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>환자명</th>
|
||||||
|
<th>처방일</th>
|
||||||
|
<th>수량</th>
|
||||||
|
<th>횟수</th>
|
||||||
|
<th>일수</th>
|
||||||
|
<th>총투약량</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="usageHistoryBody">
|
||||||
|
<tr><td colspan="6" class="purchase-empty"><div class="icon">📭</div><p>로딩 중...</p></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="purchase-modal-footer" style="flex-direction: column; gap: 12px;">
|
||||||
|
<div id="usageHistoryPagination" style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center;"></div>
|
||||||
|
<button class="purchase-modal-btn" onclick="closeUsageHistoryModal()">닫기</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let productsData = [];
|
let productsData = [];
|
||||||
let selectedItem = null;
|
let selectedItem = null;
|
||||||
@ -1236,7 +1334,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="product-name">
|
<div class="product-name">
|
||||||
${escapeHtml(item.product_name)}
|
<span class="product-name-link" onclick="event.stopPropagation();openUsageHistoryModal('${item.drug_code}', '${escapeHtml(item.product_name).replace(/'/g, "\\'")}')" title="클릭하여 사용이력 보기">${escapeHtml(item.product_name)}</span>
|
||||||
${item.is_animal_drug ? `<span class="animal-badge ${item.apc ? 'clickable' : ''}" ${item.apc ? `onclick="event.stopPropagation();printAnimalDrugInfo('${item.apc}', '${escapeHtml(item.product_name)}')" title="클릭하면 약품 안내서 인쇄"` : 'title="APC 없음"'}>🐾 동물약</span>` : ''}
|
${item.is_animal_drug ? `<span class="animal-badge ${item.apc ? 'clickable' : ''}" ${item.apc ? `onclick="event.stopPropagation();printAnimalDrugInfo('${item.apc}', '${escapeHtml(item.product_name)}')" title="클릭하면 약품 안내서 인쇄"` : 'title="APC 없음"'}>🐾 동물약</span>` : ''}
|
||||||
${categoryBadge}
|
${categoryBadge}
|
||||||
</div>
|
</div>
|
||||||
@ -2155,6 +2253,177 @@
|
|||||||
function closePurchaseModal() {
|
function closePurchaseModal() {
|
||||||
document.getElementById('purchaseModal').classList.remove('show');
|
document.getElementById('purchaseModal').classList.remove('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════
|
||||||
|
// 사용이력 모달
|
||||||
|
// ══════════════════════════════════════════════════════════════════
|
||||||
|
let currentUsageDrugCode = '';
|
||||||
|
let currentUsageDrugName = '';
|
||||||
|
let currentUsagePage = 1;
|
||||||
|
|
||||||
|
async function openUsageHistoryModal(drugCode, drugName) {
|
||||||
|
currentUsageDrugCode = drugCode;
|
||||||
|
currentUsageDrugName = drugName;
|
||||||
|
currentUsagePage = 1;
|
||||||
|
|
||||||
|
const modal = document.getElementById('usageHistoryModal');
|
||||||
|
const nameEl = document.getElementById('usageHistoryDrugName');
|
||||||
|
|
||||||
|
nameEl.textContent = drugName || drugCode;
|
||||||
|
modal.classList.add('show');
|
||||||
|
|
||||||
|
await loadUsageHistoryPage(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadUsageHistoryPage(page) {
|
||||||
|
currentUsagePage = page;
|
||||||
|
const tbody = document.getElementById('usageHistoryBody');
|
||||||
|
const pagination = document.getElementById('usageHistoryPagination');
|
||||||
|
|
||||||
|
tbody.innerHTML = '<tr><td colspan="5" class="purchase-empty"><div class="icon">⏳</div><p>사용이력 조회 중...</p></td></tr>';
|
||||||
|
pagination.innerHTML = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/products/${currentUsageDrugCode}/usage-history?page=${page}&per_page=20&months=12`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
if (data.items.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="6" class="purchase-empty"><div class="icon">📭</div><p>최근 12개월간 사용이력이 없습니다</p></td></tr>';
|
||||||
|
} else {
|
||||||
|
tbody.innerHTML = data.items.map(item => `
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="patient-name-link" onclick="event.stopPropagation();openPatientPrescriptionsModal('${item.cus_code}', '${escapeHtml(item.patient_name).replace(/'/g, "\\'")}')" title="클릭하여 최근 처방 보기">${escapeHtml(item.patient_name)}</span>
|
||||||
|
</td>
|
||||||
|
<td class="purchase-date">${item.rx_date}</td>
|
||||||
|
<td style="text-align: center;">${item.quantity}</td>
|
||||||
|
<td style="text-align: center;">${item.times}</td>
|
||||||
|
<td style="text-align: center;">${item.days}</td>
|
||||||
|
<td style="text-align: center; font-weight: 600; color: #10b981;">${item.total_dose}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 페이지네이션 렌더링
|
||||||
|
renderUsageHistoryPagination(data.pagination);
|
||||||
|
} else {
|
||||||
|
tbody.innerHTML = `<tr><td colspan="6" class="purchase-empty"><div class="icon">⚠️</div><p>조회 실패: ${data.error}</p></td></tr>`;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
tbody.innerHTML = `<tr><td colspan="6" class="purchase-empty"><div class="icon">❌</div><p>오류: ${err.message}</p></td></tr>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUsageHistoryPagination(pg) {
|
||||||
|
const pagination = document.getElementById('usageHistoryPagination');
|
||||||
|
if (pg.total_pages <= 1) {
|
||||||
|
pagination.innerHTML = `<span style="color: #64748b; font-size: 13px;">총 ${pg.total_count}건</span>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
// 이전 버튼
|
||||||
|
html += `<button class="pagination-btn" ${pg.page <= 1 ? 'disabled' : ''} onclick="loadUsageHistoryPage(${pg.page - 1})">◀ 이전</button>`;
|
||||||
|
|
||||||
|
// 페이지 번호들
|
||||||
|
const maxPages = 5;
|
||||||
|
let startPage = Math.max(1, pg.page - Math.floor(maxPages / 2));
|
||||||
|
let endPage = Math.min(pg.total_pages, startPage + maxPages - 1);
|
||||||
|
if (endPage - startPage < maxPages - 1) {
|
||||||
|
startPage = Math.max(1, endPage - maxPages + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startPage > 1) {
|
||||||
|
html += `<button class="pagination-btn" onclick="loadUsageHistoryPage(1)">1</button>`;
|
||||||
|
if (startPage > 2) html += `<span style="color: #94a3b8;">...</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
html += `<button class="pagination-btn ${i === pg.page ? 'active' : ''}" onclick="loadUsageHistoryPage(${i})">${i}</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endPage < pg.total_pages) {
|
||||||
|
if (endPage < pg.total_pages - 1) html += `<span style="color: #94a3b8;">...</span>`;
|
||||||
|
html += `<button class="pagination-btn" onclick="loadUsageHistoryPage(${pg.total_pages})">${pg.total_pages}</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 다음 버튼
|
||||||
|
html += `<button class="pagination-btn" ${pg.page >= pg.total_pages ? 'disabled' : ''} onclick="loadUsageHistoryPage(${pg.page + 1})">다음 ▶</button>`;
|
||||||
|
|
||||||
|
// 총 건수
|
||||||
|
html += `<span style="color: #64748b; font-size: 13px; margin-left: 12px;">총 ${pg.total_count}건</span>`;
|
||||||
|
|
||||||
|
pagination.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeUsageHistoryModal() {
|
||||||
|
document.getElementById('usageHistoryModal').classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 환자 최근 처방 모달
|
||||||
|
async function openPatientPrescriptionsModal(cusCode, patientName) {
|
||||||
|
const modal = document.getElementById('patientPrescriptionsModal');
|
||||||
|
const nameEl = document.getElementById('patientPrescriptionsName');
|
||||||
|
const bodyEl = document.getElementById('patientPrescriptionsBody');
|
||||||
|
|
||||||
|
nameEl.textContent = patientName || cusCode;
|
||||||
|
bodyEl.innerHTML = '<div class="purchase-empty"><div class="icon">⏳</div><p>처방 내역 조회 중...</p></div>';
|
||||||
|
modal.classList.add('show');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/patients/${cusCode}/recent-prescriptions`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success && data.prescriptions.length > 0) {
|
||||||
|
let html = '';
|
||||||
|
data.prescriptions.forEach(rx => {
|
||||||
|
html += `
|
||||||
|
<div class="rx-card" style="margin-bottom: 16px; padding: 16px; background: #f8fafc; border-radius: 12px; border-left: 4px solid #8b5cf6;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||||
|
<div>
|
||||||
|
<span style="font-weight: 600; color: #1e293b; font-size: 15px;">📅 ${rx.rx_date}</span>
|
||||||
|
<span style="color: #64748b; font-size: 13px; margin-left: 12px;">${escapeHtml(rx.hospital_name || '')}</span>
|
||||||
|
</div>
|
||||||
|
<span style="color: #8b5cf6; font-size: 13px; font-weight: 500;">${escapeHtml(rx.doctor_name || '')}</span>
|
||||||
|
</div>
|
||||||
|
<table style="width: 100%; border-collapse: collapse; font-size: 13px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background: #e2e8f0;">
|
||||||
|
<th style="padding: 8px; text-align: left; border-radius: 6px 0 0 6px;">약품명</th>
|
||||||
|
<th style="padding: 8px; text-align: center; width: 80px;">용법</th>
|
||||||
|
<th style="padding: 8px; text-align: center; width: 60px; border-radius: 0 6px 6px 0;">총량</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${rx.items.map(item => `
|
||||||
|
<tr style="border-bottom: 1px solid #e2e8f0;">
|
||||||
|
<td style="padding: 8px;">
|
||||||
|
<div style="color: #334155; font-weight: 500;">${escapeHtml(item.drug_name)}</div>
|
||||||
|
${item.category ? `<div style="font-size: 11px; color: #8b5cf6; margin-top: 2px;">${escapeHtml(item.category)}</div>` : ''}
|
||||||
|
</td>
|
||||||
|
<td style="padding: 8px; text-align: center; color: #475569; font-size: 12px;">${item.quantity}×${item.times}×${item.days}</td>
|
||||||
|
<td style="padding: 8px; text-align: center; font-weight: 600; color: #8b5cf6;">${item.total_dose}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
bodyEl.innerHTML = html;
|
||||||
|
} else {
|
||||||
|
bodyEl.innerHTML = '<div class="purchase-empty"><div class="icon">📭</div><p>최근 6개월간 처방 내역이 없습니다</p></div>';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
bodyEl.innerHTML = `<div class="purchase-empty"><div class="icon">❌</div><p>오류: ${err.message}</p></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePatientPrescriptionsModal() {
|
||||||
|
document.getElementById('patientPrescriptionsModal').classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
function copyToClipboard(text) {
|
function copyToClipboard(text) {
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user