""" 대시보드 API 사용자별 맞춤형 대시보드 데이터 제공 """ from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy import text, func from typing import Optional, Dict, Any, List from datetime import datetime, timedelta from ..database import get_db from ..auth.middleware import get_current_user from ..services.activity_logger import ActivityLogger from ..utils.logger import get_logger logger = get_logger(__name__) router = APIRouter(prefix="/dashboard", tags=["dashboard"]) @router.get("/stats") async def get_dashboard_stats( current_user: dict = Depends(get_current_user), db: Session = Depends(get_db) ): """ 사용자별 맞춤형 대시보드 통계 데이터 조회 Returns: dict: 사용자 역할에 맞는 통계 데이터 """ try: username = current_user.get('username') user_role = current_user.get('role', 'user') # 역할별 맞춤 통계 생성 if user_role == 'admin': stats = await get_admin_stats(db) elif user_role == 'manager': stats = await get_manager_stats(db, username) elif user_role == 'designer': stats = await get_designer_stats(db, username) elif user_role == 'purchaser': stats = await get_purchaser_stats(db, username) else: stats = await get_user_stats(db, username) return { "success": True, "user_role": user_role, "stats": stats } except Exception as e: logger.error(f"Dashboard stats error: {str(e)}") raise HTTPException(status_code=500, detail=f"대시보드 통계 조회 실패: {str(e)}") @router.get("/activities") async def get_user_activities( current_user: dict = Depends(get_current_user), limit: int = Query(10, ge=1, le=50), db: Session = Depends(get_db) ): """ 사용자 활동 이력 조회 Args: limit: 조회할 활동 수 (1-50) Returns: dict: 사용자 활동 이력 """ try: username = current_user.get('username') activity_logger = ActivityLogger(db) activities = activity_logger.get_user_activities( username=username, limit=limit ) return { "success": True, "activities": activities, "total": len(activities) } except Exception as e: logger.error(f"User activities error: {str(e)}") raise HTTPException(status_code=500, detail=f"활동 이력 조회 실패: {str(e)}") @router.get("/recent-activities") async def get_recent_activities( current_user: dict = Depends(get_current_user), days: int = Query(7, ge=1, le=30), limit: int = Query(20, ge=1, le=100), db: Session = Depends(get_db) ): """ 최근 전체 활동 조회 (관리자/매니저용) Args: days: 조회 기간 (일) limit: 조회할 활동 수 Returns: dict: 최근 활동 이력 """ try: user_role = current_user.get('role', 'user') # 관리자와 매니저만 전체 활동 조회 가능 if user_role not in ['admin', 'manager']: raise HTTPException(status_code=403, detail="권한이 없습니다") activity_logger = ActivityLogger(db) activities = activity_logger.get_recent_activities( days=days, limit=limit ) return { "success": True, "activities": activities, "period_days": days, "total": len(activities) } except HTTPException: raise except Exception as e: logger.error(f"Recent activities error: {str(e)}") raise HTTPException(status_code=500, detail=f"최근 활동 조회 실패: {str(e)}") async def get_admin_stats(db: Session) -> Dict[str, Any]: """관리자용 통계""" try: # 전체 프로젝트 수 total_projects_query = text("SELECT COUNT(*) FROM jobs WHERE status != 'deleted'") total_projects = db.execute(total_projects_query).scalar() # 활성 사용자 수 (최근 30일 로그인) active_users_query = text(""" SELECT COUNT(DISTINCT username) FROM user_activity_logs WHERE created_at >= CURRENT_TIMESTAMP - INTERVAL '30 days' """) active_users = db.execute(active_users_query).scalar() or 0 # 오늘 업로드된 파일 수 today_uploads_query = text(""" SELECT COUNT(*) FROM files WHERE DATE(upload_date) = CURRENT_DATE """) today_uploads = db.execute(today_uploads_query).scalar() or 0 # 전체 자재 수 total_materials_query = text("SELECT COUNT(*) FROM materials") total_materials = db.execute(total_materials_query).scalar() or 0 return { "title": "시스템 관리자", "subtitle": "전체 시스템을 관리하고 모니터링합니다", "metrics": [ {"label": "전체 프로젝트 수", "value": total_projects, "icon": "📋", "color": "#667eea"}, {"label": "활성 사용자 수", "value": active_users, "icon": "👥", "color": "#48bb78"}, {"label": "시스템 상태", "value": "정상", "icon": "🟢", "color": "#38b2ac"}, {"label": "오늘 업로드", "value": today_uploads, "icon": "📤", "color": "#ed8936"} ] } except Exception as e: logger.error(f"Admin stats error: {str(e)}") raise async def get_manager_stats(db: Session, username: str) -> Dict[str, Any]: """매니저용 통계""" try: # 담당 프로젝트 수 (향후 assigned_to 필드 활용) assigned_projects_query = text(""" SELECT COUNT(*) FROM jobs WHERE (assigned_to = :username OR created_by = :username) AND status != 'deleted' """) assigned_projects = db.execute(assigned_projects_query, {"username": username}).scalar() or 0 # 이번 주 완료된 작업 (활동 로그 기반) week_completed_query = text(""" SELECT COUNT(*) FROM user_activity_logs WHERE activity_type IN ('PROJECT_CREATE', 'PURCHASE_CONFIRM') AND created_at >= CURRENT_TIMESTAMP - INTERVAL '7 days' """) week_completed = db.execute(week_completed_query).scalar() or 0 # 승인 대기 (구매 확정 대기 등) pending_approvals_query = text(""" SELECT COUNT(*) FROM material_purchase_tracking WHERE purchase_status = 'PENDING' OR purchase_status = 'REQUESTED' """) pending_approvals = db.execute(pending_approvals_query).scalar() or 0 return { "title": "프로젝트 매니저", "subtitle": "팀 프로젝트를 관리하고 진행상황을 모니터링합니다", "metrics": [ {"label": "담당 프로젝트", "value": assigned_projects, "icon": "📋", "color": "#667eea"}, {"label": "팀 진행률", "value": "87%", "icon": "📈", "color": "#48bb78"}, {"label": "승인 대기", "value": pending_approvals, "icon": "⏳", "color": "#ed8936"}, {"label": "이번 주 완료", "value": week_completed, "icon": "✅", "color": "#38b2ac"} ] } except Exception as e: logger.error(f"Manager stats error: {str(e)}") raise async def get_designer_stats(db: Session, username: str) -> Dict[str, Any]: """설계자용 통계""" try: # 내가 업로드한 BOM 파일 수 my_files_query = text(""" SELECT COUNT(*) FROM files WHERE uploaded_by = :username AND is_active = true """) my_files = db.execute(my_files_query, {"username": username}).scalar() or 0 # 분류된 자재 수 classified_materials_query = text(""" SELECT COUNT(*) FROM materials m JOIN files f ON m.file_id = f.id WHERE f.uploaded_by = :username AND m.classified_category IS NOT NULL """) classified_materials = db.execute(classified_materials_query, {"username": username}).scalar() or 0 # 검증 대기 자재 수 pending_verification_query = text(""" SELECT COUNT(*) FROM materials m JOIN files f ON m.file_id = f.id WHERE f.uploaded_by = :username AND m.is_verified = false """) pending_verification = db.execute(pending_verification_query, {"username": username}).scalar() or 0 # 이번 주 업로드 수 week_uploads_query = text(""" SELECT COUNT(*) FROM files WHERE uploaded_by = :username AND upload_date >= CURRENT_TIMESTAMP - INTERVAL '7 days' """) week_uploads = db.execute(week_uploads_query, {"username": username}).scalar() or 0 # 분류 완료율 계산 total_materials_query = text(""" SELECT COUNT(*) FROM materials m JOIN files f ON m.file_id = f.id WHERE f.uploaded_by = :username """) total_materials = db.execute(total_materials_query, {"username": username}).scalar() or 1 classification_rate = f"{(classified_materials / total_materials * 100):.0f}%" if total_materials > 0 else "0%" return { "title": "설계 담당자", "subtitle": "BOM 파일을 관리하고 자재를 분류합니다", "metrics": [ {"label": "내 BOM 파일", "value": my_files, "icon": "📄", "color": "#667eea"}, {"label": "분류 완료율", "value": classification_rate, "icon": "🎯", "color": "#48bb78"}, {"label": "검증 대기", "value": pending_verification, "icon": "⏳", "color": "#ed8936"}, {"label": "이번 주 업로드", "value": week_uploads, "icon": "📤", "color": "#9f7aea"} ] } except Exception as e: logger.error(f"Designer stats error: {str(e)}") raise async def get_purchaser_stats(db: Session, username: str) -> Dict[str, Any]: """구매자용 통계""" try: # 구매 요청 수 purchase_requests_query = text(""" SELECT COUNT(*) FROM material_purchase_tracking WHERE purchase_status IN ('PENDING', 'REQUESTED') """) purchase_requests = db.execute(purchase_requests_query).scalar() or 0 # 발주 완료 수 orders_completed_query = text(""" SELECT COUNT(*) FROM material_purchase_tracking WHERE purchase_status = 'CONFIRMED' AND confirmed_by = :username """) orders_completed = db.execute(orders_completed_query, {"username": username}).scalar() or 0 # 입고 대기 수 receiving_pending_query = text(""" SELECT COUNT(*) FROM material_purchase_tracking WHERE purchase_status = 'ORDERED' """) receiving_pending = db.execute(receiving_pending_query).scalar() or 0 # 이번 달 구매 금액 (임시 데이터) monthly_amount = "₩2.3M" # 실제로는 계산 필요 return { "title": "구매 담당자", "subtitle": "구매 요청을 처리하고 발주를 관리합니다", "metrics": [ {"label": "구매 요청", "value": purchase_requests, "icon": "🛒", "color": "#667eea"}, {"label": "발주 완료", "value": orders_completed, "icon": "✅", "color": "#48bb78"}, {"label": "입고 대기", "value": receiving_pending, "icon": "📦", "color": "#ed8936"}, {"label": "이번 달 금액", "value": monthly_amount, "icon": "💰", "color": "#9f7aea"} ] } except Exception as e: logger.error(f"Purchaser stats error: {str(e)}") raise async def get_user_stats(db: Session, username: str) -> Dict[str, Any]: """일반 사용자용 통계""" try: # 내 활동 수 (최근 7일) my_activities_query = text(""" SELECT COUNT(*) FROM user_activity_logs WHERE username = :username AND created_at >= CURRENT_TIMESTAMP - INTERVAL '7 days' """) my_activities = db.execute(my_activities_query, {"username": username}).scalar() or 0 # 접근 가능한 프로젝트 수 (임시) accessible_projects = 5 return { "title": "일반 사용자", "subtitle": "할당된 업무를 수행하고 프로젝트에 참여합니다", "metrics": [ {"label": "내 업무", "value": 6, "icon": "📋", "color": "#667eea"}, {"label": "완료율", "value": "75%", "icon": "📈", "color": "#48bb78"}, {"label": "대기 중", "value": 2, "icon": "⏳", "color": "#ed8936"}, {"label": "이번 주 활동", "value": my_activities, "icon": "🎯", "color": "#9f7aea"} ] } except Exception as e: logger.error(f"User stats error: {str(e)}") raise @router.get("/quick-actions") async def get_quick_actions( current_user: dict = Depends(get_current_user) ): """ 사용자 역할별 빠른 작업 메뉴 조회 Returns: dict: 역할별 빠른 작업 목록 """ try: user_role = current_user.get('role', 'user') quick_actions = { "admin": [ {"title": "사용자 관리", "icon": "👤", "path": "/admin/users", "color": "#667eea"}, {"title": "시스템 설정", "icon": "⚙️", "path": "/admin/settings", "color": "#48bb78"}, {"title": "백업 관리", "icon": "💾", "path": "/admin/backup", "color": "#ed8936"}, {"title": "활동 로그", "icon": "📊", "path": "/admin/logs", "color": "#9f7aea"} ], "manager": [ {"title": "프로젝트 생성", "icon": "➕", "path": "/projects/new", "color": "#667eea"}, {"title": "팀 관리", "icon": "👥", "path": "/team", "color": "#48bb78"}, {"title": "진행 상황", "icon": "📊", "path": "/progress", "color": "#38b2ac"}, {"title": "승인 처리", "icon": "✅", "path": "/approvals", "color": "#ed8936"} ], "designer": [ {"title": "BOM 업로드", "icon": "📤", "path": "/upload", "color": "#667eea"}, {"title": "자재 분류", "icon": "🔧", "path": "/materials", "color": "#48bb78"}, {"title": "리비전 관리", "icon": "🔄", "path": "/revisions", "color": "#38b2ac"}, {"title": "분류 검증", "icon": "✅", "path": "/verify", "color": "#ed8936"} ], "purchaser": [ {"title": "구매 확정", "icon": "🛒", "path": "/purchase", "color": "#667eea"}, {"title": "발주 관리", "icon": "📋", "path": "/orders", "color": "#48bb78"}, {"title": "공급업체", "icon": "🏢", "path": "/suppliers", "color": "#38b2ac"}, {"title": "입고 처리", "icon": "📦", "path": "/receiving", "color": "#ed8936"} ], "user": [ {"title": "내 업무", "icon": "📋", "path": "/my-tasks", "color": "#667eea"}, {"title": "프로젝트 보기", "icon": "👁️", "path": "/projects", "color": "#48bb78"}, {"title": "리포트 다운로드", "icon": "📊", "path": "/reports", "color": "#38b2ac"}, {"title": "도움말", "icon": "❓", "path": "/help", "color": "#9f7aea"} ] } return { "success": True, "user_role": user_role, "quick_actions": quick_actions.get(user_role, quick_actions["user"]) } except Exception as e: logger.error(f"Quick actions error: {str(e)}") raise HTTPException(status_code=500, detail=f"빠른 작업 조회 실패: {str(e)}")