from __future__ import annotations import calendar from collections import defaultdict from datetime import date from flask import Blueprint, flash, redirect, render_template, request, url_for from flask_login import current_user, login_required from ..forms import TaskForm from ..models import TaskInstance, User from ..services.dates import month_label, today_local from ..services.tasks import complete_task, create_task_template_and_instance, refresh_task_statuses, update_template_and_instance bp = Blueprint("tasks", __name__, url_prefix="") def _user_choices() -> list[tuple[int, str]]: return [(user.id, user.name) for user in User.query.order_by(User.name.asc()).all()] @bp.route("/my-tasks") @login_required def my_tasks(): tasks = ( TaskInstance.query.filter_by(assigned_user_id=current_user.id) .order_by(TaskInstance.completed_at.is_(None).desc(), TaskInstance.due_date.asc()) .all() ) refresh_task_statuses(tasks) sections = {"open": [], "soon": [], "overdue": [], "completed": []} for task in tasks: sections[task.status].append(task) completed_count = len(sections["completed"]) active_count = len(sections["open"]) + len(sections["soon"]) + len(sections["overdue"]) completion_ratio = 0 if completed_count + active_count == 0 else round(completed_count / (completed_count + active_count) * 100) return render_template( "tasks/my_tasks.html", sections=sections, completion_ratio=completion_ratio, today=today_local(), ) @bp.route("/tasks") @login_required def all_tasks(): query = TaskInstance.query status = request.args.get("status", "all") mine = request.args.get("mine") user_filter = request.args.get("user_id", type=int) sort = request.args.get("sort", "due") if mine == "1": query = query.filter(TaskInstance.assigned_user_id == current_user.id) elif user_filter: query = query.filter(TaskInstance.assigned_user_id == user_filter) if sort == "points": query = query.order_by(TaskInstance.points_awarded.desc(), TaskInstance.due_date.asc()) elif sort == "user": query = query.order_by(TaskInstance.assigned_user_id.asc(), TaskInstance.due_date.asc()) else: query = query.order_by(TaskInstance.completed_at.is_(None).desc(), TaskInstance.due_date.asc()) tasks = query.all() refresh_task_statuses(tasks) if status != "all": status_map = {"completed": "completed", "overdue": "overdue", "open": "open", "soon": "soon"} selected = status_map.get(status) if selected: tasks = [task for task in tasks if task.status == selected] return render_template( "tasks/all_tasks.html", tasks=tasks, users=User.query.order_by(User.name.asc()).all(), filters={"status": status, "mine": mine, "user_id": user_filter, "sort": sort}, ) @bp.route("/tasks/new", methods=["GET", "POST"]) @login_required def create(): form = TaskForm() form.assigned_user_id.choices = _user_choices() if request.method == "GET" and not form.due_date.data: form.due_date.data = today_local() if form.validate_on_submit(): task = create_task_template_and_instance(form) flash(f"Aufgabe „{task.title}“ wurde angelegt.", "success") return redirect(url_for("tasks.my_tasks")) return render_template("tasks/task_form.html", form=form, mode="create", task=None) @bp.route("/tasks//edit", methods=["GET", "POST"]) @login_required def edit(task_id: int): task = TaskInstance.query.get_or_404(task_id) form = TaskForm(obj=task.task_template) form.assigned_user_id.choices = _user_choices() if request.method == "GET": form.title.data = task.title form.description.data = task.description form.default_points.data = task.points_awarded form.assigned_user_id.data = task.assigned_user_id or _user_choices()[0][0] form.due_date.data = task.due_date form.recurrence_interval_value.data = task.task_template.recurrence_interval_value or 1 form.recurrence_interval_unit.data = task.task_template.recurrence_interval_unit form.active.data = task.task_template.active if form.validate_on_submit(): update_template_and_instance(task, form) flash("Aufgabe und Vorlage wurden aktualisiert.", "success") return redirect(url_for("tasks.all_tasks")) return render_template("tasks/task_form.html", form=form, mode="edit", task=task) @bp.route("/tasks//complete", methods=["POST"]) @login_required def complete(task_id: int): task = TaskInstance.query.get_or_404(task_id) choice = request.form.get("completed_for", "me") if task.is_completed: flash("Diese Aufgabe ist bereits erledigt.", "info") return redirect(request.referrer or url_for("tasks.my_tasks")) completed_by_id = current_user.id if task.assigned_user_id and task.assigned_user_id != current_user.id and choice == "assigned": completed_by_id = task.assigned_user_id complete_task(task, completed_by_id) flash("Punkte verbucht. Gute Arbeit.", "success") return redirect(request.referrer or url_for("tasks.my_tasks")) @bp.route("/calendar") @login_required def calendar_view(): today = today_local() year = request.args.get("year", type=int) or today.year month = request.args.get("month", type=int) or today.month view = request.args.get("view", "calendar") tasks = TaskInstance.query.filter( TaskInstance.due_date >= date(year, month, 1), TaskInstance.due_date <= date(year, month, calendar.monthrange(year, month)[1]), ).order_by(TaskInstance.due_date.asc()).all() refresh_task_statuses(tasks) tasks_by_day: dict[int, list[TaskInstance]] = defaultdict(list) for task in tasks: tasks_by_day[task.due_date.day].append(task) month_calendar = calendar.Calendar(firstweekday=0).monthdayscalendar(year, month) return render_template( "tasks/calendar.html", current_year=year, current_month=month, current_label=month_label(year, month), month_calendar=month_calendar, tasks_by_day=tasks_by_day, view=view, tasks=tasks, )