split up controllers; new summaries
This commit is contained in:
parent
9510f5b01b
commit
c9e8571a85
@ -2,7 +2,7 @@ import typer
|
|||||||
import httpx
|
import httpx
|
||||||
import lxml.html
|
import lxml.html
|
||||||
from typing_extensions import Annotated
|
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 .import_csv import import_tasks_from_csv
|
||||||
from .db import initialize_db
|
from .db import initialize_db
|
||||||
from .tui import tasks
|
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 textual.widgets import Input
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from ..controller import (
|
from ..controller.tasks import (
|
||||||
get_task,
|
get_task,
|
||||||
get_tasks,
|
get_tasks,
|
||||||
add_task,
|
add_task,
|
||||||
|
Loading…
Reference in New Issue
Block a user