feat: AI CRM 어드민 대시보드 + 바텀시트 드래그 닫기 + UTF-8 인코딩 + 문서화
- /admin/ai-crm: AI 업셀링 추천 생성 현황 대시보드 (통계 카드 + 로그 테이블 + 아코디언 상세) - 마이페이지 바텀시트: 터치 드래그로 닫기 기능 추가 (80px 임계값) - Windows 콘솔 UTF-8 인코딩 강제 (app.py, clawdbot_client.py) - admin.html 헤더에 AI CRM 네비 링크 추가 - docs: ai-upselling-crm.md, windows-utf8-encoding.md 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,13 +3,21 @@ Flask 웹 서버 - QR 마일리지 적립
|
||||
간편 적립: 전화번호 + 이름만 입력
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Windows 콘솔 UTF-8 강제 (한글 깨짐 방지)
|
||||
if sys.platform == 'win32':
|
||||
import io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||
os.environ.setdefault('PYTHONIOENCODING', 'utf-8')
|
||||
|
||||
from flask import Flask, request, render_template, jsonify, redirect, url_for, session
|
||||
import hashlib
|
||||
import base64
|
||||
import secrets
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
from sqlalchemy import text
|
||||
from dotenv import load_dotenv
|
||||
@@ -2060,6 +2068,56 @@ def admin_alimtalk():
|
||||
return render_template('admin_alimtalk.html', local_logs=local_logs, stats=stats)
|
||||
|
||||
|
||||
@app.route('/admin/ai-crm')
|
||||
def admin_ai_crm():
|
||||
"""AI 업셀링 CRM 대시보드"""
|
||||
conn = db_manager.get_sqlite_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 추천 목록 (최근 50건) + 사용자 정보 JOIN
|
||||
cursor.execute("""
|
||||
SELECT r.*, u.nickname, u.phone as user_phone
|
||||
FROM ai_recommendations r
|
||||
LEFT JOIN users u ON r.user_id = u.id
|
||||
ORDER BY r.created_at DESC
|
||||
LIMIT 50
|
||||
""")
|
||||
recs = [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
# trigger_products JSON 파싱
|
||||
for rec in recs:
|
||||
tp = rec.get('trigger_products')
|
||||
if tp:
|
||||
try:
|
||||
rec['trigger_list'] = json.loads(tp)
|
||||
except Exception:
|
||||
rec['trigger_list'] = [tp]
|
||||
else:
|
||||
rec['trigger_list'] = []
|
||||
|
||||
# 통계
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN status = 'active' AND (expires_at IS NULL OR expires_at > datetime('now')) THEN 1 ELSE 0 END) as active_count,
|
||||
SUM(CASE WHEN status = 'dismissed' THEN 1 ELSE 0 END) as dismissed_count,
|
||||
SUM(CASE WHEN displayed_count > 0 THEN 1 ELSE 0 END) as displayed_count
|
||||
FROM ai_recommendations
|
||||
""")
|
||||
stats = dict(cursor.fetchone())
|
||||
|
||||
# 오늘 생성 건수
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as today_count
|
||||
FROM ai_recommendations
|
||||
WHERE date(created_at) = date('now')
|
||||
""")
|
||||
stats['today_count'] = cursor.fetchone()['today_count']
|
||||
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
return render_template('admin_ai_crm.html', recs=recs, stats=stats, now=now)
|
||||
|
||||
|
||||
@app.route('/api/admin/alimtalk/nhn-history')
|
||||
def api_admin_alimtalk_nhn_history():
|
||||
"""NHN Cloud 실제 발송 내역 API"""
|
||||
|
||||
Reference in New Issue
Block a user