feat: Flask 애플리케이션 모듈화 및 웹 대시보드 구현
- Flask Blueprint 아키텍처로 전환 (dashboard, upload, backup, status) - app.py 681줄 95줄로 축소 (86% 감소) - HTML 템플릿 모듈화 (base.html + 기능별 templates) - CSS/JS 파일 분리 (common + 기능별 파일) - 대시보드 기능 추가 (통계, 주간 예보, 방문객 추이) - 파일 업로드 웹 인터페이스 구현 - 백업/복구 관리 UI 구현 - Docker 배포 환경 개선 - .gitignore 업데이트 (uploads, backups, cache 등)
This commit is contained in:
225
app/blueprints/dashboard.py
Normal file
225
app/blueprints/dashboard.py
Normal file
@ -0,0 +1,225 @@
|
||||
# app/blueprints/dashboard.py
|
||||
"""
|
||||
대시보드 블루프린트
|
||||
|
||||
역할:
|
||||
- 데이터 통계 조회 (OKPOS, UPSolution, 날씨)
|
||||
- 주간 예보 조회
|
||||
- 방문객 추이 차트 데이터 제공
|
||||
"""
|
||||
|
||||
from flask import Blueprint, render_template, jsonify, request
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import select, func, desc
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
|
||||
|
||||
from conf import db, db_schema
|
||||
|
||||
dashboard_bp = Blueprint('dashboard', __name__, url_prefix='/api/dashboard')
|
||||
|
||||
|
||||
@dashboard_bp.route('/')
|
||||
def index():
|
||||
"""대시보드 페이지"""
|
||||
return render_template('dashboard.html')
|
||||
|
||||
|
||||
@dashboard_bp.route('/okpos-product', methods=['GET'])
|
||||
def get_okpos_product_stats():
|
||||
"""OKPOS 상품별 통계"""
|
||||
try:
|
||||
session = db.get_session()
|
||||
|
||||
# 데이터 개수
|
||||
total_records = session.query(db_schema.OkposProduct).count()
|
||||
|
||||
# 데이터 보유 일수
|
||||
earliest = session.query(func.min(db_schema.OkposProduct.data_date)).scalar()
|
||||
latest = session.query(func.max(db_schema.OkposProduct.data_date)).scalar()
|
||||
|
||||
if earliest and latest:
|
||||
total_days = (latest - earliest).days + 1
|
||||
last_date = latest.strftime('%Y-%m-%d')
|
||||
else:
|
||||
total_days = 0
|
||||
last_date = None
|
||||
|
||||
session.close()
|
||||
|
||||
return jsonify({
|
||||
'total_records': total_records,
|
||||
'total_days': total_days,
|
||||
'last_date': last_date
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@dashboard_bp.route('/okpos-receipt', methods=['GET'])
|
||||
def get_okpos_receipt_stats():
|
||||
"""OKPOS 영수증 통계"""
|
||||
try:
|
||||
session = db.get_session()
|
||||
|
||||
total_records = session.query(db_schema.OkposReceipt).count()
|
||||
|
||||
earliest = session.query(func.min(db_schema.OkposReceipt.receipt_date)).scalar()
|
||||
latest = session.query(func.max(db_schema.OkposReceipt.receipt_date)).scalar()
|
||||
|
||||
if earliest and latest:
|
||||
total_days = (latest - earliest).days + 1
|
||||
last_date = latest.strftime('%Y-%m-%d')
|
||||
else:
|
||||
total_days = 0
|
||||
last_date = None
|
||||
|
||||
session.close()
|
||||
|
||||
return jsonify({
|
||||
'total_records': total_records,
|
||||
'total_days': total_days,
|
||||
'last_date': last_date
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@dashboard_bp.route('/upsolution', methods=['GET'])
|
||||
def get_upsolution_stats():
|
||||
"""UPSolution 통계"""
|
||||
try:
|
||||
session = db.get_session()
|
||||
|
||||
total_records = session.query(db_schema.Upsolution).count()
|
||||
|
||||
earliest = session.query(func.min(db_schema.Upsolution.sales_date)).scalar()
|
||||
latest = session.query(func.max(db_schema.Upsolution.sales_date)).scalar()
|
||||
|
||||
if earliest and latest:
|
||||
total_days = (latest - earliest).days + 1
|
||||
last_date = latest.strftime('%Y-%m-%d')
|
||||
else:
|
||||
total_days = 0
|
||||
last_date = None
|
||||
|
||||
session.close()
|
||||
|
||||
return jsonify({
|
||||
'total_records': total_records,
|
||||
'total_days': total_days,
|
||||
'last_date': last_date
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@dashboard_bp.route('/weather', methods=['GET'])
|
||||
def get_weather_stats():
|
||||
"""날씨 데이터 통계"""
|
||||
try:
|
||||
session = db.get_session()
|
||||
|
||||
total_records = session.query(db_schema.Weather).count()
|
||||
|
||||
earliest = session.query(func.min(db_schema.Weather.date)).scalar()
|
||||
latest = session.query(func.max(db_schema.Weather.date)).scalar()
|
||||
|
||||
if earliest and latest:
|
||||
total_days = (latest - earliest).days + 1
|
||||
last_date = latest.strftime('%Y-%m-%d')
|
||||
else:
|
||||
total_days = 0
|
||||
last_date = None
|
||||
|
||||
session.close()
|
||||
|
||||
return jsonify({
|
||||
'total_records': total_records,
|
||||
'total_days': total_days,
|
||||
'last_date': last_date
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@dashboard_bp.route('/weekly-forecast', methods=['GET'])
|
||||
def get_weekly_forecast():
|
||||
"""이번주 예상 날씨 & 방문객"""
|
||||
try:
|
||||
session = db.get_session()
|
||||
|
||||
# 오늘부터 7일간 데이터 조회
|
||||
today = datetime.now().date()
|
||||
end_date = today + timedelta(days=6)
|
||||
|
||||
forecast_data = []
|
||||
|
||||
for i in range(7):
|
||||
current_date = today + timedelta(days=i)
|
||||
day_name = ['월', '화', '수', '목', '금', '토', '일'][current_date.weekday()]
|
||||
|
||||
# 날씨 데이터
|
||||
weather = session.query(db_schema.Weather).filter(
|
||||
db_schema.Weather.date == current_date
|
||||
).first()
|
||||
|
||||
# 예상 방문객 (동일한 요일의 평균)
|
||||
same_day_visitors = session.query(
|
||||
func.avg(db_schema.DailyVisitor.visitors)
|
||||
).filter(
|
||||
func.dayofweek(db_schema.DailyVisitor.visit_date) == (current_date.weekday() + 2) % 7 + 1
|
||||
).scalar()
|
||||
|
||||
forecast_data.append({
|
||||
'date': current_date.strftime('%Y-%m-%d'),
|
||||
'day': day_name,
|
||||
'min_temp': weather.min_temp if weather else None,
|
||||
'max_temp': weather.max_temp if weather else None,
|
||||
'precipitation': weather.precipitation if weather else 0.0,
|
||||
'humidity': weather.humidity if weather else None,
|
||||
'expected_visitors': int(same_day_visitors) if same_day_visitors else 0
|
||||
})
|
||||
|
||||
session.close()
|
||||
|
||||
return jsonify({'forecast_data': forecast_data})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@dashboard_bp.route('/visitor-trend', methods=['GET'])
|
||||
def get_visitor_trend():
|
||||
"""방문객 추이 데이터"""
|
||||
try:
|
||||
start_date_str = request.args.get('start_date')
|
||||
end_date_str = request.args.get('end_date')
|
||||
|
||||
if not start_date_str or not end_date_str:
|
||||
return jsonify({'error': '날짜 범위를 지정하세요.'}), 400
|
||||
|
||||
start_date = datetime.strptime(start_date_str, '%Y-%m-%d').date()
|
||||
end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date()
|
||||
|
||||
session = db.get_session()
|
||||
|
||||
visitors = session.query(
|
||||
db_schema.DailyVisitor.visit_date,
|
||||
db_schema.DailyVisitor.visitors
|
||||
).filter(
|
||||
db_schema.DailyVisitor.visit_date.between(start_date, end_date)
|
||||
).order_by(db_schema.DailyVisitor.visit_date).all()
|
||||
|
||||
session.close()
|
||||
|
||||
dates = [v[0].strftime('%Y-%m-%d') for v in visitors]
|
||||
visitor_counts = [v[1] for v in visitors]
|
||||
|
||||
return jsonify({
|
||||
'dates': dates,
|
||||
'visitors': visitor_counts
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
Reference in New Issue
Block a user