diff --git a/backend/app.py b/backend/app.py index 433e166..0cec185 100644 --- a/backend/app.py +++ b/backend/app.py @@ -456,6 +456,145 @@ def admin_transaction_detail(transaction_id): }), 500 +@app.route('/admin/user/') +def admin_user_detail(user_id): + """사용자 상세 이력 조회 - 구매 이력, 적립 이력, 구매 품목""" + try: + # 1. SQLite 연결 + conn = db_manager.get_sqlite_connection() + cursor = conn.cursor() + + # 2. 사용자 기본 정보 조회 + cursor.execute(""" + SELECT id, nickname, phone, mileage_balance, created_at + FROM users WHERE id = ? + """, (user_id,)) + user = cursor.fetchone() + + if not user: + return jsonify({ + 'success': False, + 'message': '사용자를 찾을 수 없습니다.' + }), 404 + + # 3. 마일리지 이력 조회 (최근 50건) + cursor.execute(""" + SELECT transaction_id, points, balance_after, reason, description, created_at + FROM mileage_ledger + WHERE user_id = ? + ORDER BY created_at DESC + LIMIT 50 + """, (user_id,)) + mileage_history = cursor.fetchall() + + # 4. 구매 이력 조회 (적립된 거래만, 최근 20건) + cursor.execute(""" + SELECT transaction_id, total_amount, claimable_points, claimed_at + FROM claim_tokens + WHERE claimed_by_user_id = ? + ORDER BY claimed_at DESC + LIMIT 20 + """, (user_id,)) + claimed_tokens = cursor.fetchall() + + # 5. 각 거래의 상품 상세 조회 (MSSQL) + purchases = [] + + try: + session = db_manager.get_session('PM_PRES') + + for token in claimed_tokens: + transaction_id = token['transaction_id'] + + # SALE_SUB + CD_GOODS JOIN + sale_items_query = text(""" + SELECT + S.DrugCode, + ISNULL(G.GoodsName, '(약품명 없음)') AS goods_name, + S.SL_NM_item AS quantity, + S.SL_NM_cost_a AS price, + S.SL_TOTAL_PRICE AS total + FROM SALE_SUB S + LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode + WHERE S.SL_NO_order = :transaction_id + ORDER BY S.DrugCode + """) + + items_raw = session.execute( + sale_items_query, + {'transaction_id': transaction_id} + ).fetchall() + + # 상품 리스트 변환 + items = [ + { + 'code': item.DrugCode, + 'name': item.goods_name, + 'qty': int(item.quantity or 0), + 'price': int(item.price or 0), + 'total': int(item.total or 0) + } + for item in items_raw + ] + + # 상품 요약 생성 ("첫번째상품명 외 N개") + if items: + first_item_name = items[0]['name'] + items_count = len(items) + if items_count == 1: + items_summary = first_item_name + else: + items_summary = f"{first_item_name} 외 {items_count - 1}개" + else: + items_summary = "상품 정보 없음" + items_count = 0 + + purchases.append({ + 'transaction_id': transaction_id, + 'date': str(token['claimed_at'])[:16].replace('T', ' '), + 'amount': int(token['total_amount']), + 'points': int(token['claimable_points']), + 'items_summary': items_summary, + 'items_count': items_count, + 'items': items + }) + + except Exception as mssql_error: + # MSSQL 연결 실패 시 빈 배열 반환 + print(f"[WARNING] MSSQL 조회 실패 (user {user_id}): {mssql_error}") + purchases = [] + + # 6. 응답 생성 + return jsonify({ + 'success': True, + 'user': { + 'id': user['id'], + 'name': user['nickname'], + 'phone': user['phone'], + 'balance': user['mileage_balance'], + 'created_at': str(user['created_at'])[:16].replace('T', ' ') + }, + 'mileage_history': [ + { + 'points': ml['points'], + 'balance_after': ml['balance_after'], + 'reason': ml['reason'], + 'description': ml['description'], + 'created_at': str(ml['created_at'])[:16].replace('T', ' '), + 'transaction_id': ml['transaction_id'] + } + for ml in mileage_history + ], + 'purchases': purchases + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'message': f'조회 실패: {str(e)}' + }), 500 + + @app.route('/admin') def admin(): """관리자 페이지 - 전체 사용자 및 적립 현황""" diff --git a/backend/templates/admin.html b/backend/templates/admin.html index 8f6fd1a..74b26b3 100644 --- a/backend/templates/admin.html +++ b/backend/templates/admin.html @@ -176,6 +176,31 @@ padding: 10px 12px; } } + + /* 아코디언 스타일 */ + .accordion-content { + transition: max-height 0.3s ease-out; + } + + /* 탭 스타일 */ + .tab-btn { + transition: all 0.2s ease; + } + + .tab-btn:hover { + color: #6366f1 !important; + } + + /* 사용자 테이블 행 호버 */ + .section table tbody tr { + transition: all 0.2s ease; + } + + .section table tbody tr:hover { + background: #f8f9fa; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + } @@ -230,9 +255,9 @@ {% for user in recent_users %} - + {{ user.id }} - {{ user.nickname }} + {{ user.nickname }} {{ user.phone[:3] }}-{{ user.phone[3:7] }}-{{ user.phone[7:] if user.phone|length > 7 else '' }} {{ "{:,}".format(user.mileage_balance) }}P {{ user.created_at[:16].replace('T', ' ') }} @@ -342,6 +367,21 @@ + + +