""" routes/dashboard.py =================== Dashboard and related API routes. Routes: /dashboard, /project//qr-codes, /dashboard/search, /api/dashboard/stats, /api/dashboard/realtime """ from flask import abort, Blueprint, render_template, request, redirect, flash, session, jsonify, url_for from datetime import datetime, timedelta, date, time from extensions import db, logger_handler from models.attendance import AttendanceData from models.project import Project from models.qrcode import QRCode from models.user import User from logger_handler import log_user_activity, log_database_operations from utils.helpers import login_required bp = Blueprint('dashboard', __name__) @bp.route('/dashboard', endpoint='dashboard') @login_required def dashboard(): """Enhanced project-centric dashboard with search filters""" try: user = db.session.get(User, session['user_id']) # Get search parameters from URL search_name = request.args.get('search_name', '').strip() search_status = request.args.get('search_status', '').strip() # Build QR codes query with filters qr_query = QRCode.query # Apply name filter if provided if search_name: qr_query = qr_query.filter(QRCode.name.ilike(f'%{search_name}%')) # Apply status filter if provided if search_status == 'active': qr_query = qr_query.filter(QRCode.active_status == True) elif search_status == 'inactive': qr_query = qr_query.filter(QRCode.active_status == False) # Execute query qr_codes = qr_query.order_by(QRCode.created_date.desc()).all() projects = Project.query.order_by(Project.name.asc()).all() # Log dashboard access with filter info filter_info = [] if search_name: filter_info.append(f"name contains '{search_name}'") if search_status: filter_info.append(f"status is {search_status}") log_message = f"User {session['username']} accessed dashboard: {len(qr_codes)} QR codes" if filter_info: log_message += f" (filtered: {', '.join(filter_info)})" logger_handler.logger.info(log_message) return render_template('dashboard.html', user=user, qr_codes=qr_codes, projects=projects, search_name=search_name, search_status=search_status) except Exception as e: db.session.rollback() logger_handler.log_database_error('dashboard_load', e) flash('Error loading dashboard. Please try again.', 'error') return redirect(url_for('auth.login')) @bp.route('/project//qr-codes', endpoint='project_qr_codes') @login_required def project_qr_codes(project_id): """ View all QR codes for a specific project with search filters Allows filtering by name and status within the project """ try: # Get the project project = db.session.get(Project, project_id) if project is None: abort(404) # Get search parameters from URL search_name = request.args.get('search_name', '').strip() search_status = request.args.get('search_status', '').strip() # Build QR codes query with filters for this project only qr_query = QRCode.query.filter_by(project_id=project_id) # Apply name filter if provided if search_name: qr_query = qr_query.filter(QRCode.name.ilike(f'%{search_name}%')) # Apply status filter if provided if search_status == 'active': qr_query = qr_query.filter(QRCode.active_status == True) elif search_status == 'inactive': qr_query = qr_query.filter(QRCode.active_status == False) # Execute query qr_codes = qr_query.order_by(QRCode.created_date.desc()).all() # Log access with filter info filter_info = [] if search_name: filter_info.append(f"name contains '{search_name}'") if search_status: filter_info.append(f"status is {search_status}") log_message = f"User {session['username']} viewed project '{project.name}' QR codes: {len(qr_codes)} QR codes" if filter_info: log_message += f" (filtered: {', '.join(filter_info)})" logger_handler.logger.info(log_message) return render_template('project_qr_codes.html', project=project, qr_codes=qr_codes, search_name=search_name, search_status=search_status) except Exception as e: db.session.rollback() logger_handler.log_database_error('project_qr_codes_view', e) flash('Error loading project QR codes. Please try again.', 'error') return redirect(url_for('dashboard.dashboard')) @bp.route('/dashboard/search', methods=['GET'], endpoint='search_qr_codes') @login_required def search_qr_codes(): """Search QR codes - redirect to dashboard with filters""" search_name = request.args.get('search_name', '').strip() search_status = request.args.get('search_status', '').strip() # Log search activity logger_handler.logger.info( f"User {session['username']} searched QR codes: " f"name='{search_name}', status='{search_status}'" ) # Redirect to dashboard with search parameters return redirect(url_for('dashboard.dashboard', search_name=search_name, search_status=search_status)) @bp.route('/api/dashboard/stats', endpoint='dashboard_stats_api') @login_required def dashboard_stats_api(): """API endpoint for dashboard statistics""" try: # Get current stats total_qr_codes = QRCode.query.filter_by(active_status=True).count() # Today's check-ins today = datetime.utcnow().date() today_checkins = AttendanceData.query.filter( AttendanceData.check_in_date == today ).count() # Active projects active_projects = Project.query.filter_by(active_status=True).count() # Unique locations unique_locations = db.session.query( AttendanceData.location_name ).distinct().count() # Calculate trends (compared to last month) last_month = datetime.utcnow() - timedelta(days=30) # QR codes trend old_qr_count = QRCode.query.filter( QRCode.created_date <= last_month, QRCode.active_status == True ).count() qr_change = ((total_qr_codes - old_qr_count) / max(old_qr_count, 1)) * 100 # Check-ins trend (yesterday) yesterday = today - timedelta(days=1) yesterday_checkins = AttendanceData.query.filter( AttendanceData.check_in_date == yesterday ).count() checkin_change = ((today_checkins - yesterday_checkins) / max(yesterday_checkins, 1)) * 100 return jsonify({ 'success': True, 'total_qr_codes': total_qr_codes, 'today_checkins': today_checkins, 'active_projects': active_projects, 'unique_locations': unique_locations, 'qr_change': round(qr_change, 1), 'checkin_change': round(checkin_change, 1), 'project_change': 0, # You can calculate this based on your needs 'location_change': 0 # You can calculate this based on your needs }) except Exception as e: db.session.rollback() logger_handler.log_database_error('dashboard_stats_api', e) return jsonify({ 'success': False, 'error': 'Failed to fetch dashboard statistics' }), 500 @bp.route('/api/dashboard/realtime', endpoint='dashboard_realtime_api') @login_required def dashboard_realtime_api(): """API endpoint for real-time dashboard data""" try: # Get recent activity (last 10 check-ins) recent_activity = db.session.query( AttendanceData.employee_id, AttendanceData.location_name, AttendanceData.check_in_time, AttendanceData.check_in_date ).order_by( AttendanceData.check_in_date.desc(), AttendanceData.check_in_time.desc() ).limit(10).all() activity_data = [ { 'employee_id': activity.employee_id, 'location': activity.location_name, 'time': activity.check_in_time.strftime('%H:%M'), 'date': activity.check_in_date.strftime('%Y-%m-%d') } for activity in recent_activity ] return jsonify({ 'success': True, 'recent_activity': activity_data }) except Exception as e: db.session.rollback() logger_handler.log_database_error('dashboard_realtime_api', e) return jsonify({ 'success': False, 'error': 'Failed to fetch real-time data' }), 500 # USER MANAGEMENT ROUTES