Files
lifeos-prod/routers/eisenhower.py
2026-03-03 00:44:33 +00:00

121 lines
3.7 KiB
Python

"""Eisenhower Matrix: read-only 2x2 priority/urgency grid of open tasks."""
from fastapi import APIRouter, Request, Depends
from fastapi.templating import Jinja2Templates
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
from typing import Optional
from core.database import get_db
from core.base_repository import BaseRepository
from core.sidebar import get_sidebar_data
router = APIRouter(prefix="/eisenhower", tags=["eisenhower"])
templates = Jinja2Templates(directory="templates")
@router.get("/")
async def eisenhower_matrix(
request: Request,
domain_id: Optional[str] = None,
project_id: Optional[str] = None,
status: Optional[str] = None,
context: Optional[str] = None,
db: AsyncSession = Depends(get_db),
):
sidebar = await get_sidebar_data(db)
where_clauses = [
"t.is_deleted = false",
"t.status IN ('open', 'in_progress', 'blocked')",
]
params = {}
if domain_id:
where_clauses.append("t.domain_id = :domain_id")
params["domain_id"] = domain_id
if project_id:
where_clauses.append("t.project_id = :project_id")
params["project_id"] = project_id
if status:
where_clauses.append("t.status = :status")
params["status"] = status
if context:
where_clauses.append("t.context = :context")
params["context"] = context
where_sql = " AND ".join(where_clauses)
result = await db.execute(text(f"""
SELECT t.id, t.title, t.priority, t.status, t.due_date,
t.context, t.estimated_minutes,
p.name as project_name,
d.name as domain_name, d.color as domain_color
FROM tasks t
LEFT JOIN projects p ON t.project_id = p.id
LEFT JOIN domains d ON t.domain_id = d.id
WHERE {where_sql}
ORDER BY t.priority, t.due_date NULLS LAST, t.title
"""), params)
tasks = [dict(r._mapping) for r in result]
# Classify into quadrants
from datetime import date, timedelta
today = date.today()
urgent_cutoff = today + timedelta(days=7)
quadrants = {
"do_first": [],
"schedule": [],
"delegate": [],
"eliminate": [],
}
for t in tasks:
important = t["priority"] in (1, 2)
urgent = (
t["due_date"] is not None
and t["due_date"] <= urgent_cutoff
)
if important and urgent:
quadrants["do_first"].append(t)
elif important and not urgent:
quadrants["schedule"].append(t)
elif not important and urgent:
quadrants["delegate"].append(t)
else:
quadrants["eliminate"].append(t)
counts = {k: len(v) for k, v in quadrants.items()}
total = sum(counts.values())
# Filter options
domains_repo = BaseRepository("domains", db)
domains = await domains_repo.list()
projects_repo = BaseRepository("projects", db)
projects = await projects_repo.list()
result = await db.execute(text(
"SELECT value, label FROM context_types WHERE is_deleted = false ORDER BY sort_order"
))
context_types = [dict(r._mapping) for r in result]
return templates.TemplateResponse("eisenhower.html", {
"request": request,
"sidebar": sidebar,
"quadrants": quadrants,
"counts": counts,
"total": total,
"today": today,
"domains": domains,
"projects": projects,
"context_types": context_types,
"current_domain_id": domain_id or "",
"current_project_id": project_id or "",
"current_status": status or "",
"current_context": context or "",
"page_title": "Eisenhower Matrix",
"active_nav": "eisenhower",
})