From cb927d2207c219282ca9decbd1348890d7fc1185 Mon Sep 17 00:00:00 2001 From: thug0bin Date: Wed, 25 Feb 2026 16:37:50 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20=EC=A0=81?= =?UTF-8?q?=EB=A6=BD=EB=82=B4=EC=97=AD=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20?= =?UTF-8?q?=ED=92=88=EB=AA=A9=20=EC=83=81=EC=84=B8=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?+=20=ED=82=A4=EC=98=A4=EC=8A=A4=ED=81=AC=20UI=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 어드민 최근 적립 내역에서 행 클릭 시 MSSQL 품목 상세 모달 표시 - transaction_id가 있는 행만 클릭 가능 (돋보기 아이콘 표시) - 키오스크 품목 목록 표시, 세로 모니터 반응형 레이아웃 추가 Co-Authored-By: Claude Opus 4.6 --- backend/app.py | 29 ++++++++- backend/templates/admin.html | 9 ++- backend/templates/kiosk.html | 110 ++++++++++++++++++++++++++++++++--- 3 files changed, 137 insertions(+), 11 deletions(-) diff --git a/backend/app.py b/backend/app.py index a27c563..d00dfb4 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1791,7 +1791,8 @@ def admin(): ml.balance_after, ml.reason, ml.description, - ml.created_at + ml.created_at, + ml.transaction_id FROM mileage_ledger ml JOIN users u ON ml.user_id = u.id ORDER BY ml.created_at DESC @@ -1912,12 +1913,35 @@ def api_kiosk_trigger(): claimable_points = token_info['claimable_points'] qr_url = token_info['qr_url'] + # MSSQL에서 구매 품목 조회 + sale_items = [] + try: + mssql_session = db_manager.get_session('PM_PRES') + sale_sub_query = text(""" + SELECT + ISNULL(G.GoodsName, '(약품명 없음)') AS goods_name, + S.SL_NM_item AS quantity, + 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 + """) + rows = mssql_session.execute(sale_sub_query, {'transaction_id': transaction_id}).fetchall() + sale_items = [ + {'name': r.goods_name, 'qty': int(r.quantity or 0), 'total': int(r.total or 0)} + for r in rows + ] + except Exception as e: + logging.warning(f"키오스크 품목 조회 실패 (transaction_id={transaction_id}): {e}") + # 키오스크 세션 저장 kiosk_current_session = { 'transaction_id': transaction_id, 'amount': int(amount), 'points': claimable_points, 'qr_url': qr_url, + 'items': sale_items, 'created_at': datetime.now(KST).isoformat() } @@ -1954,7 +1978,8 @@ def api_kiosk_current(): 'transaction_id': kiosk_current_session['transaction_id'], 'amount': kiosk_current_session['amount'], 'points': kiosk_current_session['points'], - 'qr_url': kiosk_current_session.get('qr_url') + 'qr_url': kiosk_current_session.get('qr_url'), + 'items': kiosk_current_session.get('items', []) }) diff --git a/backend/templates/admin.html b/backend/templates/admin.html index 942daf3..95c77ea 100644 --- a/backend/templates/admin.html +++ b/backend/templates/admin.html @@ -202,6 +202,11 @@ box-shadow: 0 2px 4px rgba(0,0,0,0.05); } + .section table tbody tr[onclick]:hover { + background: #eef2ff; + cursor: pointer; + } + /* 사이드바 레이아웃 */ .layout-wrapper { display: flex; @@ -495,12 +500,12 @@ {% for tx in recent_transactions %} - + {{ tx.nickname }} {{ tx.phone[:3] }}-{{ tx.phone[3:7] }}-{{ tx.phone[7:] if tx.phone|length > 7 else '' }} {{ "{:,}".format(tx.points) }}P {{ "{:,}".format(tx.balance_after) }}P - {{ tx.description or tx.reason }} + {{ tx.description or tx.reason }}{% if tx.transaction_id %} 🔍{% endif %} {{ tx.created_at[:16].replace('T', ' ') }} {% endfor %} diff --git a/backend/templates/kiosk.html b/backend/templates/kiosk.html index 75ac8d1..3064670 100644 --- a/backend/templates/kiosk.html +++ b/backend/templates/kiosk.html @@ -38,8 +38,9 @@ align-items: center; padding: 24px; position: relative; + overflow-y: auto; } - .screen { display: none; width: 100%; height: 100%; } + .screen { display: none; width: 100%; } .screen.active { display: flex; } /* ══════════════════════════════════════ @@ -207,6 +208,39 @@ .claim-amount-label { font-size: 15px; color: #6b7280; margin-bottom: 4px; } .claim-amount { font-size: 36px; font-weight: 900; color: #1e1b4b; letter-spacing: -1px; } .claim-points { font-size: 20px; color: #6366f1; font-weight: 700; margin-top: 8px; } + /* 품목 카드 */ + .items-card { + background: #fff; + border-radius: 16px; + padding: 16px 20px; + box-shadow: 0 4px 20px rgba(0,0,0,0.06); + width: 100%; + max-height: 200px; + overflow-y: auto; + } + .items-title { + font-size: 13px; + font-weight: 700; + color: #6b7280; + margin-bottom: 8px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + .items-list { display: flex; flex-direction: column; gap: 4px; } + .item-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #374151; + padding: 4px 0; + border-bottom: 1px solid #f3f4f6; + } + .item-row:last-child { border-bottom: none; } + .item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; } + .item-qty { color: #9ca3af; font-size: 13px; flex-shrink: 0; } + .item-total { font-weight: 600; color: #6366f1; flex-shrink: 0; min-width: 60px; text-align: right; } + .qr-container { background: #fff; border-radius: 20px; @@ -246,6 +280,7 @@ padding: 14px 20px; display: flex; align-items: center; + justify-content: center; transition: border-color 0.2s; min-height: 64px; } @@ -256,7 +291,6 @@ font-weight: 700; color: #9ca3af; letter-spacing: 1px; - margin-right: 4px; flex-shrink: 0; } .phone-number { @@ -264,8 +298,7 @@ font-weight: 700; color: #1e1b4b; letter-spacing: 2px; - flex: 1; - text-align: center; + margin-left: 2px; } .phone-number.placeholder { color: #d1d5db; } @@ -348,9 +381,52 @@ /* ── 적립/성공 화면 배경 밝게 ── */ .claim-screen, .success-screen { background: #f5f7fa; border-radius: 24px; padding: 32px; } - /* ── 반응형 ── */ + /* ── 반응형: 세로 모니터 (portrait, 폭 700px 이상) ── */ + @media (orientation: portrait) and (min-width: 700px) { + .main { padding: 32px; } + + /* 적립 화면: 세로 스택, 공간 활용 */ + .claim-screen { + flex-direction: column; + gap: 24px; + padding: 32px 48px; + max-width: 640px; + align-items: center; + justify-content: center; + } + .claim-left { + flex-direction: row; + flex-wrap: wrap; + gap: 16px; + width: 100%; + align-items: flex-start; + } + .claim-info-card { flex: 1; min-width: 200px; } + .qr-container { flex-shrink: 0; } + .items-card { width: 100%; max-height: 160px; } + .qr-container img { width: 140px; height: 140px; } + + .divider { flex-direction: row; } + .divider-line { width: 60px; height: 2px; } + + .claim-right { width: 100%; align-items: center; } + .phone-display-wrap { max-width: 440px; } + .phone-prefix { font-size: 30px; } + .phone-number { font-size: 30px; white-space: nowrap; } + .numpad { max-width: 440px; } + .submit-btn { max-width: 440px; } + + /* 슬라이드 더 크게 */ + .slides-wrapper { height: 420px; } + .slide-icon { width: 110px; height: 110px; font-size: 56px; } + .slide-title { font-size: 34px; } + .slide-desc { font-size: 19px; } + .slide-highlight { font-size: 16px; padding: 12px 32px; } + } + + /* ── 반응형: 좁은 화면 (모바일) ── */ @media (max-width: 700px) { - .claim-screen { flex-direction: column; gap: 20px; padding: 20px; } + .claim-screen { flex-direction: column; gap: 24px; padding: 20px; } .divider { flex-direction: row; } .divider-line { width: 60px; height: 2px; } .claim-amount { font-size: 28px; } @@ -438,6 +514,10 @@
0원
적립 0P
+