from flask import Flask, render_template, request, redirect, url_for, jsonify from flask_sqlalchemy import SQLAlchemy from flask_socketio import SocketIO, emit from datetime import datetime, timedelta app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) socketio = SocketIO(app) # Database models class Task(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) priority = db.Column(db.String(1), nullable=False) # A, B, C, or D duration = db.Column(db.Integer, nullable=False) # Duration in minutes status = db.Column(db.String(10), default='pending') due_date = db.Column(db.Date, nullable=True) # Due date for the task completed_at = db.Column(db.DateTime, nullable=True) # Timestamp for when the task was completed def format_duration(minutes): """Convert duration in minutes to 'H M' format.""" hours = minutes // 60 remaining_minutes = minutes % 60 if hours > 0: return f"{hours}H {remaining_minutes}M" if remaining_minutes > 0 else f"{hours}H" return f"{remaining_minutes}M" @app.context_processor def utility_processor(): return dict(format_duration=format_duration) def cleanup_completed_tasks(): """Remove tasks that have been completed for more than 1 hour.""" expiration_time = datetime.utcnow() - timedelta(hours=1) old_tasks = Task.query.filter(Task.status == 'completed', Task.completed_at < expiration_time).all() for task in old_tasks: db.session.delete(task) db.session.commit() # Routes @app.route('/') def index(): cleanup_completed_tasks() # Remove old completed tasks # Get the sorting parameter from the query string (default is "priority") sort_by = request.args.get('sort_by', 'priority') # Dynamically sort tasks based on the parameter if sort_by == 'priority': tasks = Task.query.filter(Task.status == 'pending').order_by(Task.priority).all() elif sort_by == 'due_date': tasks = Task.query.filter(Task.status == 'pending').order_by(Task.due_date).all() elif sort_by == 'duration': tasks = Task.query.filter(Task.status == 'pending').order_by(Task.duration).all() else: tasks = Task.query.filter(Task.status == 'pending').all() # Default: no sorting return render_template('index.html', tasks=tasks, sort_by=sort_by) @app.route('/add_task', methods=['POST']) def add_task(): name = request.form['name'] priority = request.form['priority'] duration_input = request.form['duration'] due_date_input = request.form['due_date'] # Parse duration (e.g., "10M" -> 10 minutes, "2H" -> 120 minutes) if duration_input.endswith('M'): duration = int(duration_input[:-1]) # Extract the number before 'M' elif duration_input.endswith('H'): duration = int(duration_input[:-1]) * 60 # Convert hours to minutes else: return "Invalid duration format. Use 'M' for minutes or 'H' for hours.", 400 # Parse due date (e.g., "2025-03-30") try: due_date = datetime.strptime(due_date_input, '%Y-%m-%d').date() if due_date_input else None except ValueError: return "Invalid due date format. Use 'YYYY-MM-DD'.", 400 # Create and save the new task new_task = Task(name=name, priority=priority, duration=duration, due_date=due_date) db.session.add(new_task) db.session.commit() # Emit the new task to connected clients socketio.emit('task_update', { 'action': 'add', 'task': { 'id': new_task.id, 'name': name, 'priority': priority, 'duration': duration, 'due_date': due_date_input, 'status': 'pending' } }) return redirect(url_for('index')) @app.route('/complete_task/') def complete_task(task_id): task = Task.query.get(task_id) if task: task.status = 'completed' task.completed_at = datetime.utcnow() # Store the timestamp in UTC db.session.commit() socketio.emit('task_update', {'action': 'complete', 'task_id': task_id}) return redirect(url_for('index')) @app.route('/completed_tasks') def completed_tasks(): cleanup_completed_tasks() # Remove old completed tasks # Get the sorting parameter from the query string (default is "completed_at") sort_by = request.args.get('sort_by', 'completed_at') # Dynamically sort tasks based on the parameter if sort_by == 'priority': tasks = Task.query.filter(Task.status == 'completed').order_by(Task.priority).all() elif sort_by == 'due_date': tasks = Task.query.filter(Task.status == 'completed').order_by(Task.due_date).all() elif sort_by == 'duration': tasks = Task.query.filter(Task.status == 'completed').order_by(Task.duration).all() elif sort_by == 'completed_at': tasks = Task.query.filter(Task.status == 'completed').order_by(Task.completed_at.desc()).all() else: tasks = Task.query.filter(Task.status == 'completed').all() # Default: no sorting return render_template('completed_tasks.html', tasks=tasks, sort_by=sort_by) if __name__ == '__main__': with app.app_context(): # Create database tables within the app context db.create_all() socketio.run(app, debug=True)