split up controllers; new summaries
This commit is contained in:
parent
9510f5b01b
commit
c9e8571a85
@ -2,7 +2,7 @@ import typer
|
||||
import httpx
|
||||
import lxml.html
|
||||
from typing_extensions import Annotated
|
||||
from .controller import add_task
|
||||
from .controller.tasks import add_task
|
||||
from .import_csv import import_tasks_from_csv
|
||||
from .db import initialize_db
|
||||
from .tui import tasks
|
||||
|
180
src/tt/controller/summaries.py
Normal file
180
src/tt/controller/summaries.py
Normal file
@ -0,0 +1,180 @@
|
||||
from datetime import datetime, timedelta
|
||||
from peewee import fn, JOIN
|
||||
from .db import Task, Category, TaskStatus
|
||||
|
||||
|
||||
def get_category_summary(num: int = 5) -> list[dict]:
|
||||
"""
|
||||
Returns summary of top categories with task counts by status and due dates.
|
||||
|
||||
Args:
|
||||
num: Number of categories to return, ordered by total task count
|
||||
|
||||
Returns:
|
||||
List of dicts containing category name and task statistics
|
||||
"""
|
||||
now = datetime.now()
|
||||
week_from_now = now + timedelta(days=7)
|
||||
|
||||
# Subquery for overdue tasks
|
||||
overdue_count = (
|
||||
Task.select(Task.category, fn.COUNT(Task.id).alias("overdue"))
|
||||
.where(
|
||||
(~Task.deleted) & (Task.due < now) & (Task.status != TaskStatus.DONE.value)
|
||||
)
|
||||
.group_by(Task.category)
|
||||
)
|
||||
|
||||
# Subquery for tasks due in next 7 days
|
||||
due_soon_count = (
|
||||
Task.select(Task.category, fn.COUNT(Task.id).alias("due_soon"))
|
||||
.where(
|
||||
(~Task.deleted)
|
||||
& (Task.due >= now)
|
||||
& (Task.due <= week_from_now)
|
||||
& (Task.status != TaskStatus.DONE.value)
|
||||
)
|
||||
.group_by(Task.category)
|
||||
)
|
||||
|
||||
# Main query joining all the information
|
||||
query = (
|
||||
Category.select(
|
||||
Category.name,
|
||||
fn.COALESCE(fn.SUM(Task.status == TaskStatus.ZERO.value), 0).alias(
|
||||
"zero_count"
|
||||
),
|
||||
fn.COALESCE(fn.SUM(Task.status == TaskStatus.WIP.value), 0).alias(
|
||||
"wip_count"
|
||||
),
|
||||
fn.COALESCE(fn.SUM(Task.status == TaskStatus.BLOCKED.value), 0).alias(
|
||||
"blocked_count"
|
||||
),
|
||||
fn.COALESCE(fn.SUM(Task.status == TaskStatus.DONE.value), 0).alias(
|
||||
"done_count"
|
||||
),
|
||||
fn.COALESCE(overdue_count.c.overdue, 0).alias("overdue"),
|
||||
fn.COALESCE(due_soon_count.c.due_soon, 0).alias("due_soon"),
|
||||
)
|
||||
.join(Task, JOIN.LEFT_OUTER)
|
||||
.join(
|
||||
overdue_count,
|
||||
JOIN.LEFT_OUTER,
|
||||
on=(Category.id == overdue_count.c.category_id),
|
||||
)
|
||||
.join(
|
||||
due_soon_count,
|
||||
JOIN.LEFT_OUTER,
|
||||
on=(Category.id == due_soon_count.c.category_id),
|
||||
)
|
||||
.where(~Task.deleted)
|
||||
.group_by(Category.id)
|
||||
.order_by(fn.COUNT(Task.id).desc())
|
||||
.limit(num)
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"category": cat.name,
|
||||
"tasks": {
|
||||
"zero": cat.zero_count,
|
||||
"wip": cat.wip_count,
|
||||
"blocked": cat.blocked_count,
|
||||
"done": cat.done_count,
|
||||
"overdue": cat.overdue,
|
||||
"due_soon": cat.due_soon,
|
||||
},
|
||||
}
|
||||
for cat in query
|
||||
]
|
||||
|
||||
|
||||
def get_recently_active(num: int = 5, category: str | None = None) -> list[dict]:
|
||||
"""
|
||||
Returns most recently active tasks, optionally filtered by category.
|
||||
|
||||
Args:
|
||||
num: Number of tasks to return
|
||||
category: Optional category name to filter by
|
||||
|
||||
Returns:
|
||||
List of tasks ordered by last activity (updated_at)
|
||||
"""
|
||||
query = (
|
||||
Task.select(Task, Category.name.alias("category_name"))
|
||||
.join(Category, JOIN.LEFT_OUTER)
|
||||
.where(~Task.deleted)
|
||||
)
|
||||
|
||||
if category:
|
||||
query = query.where(Category.name == category)
|
||||
|
||||
query = query.order_by(Task.updated_at.desc()).limit(num)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": task.id,
|
||||
"text": task.text,
|
||||
"status": task.status,
|
||||
"type": task.type,
|
||||
"category": task.category_name,
|
||||
"due": task.due,
|
||||
"updated_at": task.updated_at,
|
||||
}
|
||||
for task in query
|
||||
]
|
||||
|
||||
|
||||
def get_due_soon(
|
||||
num: int = 5, all_overdue: bool = True, category: str | None = None
|
||||
) -> list[dict]:
|
||||
"""
|
||||
Returns tasks ordered by due date, optionally including all overdue tasks.
|
||||
|
||||
Args:
|
||||
num: Number of non-overdue tasks to return
|
||||
all_overdue: If True, returns all overdue tasks plus num more tasks
|
||||
If False, includes overdue tasks in the count
|
||||
category: Optional category name to filter by
|
||||
|
||||
Returns:
|
||||
List of tasks ordered by due date
|
||||
"""
|
||||
now = datetime.now()
|
||||
|
||||
# Base query
|
||||
query = (
|
||||
Task.select(Task, Category.name.alias("category_name"))
|
||||
.join(Category, JOIN.LEFT_OUTER)
|
||||
.where(
|
||||
(~Task.deleted)
|
||||
& (Task.due.is_null(False))
|
||||
& (Task.status != TaskStatus.DONE.value)
|
||||
)
|
||||
)
|
||||
|
||||
if category:
|
||||
query = query.where(Category.name == category)
|
||||
|
||||
# Handle overdue tasks based on all_overdue parameter
|
||||
if all_overdue:
|
||||
overdue_tasks = list(query.where(Task.due < now).order_by(Task.due))
|
||||
upcoming_tasks = list(
|
||||
query.where(Task.due >= now).order_by(Task.due).limit(num)
|
||||
)
|
||||
tasks = overdue_tasks + upcoming_tasks
|
||||
else:
|
||||
tasks = list(query.order_by(Task.due).limit(num))
|
||||
|
||||
return [
|
||||
{
|
||||
"id": task.id,
|
||||
"text": task.text,
|
||||
"status": task.status,
|
||||
"type": task.type,
|
||||
"category": task.category_name,
|
||||
"due": task.due,
|
||||
"days_until_due": (task.due - now).days if task.due else None,
|
||||
}
|
||||
for task in tasks
|
||||
]
|
@ -2,7 +2,7 @@ import json
|
||||
from textual.widgets import Input
|
||||
from datetime import datetime
|
||||
|
||||
from ..controller import (
|
||||
from ..controller.tasks import (
|
||||
get_task,
|
||||
get_tasks,
|
||||
add_task,
|
||||
|
Loading…
Reference in New Issue
Block a user