feat(반품관리): 약품 위치 컬럼 추가
- API: return-candidates에서 CD_item_position 조인하여 위치 정보 반환 - 테이블에 '위치' 컬럼 추가 (노란색 뱃지 스타일) - 위치 미지정 약품은 '-' 표시
This commit is contained in:
parent
c71c9ad678
commit
71d1916efb
@ -5360,8 +5360,9 @@ def api_return_candidates():
|
||||
# 약품코드 목록 추출
|
||||
drug_codes = [row['drug_code'] for row in rows]
|
||||
|
||||
# MSSQL에서 가격 정보 조회 (한 번에)
|
||||
# MSSQL에서 가격 + 위치 정보 조회 (한 번에)
|
||||
price_map = {}
|
||||
location_map = {}
|
||||
if drug_codes:
|
||||
try:
|
||||
mssql_conn_str = (
|
||||
@ -5378,20 +5379,24 @@ def api_return_candidates():
|
||||
|
||||
# IN 절 생성 (SQL Injection 방지를 위해 파라미터화)
|
||||
# Price가 없으면 Saleprice, Topprice 순으로 fallback
|
||||
# CD_item_position JOIN으로 위치 정보도 함께 조회
|
||||
placeholders = ','.join(['?' for _ in drug_codes])
|
||||
mssql_cursor.execute(f"""
|
||||
SELECT DrugCode,
|
||||
COALESCE(NULLIF(Price, 0), NULLIF(Saleprice, 0), NULLIF(Topprice, 0), 0) as BestPrice
|
||||
FROM CD_GOODS
|
||||
WHERE DrugCode IN ({placeholders})
|
||||
SELECT G.DrugCode,
|
||||
COALESCE(NULLIF(G.Price, 0), NULLIF(G.Saleprice, 0), NULLIF(G.Topprice, 0), 0) as BestPrice,
|
||||
ISNULL(POS.CD_NM_sale, '') as Location
|
||||
FROM CD_GOODS G
|
||||
LEFT JOIN CD_item_position POS ON G.DrugCode = POS.DrugCode
|
||||
WHERE G.DrugCode IN ({placeholders})
|
||||
""", drug_codes)
|
||||
|
||||
for row in mssql_cursor.fetchall():
|
||||
price_map[row[0]] = float(row[1]) if row[1] else 0
|
||||
location_map[row[0]] = row[2] or ''
|
||||
|
||||
mssql_conn.close()
|
||||
except Exception as e:
|
||||
logging.warning(f"MSSQL 가격 조회 실패: {e}")
|
||||
logging.warning(f"MSSQL 가격/위치 조회 실패: {e}")
|
||||
|
||||
# 전체 데이터 조회 (통계용)
|
||||
cursor.execute("SELECT drug_code, current_stock, months_since_use, months_since_purchase FROM return_candidates")
|
||||
@ -5432,6 +5437,7 @@ def api_return_candidates():
|
||||
'current_stock': stock,
|
||||
'unit_price': price,
|
||||
'recoverable_amount': recoverable,
|
||||
'location': location_map.get(code, ''),
|
||||
'last_prescription_date': row['last_prescription_date'],
|
||||
'months_since_use': row['months_since_use'],
|
||||
'last_purchase_date': row['last_purchase_date'],
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>반품 관리 - 청춘약국</title>
|
||||
<title>반품 ê´€ë¦?- ì²?¶˜?½êµ</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;600&display=swap" rel="stylesheet">
|
||||
@ -38,7 +38,7 @@
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ══════════════════ 헤더 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> ?¤ë<C2A4>” ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #dc2626 0%, #f97316 50%, #f59e0b 100%);
|
||||
padding: 20px 24px;
|
||||
@ -89,14 +89,14 @@
|
||||
background: rgba(255,255,255,0.25);
|
||||
}
|
||||
|
||||
/* ══════════════════ 컨텐츠 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> 컨í…<C3AD>ì¸??<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.content {
|
||||
max-width: 1800px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* ══════════════════ 통계 카드 (2줄) ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> ?µê³„ 카드 (2ì¤? ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.stats-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
@ -162,7 +162,7 @@
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ══════════════════ 필터 바 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> ?„í„° ë°??<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.filter-bar {
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
@ -218,7 +218,7 @@
|
||||
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4);
|
||||
}
|
||||
|
||||
/* ══════════════════ 긴급도 탭 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> 긴급?????<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.urgency-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@ -256,7 +256,7 @@
|
||||
background: rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
/* ══════════════════ 테이블 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> ?Œì<C592>´ë¸??<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.table-wrap {
|
||||
background: var(--bg-card);
|
||||
border-radius: 16px;
|
||||
@ -299,7 +299,7 @@
|
||||
background: rgba(249, 115, 22, 0.05);
|
||||
}
|
||||
|
||||
/* 약품 셀 */
|
||||
/* ?½í’ˆ ?€ */
|
||||
.drug-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -316,7 +316,26 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 긴급도 배지 */
|
||||
/* ?„치 ?€ */
|
||||
.location-cell {
|
||||
text-align: center;
|
||||
}
|
||||
.location-badge {
|
||||
display: inline-block;
|
||||
background: rgba(251, 191, 36, 0.2);
|
||||
color: var(--accent-amber);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.location-empty {
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 긴급??ë°°ì? */
|
||||
.urgency-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -339,7 +358,7 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 상태 배지 */
|
||||
/* ?<3F>태 ë°°ì? */
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
@ -354,7 +373,7 @@
|
||||
.status-badge.disposed { background: rgba(244, 63, 94, 0.2); color: var(--accent-rose); }
|
||||
.status-badge.resolved { background: rgba(100, 116, 139, 0.2); color: var(--text-muted); }
|
||||
|
||||
/* 수량/날짜/금액 셀 */
|
||||
/* ?˜ëŸ‰/? ì§œ/금액 ?€ */
|
||||
.qty-cell {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-weight: 600;
|
||||
@ -385,7 +404,7 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 액션 버튼 */
|
||||
/* ?¡ì…˜ 버튼 */
|
||||
.action-btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
@ -404,7 +423,7 @@
|
||||
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4);
|
||||
}
|
||||
|
||||
/* ══════════════════ 페이지네이션 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> ?˜ì<CB9C>´ì§€?¤ì<C2A4>´???<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -428,7 +447,7 @@
|
||||
.page-btn:disabled { opacity: 0.3; cursor: not-allowed; }
|
||||
.page-info { color: var(--text-muted); font-size: 13px; }
|
||||
|
||||
/* ══════════════════ 모달 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> 모달 ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@ -496,7 +515,7 @@
|
||||
.drug-detail-label { color: var(--text-muted); }
|
||||
.drug-detail-value { font-weight: 600; font-family: 'JetBrains Mono', monospace; }
|
||||
|
||||
/* ══════════════════ 로딩/빈 상태 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> 로딩/ë¹??<3F>태 ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.loading-state, .empty-state { text-align: center; padding: 60px 20px; color: var(--text-muted); }
|
||||
.loading-spinner {
|
||||
width: 40px; height: 40px;
|
||||
@ -509,7 +528,7 @@
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.empty-icon { font-size: 48px; margin-bottom: 16px; }
|
||||
|
||||
/* ══════════════════ 토스트 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> ? 스???<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
@ -531,7 +550,7 @@
|
||||
.toast.success { border-color: var(--accent-emerald); }
|
||||
.toast.error { border-color: var(--accent-rose); }
|
||||
|
||||
/* ══════════════════ 반응형 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> ë°˜ì<CB9C>‘???<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
@media (max-width: 1200px) {
|
||||
.stats-row { flex-wrap: wrap; }
|
||||
.stat-card { min-width: calc(33% - 12px); }
|
||||
@ -545,7 +564,7 @@
|
||||
.urgency-tabs { flex-wrap: wrap; }
|
||||
}
|
||||
|
||||
/* ══════════════════ 입고이력 모달 ══════════════════ */
|
||||
/* ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> ?…ê³ ?´ë ¥ 모달 ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> */
|
||||
.purchase-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@ -669,7 +688,7 @@
|
||||
background: var(--bg-card-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
/* 테이블 행 더블클릭 가능 표시 */
|
||||
/* ?Œì<C592>´ë¸????”블?´ë¦ ê°€???œì‹œ */
|
||||
#dataTableBody tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -682,154 +701,155 @@
|
||||
<div class="header">
|
||||
<div class="header-inner">
|
||||
<div class="header-left">
|
||||
<h1>📦 반품 후보 관리</h1>
|
||||
<p>장기 미사용 의약품 반품/폐기 관리</p>
|
||||
<h1>?“¦ 반품 ?„ë³´ ê´€ë¦?/h1>
|
||||
<p>?¥ê¸° 미사???˜ì•½??반품/?<3F>기 ê´€ë¦?/p>
|
||||
</div>
|
||||
<nav class="header-nav">
|
||||
<a href="/admin">📊 대시보드</a>
|
||||
<a href="/admin/rx-usage">💊 Rx 사용량</a>
|
||||
<a href="/admin/return-management" class="active">📦 반품관리</a>
|
||||
<a href="/admin">?“Š ?€?œë³´??/a>
|
||||
<a href="/admin/rx-usage">?’Š Rx ?¬ìš©??/a>
|
||||
<a href="/admin/return-management" class="active">?“¦ 반품관ë¦?/a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 통계 카드 1줄: 건수 -->
|
||||
<!-- ?µê³„ 카드 1ì¤? 건수 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-card rose">
|
||||
<div class="stat-icon">🔴</div>
|
||||
<div class="stat-icon">?”´</div>
|
||||
<div class="stat-value" id="statCritical">-</div>
|
||||
<div class="stat-label">3년+ 미사용</div>
|
||||
<div class="stat-sub">긴급 처리 필요</div>
|
||||
<div class="stat-label">3?? 미사??/div>
|
||||
<div class="stat-sub">긴급 처리 ?„ìš”</div>
|
||||
</div>
|
||||
<div class="stat-card amber">
|
||||
<div class="stat-icon">🟠</div>
|
||||
<div class="stat-icon">?Ÿ </div>
|
||||
<div class="stat-value" id="statWarning">-</div>
|
||||
<div class="stat-label">2년+ 미사용</div>
|
||||
<div class="stat-sub">검토 권장</div>
|
||||
<div class="stat-label">2?? 미사??/div>
|
||||
<div class="stat-sub">검??권장</div>
|
||||
</div>
|
||||
<div class="stat-card cyan">
|
||||
<div class="stat-icon">📋</div>
|
||||
<div class="stat-icon">?“‹</div>
|
||||
<div class="stat-value" id="statPending">-</div>
|
||||
<div class="stat-label">미결정</div>
|
||||
<div class="stat-sub">pending 상태</div>
|
||||
<div class="stat-label">미결??/div>
|
||||
<div class="stat-sub">pending ?<3F>태</div>
|
||||
</div>
|
||||
<div class="stat-card emerald">
|
||||
<div class="stat-icon">✅</div>
|
||||
<div class="stat-icon">??/div>
|
||||
<div class="stat-value" id="statProcessed">-</div>
|
||||
<div class="stat-label">처리완료</div>
|
||||
<div class="stat-label">처리?„료</div>
|
||||
</div>
|
||||
<div class="stat-card purple">
|
||||
<div class="stat-icon">📊</div>
|
||||
<div class="stat-icon">?“Š</div>
|
||||
<div class="stat-value" id="statTotal">-</div>
|
||||
<div class="stat-label">전체 후보</div>
|
||||
<div class="stat-label">?„ì²´ ?„ë³´</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 통계 카드 2줄: 회수가능 금액 -->
|
||||
<!-- ?µê³„ 카드 2ì¤? ?Œìˆ˜ê°€??금액 -->
|
||||
<div class="stats-row amount-row">
|
||||
<div class="stat-card gold">
|
||||
<div class="stat-icon">💰</div>
|
||||
<div class="stat-icon">?’°</div>
|
||||
<div class="stat-value" id="statTotalAmount">-</div>
|
||||
<div class="stat-label">총 회수가능 금액</div>
|
||||
<div class="stat-sub">전체 반품 대상</div>
|
||||
<div class="stat-label">ì´??Œìˆ˜ê°€??금액</div>
|
||||
<div class="stat-sub">?„ì²´ 반품 ?€??/div>
|
||||
</div>
|
||||
<div class="stat-card rose">
|
||||
<div class="stat-icon">🔴💵</div>
|
||||
<div class="stat-icon">?”´?’µ</div>
|
||||
<div class="stat-value small" id="statCriticalAmount">-</div>
|
||||
<div class="stat-label">3년+ 금액</div>
|
||||
<div class="stat-sub">긴급 회수 대상</div>
|
||||
<div class="stat-label">3?? 금액</div>
|
||||
<div class="stat-sub">긴급 ?Œìˆ˜ ?€??/div>
|
||||
</div>
|
||||
<div class="stat-card amber">
|
||||
<div class="stat-icon">🟠💵</div>
|
||||
<div class="stat-icon">?Ÿ ?’µ</div>
|
||||
<div class="stat-value small" id="statWarningAmount">-</div>
|
||||
<div class="stat-label">2년+ 금액</div>
|
||||
<div class="stat-sub">주의 대상</div>
|
||||
<div class="stat-label">2?? 금액</div>
|
||||
<div class="stat-sub">주ì<EFBFBD>˜ ?€??/div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 필터 바 -->
|
||||
<!-- ?„í„° ë°?-->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-group">
|
||||
<label>상태</label>
|
||||
<label>?<3F>태</label>
|
||||
<select id="filterStatus">
|
||||
<option value="">전체</option>
|
||||
<option value="pending" selected>미결정 (pending)</option>
|
||||
<option value="reviewed">검토됨</option>
|
||||
<option value="returned">반품완료</option>
|
||||
<option value="">?„ì²´</option>
|
||||
<option value="pending" selected>미결??(pending)</option>
|
||||
<option value="reviewed">ê²€? ë<C2A0>¨</option>
|
||||
<option value="returned">반품?„료</option>
|
||||
<option value="keep">보류</option>
|
||||
<option value="disposed">폐기</option>
|
||||
<option value="resolved">재고소진</option>
|
||||
<option value="disposed">?<3F>기</option>
|
||||
<option value="resolved">?¬ê³ ?Œì§„</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>긴급도</label>
|
||||
<label>긴급??/label>
|
||||
<select id="filterUrgency">
|
||||
<option value="">전체</option>
|
||||
<option value="critical">🔴 3년+ (긴급)</option>
|
||||
<option value="warning">🟠 2년+ (주의)</option>
|
||||
<option value="normal">일반</option>
|
||||
<option value="">?„ì²´</option>
|
||||
<option value="critical">?”´ 3?? (긴급)</option>
|
||||
<option value="warning">?Ÿ 2?? (주ì<C2BC>˜)</option>
|
||||
<option value="normal">?¼ë°˜</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>검색어</label>
|
||||
<input type="text" id="searchInput" placeholder="약품명, 코드...">
|
||||
<label>ê²€?‰ì–´</label>
|
||||
<input type="text" id="searchInput" placeholder="?½í’ˆëª? 코드...">
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>정렬</label>
|
||||
<label>?•ë ¬</label>
|
||||
<select id="sortSelect">
|
||||
<option value="months_desc">미사용기간 긴 순</option>
|
||||
<option value="months_asc">미사용기간 짧은 순</option>
|
||||
<option value="stock_desc">재고량 많은 순</option>
|
||||
<option value="name_asc">약품명순</option>
|
||||
<option value="detected_desc">감지일 최신순</option>
|
||||
<option value="months_desc">미사?©ê¸°ê°?ê¸???/option>
|
||||
<option value="months_asc">미사?©ê¸°ê°?ì§§ì? ??/option>
|
||||
<option value="stock_desc">?¬ê³ ??ë§Žì? ??/option>
|
||||
<option value="name_asc">?½í’ˆëª…순</option>
|
||||
<option value="detected_desc">ê°<EFBFBD>ì???ìµœì‹ ??/option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="filter-btn" onclick="loadData()">🔍 조회</button>
|
||||
<button class="filter-btn" onclick="loadData()">?”<> 조회</button>
|
||||
</div>
|
||||
|
||||
<!-- 긴급도 탭 -->
|
||||
<!-- 긴급????-->
|
||||
<div class="urgency-tabs">
|
||||
<button class="urgency-tab active" data-urgency="" onclick="setUrgencyTab('')">
|
||||
전체 <span class="count" id="tabAll">0</span>
|
||||
?„ì²´ <span class="count" id="tabAll">0</span>
|
||||
</button>
|
||||
<button class="urgency-tab" data-urgency="critical" onclick="setUrgencyTab('critical')">
|
||||
🔴 3년+ <span class="count" id="tabCritical">0</span>
|
||||
?”´ 3?? <span class="count" id="tabCritical">0</span>
|
||||
</button>
|
||||
<button class="urgency-tab" data-urgency="warning" onclick="setUrgencyTab('warning')">
|
||||
🟠 2년+ <span class="count" id="tabWarning">0</span>
|
||||
?Ÿ 2?? <span class="count" id="tabWarning">0</span>
|
||||
</button>
|
||||
<button class="urgency-tab" data-urgency="normal" onclick="setUrgencyTab('normal')">
|
||||
일반 <span class="count" id="tabNormal">0</span>
|
||||
?¼ë°˜ <span class="count" id="tabNormal">0</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 데이터 테이블 -->
|
||||
<!-- ?°ì<C2B0>´???Œì<C592>´ë¸?-->
|
||||
<div style="margin-bottom: 8px; font-size: 12px; color: var(--text-muted);">
|
||||
💡 행 더블클릭 → 입고이력 확인 (도매상 연락처)
|
||||
?’¡ ???”블?´ë¦ ???…ê³ ?´ë ¥ ?•ì<E280A2>¸ (?„매???°ë<C2B0>½ì²?
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:4%">긴급</th>
|
||||
<th style="width:24%">약품</th>
|
||||
<th class="center" style="width:6%">현재고</th>
|
||||
<th class="right" style="width:8%">단가</th>
|
||||
<th class="right" style="width:10%">회수가능금액</th>
|
||||
<th class="center" style="width:10%">마지막 처방</th>
|
||||
<th class="center" style="width:8%">미사용</th>
|
||||
<th class="center" style="width:10%">마지막 입고</th>
|
||||
<th class="center" style="width:7%">상태</th>
|
||||
<th style="width:10%">액션</th>
|
||||
<th style="width:20%">?½í’ˆ</th>
|
||||
<th class="center" style="width:6%">?„치</th>
|
||||
<th class="center" style="width:5%">?„재ê³?/th>
|
||||
<th class="right" style="width:7%">?¨ê?</th>
|
||||
<th class="right" style="width:9%">?Œìˆ˜ê°€?¥ê¸ˆ??/th>
|
||||
<th class="center" style="width:9%">마ì?ë§?처방</th>
|
||||
<th class="center" style="width:7%">미사??/th>
|
||||
<th class="center" style="width:9%">마ì?ë§??…ê³ </th>
|
||||
<th class="center" style="width:6%">?<3F>태</th>
|
||||
<th style="width:9%">?¡ì…˜</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dataTableBody">
|
||||
<tr>
|
||||
<td colspan="10">
|
||||
<td colspan="11">
|
||||
<div class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<div>데이터 로딩 중...</div>
|
||||
<div>?°ì<C2B0>´??로딩 ì¤?..</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -837,70 +857,70 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 페이지네이션 -->
|
||||
<!-- ?˜ì<CB9C>´ì§€?¤ì<C2A4>´??-->
|
||||
<div class="pagination" id="pagination"></div>
|
||||
</div>
|
||||
|
||||
<!-- 상태 변경 모달 -->
|
||||
<!-- ?<3F>태 변�모달 -->
|
||||
<div class="modal-overlay" id="statusModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">📋 상태 변경</h3>
|
||||
<h3 class="modal-title">?“‹ ?<3F>태 ë³€ê²?/h3>
|
||||
<button class="modal-close" onclick="closeModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="drug-detail" id="modalDrugDetail"></div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>변경할 상태</label>
|
||||
<label>ë³€ê²½í• ?<3F>태</label>
|
||||
<select id="modalStatus">
|
||||
<option value="reviewed">검토됨</option>
|
||||
<option value="returned">반품완료</option>
|
||||
<option value="keep">보류 (유지)</option>
|
||||
<option value="disposed">폐기</option>
|
||||
<option value="resolved">재고소진</option>
|
||||
<option value="reviewed">ê²€? ë<C2A0>¨</option>
|
||||
<option value="returned">반품?„료</option>
|
||||
<option value="keep">보류 (? ì?)</option>
|
||||
<option value="disposed">?<3F>기</option>
|
||||
<option value="resolved">?¬ê³ ?Œì§„</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>결정 사유</label>
|
||||
<textarea id="modalReason" placeholder="상태 변경 사유를 입력하세요... (보류 선택 시 필수)"></textarea>
|
||||
<label>ê²°ì • ?¬ìœ </label>
|
||||
<textarea id="modalReason" placeholder="?<3F>태 ë³€ê²??¬ìœ ë¥??…ë ¥?˜ì„¸??.. (보류 ? íƒ<C3AD> ???„수)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="modal-btn cancel" onclick="closeModal()">취소</button>
|
||||
<button class="modal-btn submit" onclick="submitStatusChange()">변경 저장</button>
|
||||
<button class="modal-btn submit" onclick="submitStatusChange()">ë³€ê²??€??/button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 토스트 -->
|
||||
<!-- ? 스??-->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<!-- 입고이력 모달 -->
|
||||
<!-- ?…ê³ ?´ë ¥ 모달 -->
|
||||
<div class="purchase-modal" id="purchaseModal">
|
||||
<div class="purchase-modal-content">
|
||||
<div class="purchase-modal-header">
|
||||
<h3>📦 입고 이력</h3>
|
||||
<h3>?“¦ ?…ê³ ?´ë ¥</h3>
|
||||
<div class="drug-name" id="purchaseDrugName">-</div>
|
||||
</div>
|
||||
<div class="purchase-modal-body">
|
||||
<table class="purchase-history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>도매상</th>
|
||||
<th>입고일</th>
|
||||
<th>수량</th>
|
||||
<th>단가</th>
|
||||
<th>?„매??/th>
|
||||
<th>?…ê³ ??/th>
|
||||
<th>?˜ëŸ‰</th>
|
||||
<th>?¨ê?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="purchaseHistoryBody">
|
||||
<tr><td colspan="4" class="purchase-empty"><div class="icon">📭</div><p>로딩 중...</p></td></tr>
|
||||
<tr><td colspan="4" class="purchase-empty"><div class="icon">?“</div><p>로딩 ì¤?..</p></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="purchase-modal-footer">
|
||||
<button class="purchase-modal-btn" onclick="closePurchaseModal()">닫기</button>
|
||||
<button class="purchase-modal-btn" onclick="closePurchaseModal()">?«ê¸°</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -920,7 +940,7 @@
|
||||
|
||||
async function loadData() {
|
||||
const tbody = document.getElementById('dataTableBody');
|
||||
tbody.innerHTML = `<tr><td colspan="10"><div class="loading-state"><div class="loading-spinner"></div><div>데이터 로딩 중...</div></div></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="11"><div class="loading-state"><div class="loading-spinner"></div><div>?°ì<C2B0>´??로딩 ì¤?..</div></div></td></tr>`;
|
||||
|
||||
const status = document.getElementById('filterStatus').value;
|
||||
const urgency = document.getElementById('filterUrgency').value;
|
||||
@ -944,10 +964,10 @@
|
||||
currentPage = 1;
|
||||
renderTable();
|
||||
} else {
|
||||
tbody.innerHTML = `<tr><td colspan="10"><div class="empty-state"><div class="empty-icon">⚠️</div><div>오류: ${data.error}</div></div></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="11"><div class="empty-state"><div class="empty-icon">? ï¸<C3AF></div><div>?¤ë¥˜: ${data.error}</div></div></td></tr>`;
|
||||
}
|
||||
} catch (err) {
|
||||
tbody.innerHTML = `<tr><td colspan="10"><div class="empty-state"><div class="empty-icon">❌</div><div>데이터 로드 실패</div></div></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="11"><div class="empty-state"><div class="empty-icon">??/div><div>?°ì<C2B0>´??로드 ?¤íŒ¨</div></div></td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -958,23 +978,23 @@
|
||||
document.getElementById('statProcessed').textContent = stats.processed || 0;
|
||||
document.getElementById('statTotal').textContent = stats.total || 0;
|
||||
|
||||
// 금액 표시
|
||||
// 금액 ?œì‹œ
|
||||
document.getElementById('statTotalAmount').textContent = formatAmount(stats.total_amount || 0);
|
||||
document.getElementById('statCriticalAmount').textContent = formatAmount(stats.critical_amount || 0);
|
||||
document.getElementById('statWarningAmount').textContent = formatAmount(stats.warning_amount || 0);
|
||||
}
|
||||
|
||||
function formatAmount(amount) {
|
||||
if (!amount || amount === 0) return '₩0';
|
||||
if (!amount || amount === 0) return '??';
|
||||
if (amount >= 1000000) {
|
||||
return '₩' + (amount / 10000).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + '만';
|
||||
return '?? + (amount / 10000).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + 'ë§?;
|
||||
}
|
||||
return '₩' + Math.round(amount).toLocaleString();
|
||||
return '?? + Math.round(amount).toLocaleString();
|
||||
}
|
||||
|
||||
function formatPrice(price) {
|
||||
if (!price || price === 0) return '-';
|
||||
return '₩' + Math.round(price).toLocaleString();
|
||||
return '?? + Math.round(price).toLocaleString();
|
||||
}
|
||||
|
||||
function updateTabs() {
|
||||
@ -1016,7 +1036,7 @@
|
||||
}
|
||||
|
||||
if (filteredData.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="10"><div class="empty-state"><div class="empty-icon">📦</div><div>해당 조건의 반품 후보가 없습니다</div></div></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="11"><div class="empty-state"><div class="empty-icon">?“¦</div><div>?´ë‹¹ ì¡°ê±´??반품 ?„ë³´ê°€ ?†ìе?ˆë‹¤</div></div></td></tr>`;
|
||||
document.getElementById('pagination').innerHTML = '';
|
||||
return;
|
||||
}
|
||||
@ -1035,7 +1055,7 @@
|
||||
<tr class="${urgencyClass}" ondblclick="openPurchaseModal('${item.drug_code}', '${escapeHtml(item.drug_name).replace(/'/g, "\\'")}')">
|
||||
<td>
|
||||
<span class="urgency-badge ${urgency}">
|
||||
${urgency === 'critical' ? '🔴' : urgency === 'warning' ? '🟠' : '⚪'}
|
||||
${urgency === 'critical' ? '?”´' : urgency === 'warning' ? '?Ÿ ' : '??}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@ -1044,9 +1064,10 @@
|
||||
<span class="drug-code">${item.drug_code}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="location-cell">${item.location ? `<span class="location-badge">${escapeHtml(item.location)}</span>` : '<span class="location-empty">-</span>'}</td>
|
||||
<td class="qty-cell">${item.current_stock || 0}</td>
|
||||
<td class="amount-cell ${item.unit_price ? '' : 'zero'}">${formatPrice(item.unit_price)}</td>
|
||||
<td class="amount-cell ${hasAmount ? '' : 'zero'}">${hasAmount ? '₩' + Math.round(item.recoverable_amount).toLocaleString() : '-'}</td>
|
||||
<td class="amount-cell ${hasAmount ? '' : 'zero'}">${hasAmount ? '?? + Math.round(item.recoverable_amount).toLocaleString() : '-'}</td>
|
||||
<td class="date-cell">${formatDate(item.last_prescription_date) || '-'}</td>
|
||||
<td class="months-cell ${urgency}">
|
||||
${item.months_since_use ? item.months_since_use + '개월' : '-'}
|
||||
@ -1054,7 +1075,7 @@
|
||||
<td class="date-cell">${formatDate(item.last_purchase_date) || '-'}</td>
|
||||
<td><span class="status-badge ${item.status}">${getStatusLabel(item.status)}</span></td>
|
||||
<td>
|
||||
<button class="action-btn primary" onclick="event.stopPropagation();openStatusModal(${item.id})">상태 변경</button>
|
||||
<button class="action-btn primary" onclick="event.stopPropagation();openStatusModal(${item.id})">?<3F>태 변�/button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
@ -1067,12 +1088,12 @@
|
||||
const pagination = document.getElementById('pagination');
|
||||
|
||||
if (totalPages <= 1) {
|
||||
pagination.innerHTML = `<span class="page-info">총 ${totalItems}건</span>`;
|
||||
pagination.innerHTML = `<span class="page-info">ì´?${totalItems}ê±?/span>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `<button class="page-btn" onclick="goToPage(1)" ${currentPage === 1 ? 'disabled' : ''}>«</button>`;
|
||||
html += `<button class="page-btn" onclick="goToPage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}>‹</button>`;
|
||||
html += `<button class="page-btn" onclick="goToPage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}>??/button>`;
|
||||
|
||||
const maxVisible = 5;
|
||||
let startPage = Math.max(1, currentPage - Math.floor(maxVisible / 2));
|
||||
@ -1083,9 +1104,9 @@
|
||||
html += `<button class="page-btn ${i === currentPage ? 'active' : ''}" onclick="goToPage(${i})">${i}</button>`;
|
||||
}
|
||||
|
||||
html += `<button class="page-btn" onclick="goToPage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''}>›</button>`;
|
||||
html += `<button class="page-btn" onclick="goToPage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''}>??/button>`;
|
||||
html += `<button class="page-btn" onclick="goToPage(${totalPages})" ${currentPage === totalPages ? 'disabled' : ''}>»</button>`;
|
||||
html += `<span class="page-info" style="margin-left:12px;">총 ${totalItems}건</span>`;
|
||||
html += `<span class="page-info" style="margin-left:12px;">ì´?${totalItems}ê±?/span>`;
|
||||
|
||||
pagination.innerHTML = html;
|
||||
}
|
||||
@ -1106,27 +1127,27 @@
|
||||
<div class="drug-detail-name">${escapeHtml(item.drug_name)}</div>
|
||||
<div class="drug-detail-info">
|
||||
<div class="drug-detail-item">
|
||||
<span class="drug-detail-label">약품코드</span>
|
||||
<span class="drug-detail-label">?½í’ˆì½”드</span>
|
||||
<span class="drug-detail-value">${item.drug_code}</span>
|
||||
</div>
|
||||
<div class="drug-detail-item">
|
||||
<span class="drug-detail-label">현재고</span>
|
||||
<span class="drug-detail-label">?„재ê³?/span>
|
||||
<span class="drug-detail-value">${item.current_stock || 0}</span>
|
||||
</div>
|
||||
<div class="drug-detail-item">
|
||||
<span class="drug-detail-label">단가</span>
|
||||
<span class="drug-detail-label">?¨ê?</span>
|
||||
<span class="drug-detail-value">${formatPrice(item.unit_price)}</span>
|
||||
</div>
|
||||
<div class="drug-detail-item">
|
||||
<span class="drug-detail-label">회수가능금액</span>
|
||||
<span class="drug-detail-value" style="color:var(--accent-gold)">${hasAmount ? '₩' + Math.round(item.recoverable_amount).toLocaleString() : '-'}</span>
|
||||
<span class="drug-detail-label">?Œìˆ˜ê°€?¥ê¸ˆ??/span>
|
||||
<span class="drug-detail-value" style="color:var(--accent-gold)">${hasAmount ? '?? + Math.round(item.recoverable_amount).toLocaleString() : '-'}</span>
|
||||
</div>
|
||||
<div class="drug-detail-item">
|
||||
<span class="drug-detail-label">마지막 처방</span>
|
||||
<span class="drug-detail-label">마ì?ë§?처방</span>
|
||||
<span class="drug-detail-value">${formatDate(item.last_prescription_date) || '-'}</span>
|
||||
</div>
|
||||
<div class="drug-detail-item">
|
||||
<span class="drug-detail-label">미사용 기간</span>
|
||||
<span class="drug-detail-label">미사??기간</span>
|
||||
<span class="drug-detail-value">${item.months_since_use ? item.months_since_use + '개월' : '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -1149,7 +1170,7 @@
|
||||
const reason = document.getElementById('modalReason').value;
|
||||
|
||||
if (status === 'keep' && !reason.trim()) {
|
||||
showToast('보류 상태에서는 사유 입력이 필수입니다', 'error');
|
||||
showToast('보류 ?<3F>태?<3F>서???¬ìœ ?…ë ¥???„수?…니??, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1163,14 +1184,14 @@
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast('상태가 변경되었습니다', 'success');
|
||||
showToast('?<3F>태가 변경ë<C2BD>˜?ˆìе?ˆë‹¤', 'success');
|
||||
closeModal();
|
||||
loadData();
|
||||
} else {
|
||||
showToast('변경 실패: ' + data.error, 'error');
|
||||
showToast('ë³€ê²??¤íŒ¨: ' + data.error, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast('서버 오류', 'error');
|
||||
showToast('?œë²„ ?¤ë¥˜', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1191,12 +1212,12 @@
|
||||
|
||||
function getStatusLabel(status) {
|
||||
const labels = {
|
||||
'pending': '미결정',
|
||||
'reviewed': '검토됨',
|
||||
'returned': '반품완료',
|
||||
'pending': '미결??,
|
||||
'reviewed': 'ê²€? ë<C2A0>¨',
|
||||
'returned': '반품?„료',
|
||||
'keep': '보류',
|
||||
'disposed': '폐기',
|
||||
'resolved': '재고소진'
|
||||
'disposed': '?<3F>기',
|
||||
'resolved': '?¬ê³ ?Œì§„'
|
||||
};
|
||||
return labels[status] || status;
|
||||
}
|
||||
@ -1208,14 +1229,14 @@
|
||||
setTimeout(() => { toast.classList.remove('show'); }, 3000);
|
||||
}
|
||||
|
||||
// ══════════════════ 입고이력 모달 ══════════════════
|
||||
// ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2> ?…ê³ ?´ë ¥ 모달 ?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>?<3F>â•<C3A2>
|
||||
async function openPurchaseModal(drugCode, drugName) {
|
||||
const modal = document.getElementById('purchaseModal');
|
||||
const nameEl = document.getElementById('purchaseDrugName');
|
||||
const tbody = document.getElementById('purchaseHistoryBody');
|
||||
|
||||
nameEl.textContent = drugName || drugCode;
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="purchase-empty"><div class="icon">⏳</div><p>입고이력 조회 중...</p></td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="purchase-empty"><div class="icon">??/div><p>?…ê³ ?´ë ¥ 조회 ì¤?..</p></td></tr>';
|
||||
modal.classList.add('open');
|
||||
|
||||
try {
|
||||
@ -1224,13 +1245,13 @@
|
||||
|
||||
if (data.success) {
|
||||
if (data.history.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="purchase-empty"><div class="icon">📭</div><p>입고 이력이 없습니다</p></td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="purchase-empty"><div class="icon">?“</div><p>?…ê³ ?´ë ¥???†ìе?ˆë‹¤</p></td></tr>';
|
||||
} else {
|
||||
tbody.innerHTML = data.history.map(h => `
|
||||
<tr>
|
||||
<td>
|
||||
<div class="supplier-name">${escapeHtml(h.supplier)}</div>
|
||||
${h.supplier_tel ? `<div class="supplier-tel" onclick="event.stopPropagation();copyToClipboard('${h.supplier_tel}')" title="클릭하여 복사">📞 ${h.supplier_tel}</div>` : ''}
|
||||
${h.supplier_tel ? `<div class="supplier-tel" onclick="event.stopPropagation();copyToClipboard('${h.supplier_tel}')" title="?´ë¦?˜ì—¬ 복사">?“ž ${h.supplier_tel}</div>` : ''}
|
||||
</td>
|
||||
<td class="purchase-date">${h.date}</td>
|
||||
<td class="purchase-qty">${h.quantity.toLocaleString()}</td>
|
||||
@ -1239,10 +1260,10 @@
|
||||
`).join('');
|
||||
}
|
||||
} else {
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="purchase-empty"><div class="icon">⚠️</div><p>조회 실패: ${data.error}</p></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="purchase-empty"><div class="icon">? ï¸<C3AF></div><p>조회 ?¤íŒ¨: ${data.error}</p></td></tr>`;
|
||||
}
|
||||
} catch (err) {
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="purchase-empty"><div class="icon">❌</div><p>오류: ${err.message}</p></td></tr>`;
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="purchase-empty"><div class="icon">??/div><p>?¤ë¥˜: ${err.message}</p></td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1252,7 +1273,7 @@
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showToast(`📋 ${text} 복사됨`, 'success');
|
||||
showToast(`?“‹ ${text} 복사??, 'success');
|
||||
}).catch(() => {
|
||||
const input = document.createElement('input');
|
||||
input.value = text;
|
||||
@ -1260,11 +1281,11 @@
|
||||
input.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(input);
|
||||
showToast(`📋 ${text} 복사됨`, 'success');
|
||||
showToast(`?“‹ ${text} 복사??, 'success');
|
||||
});
|
||||
}
|
||||
|
||||
// 모달 외부 클릭 시 닫기
|
||||
// 모달 ?¸ë? ?´ë¦ ???«ê¸°
|
||||
document.getElementById('purchaseModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closePurchaseModal();
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user