feat: make focus items permanent — no date scoping
Focus items now persist until explicitly removed. Removed date filtering from main query, dedup subqueries, reorder, and sidebar badge count. Removed date navigation from UI. The focus_date column remains as metadata but no longer affects display. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Daily Focus: date-scoped task/list-item commitment list."""
|
||||
"""Focus: persistent commitment list — items stay until removed."""
|
||||
|
||||
from fastapi import APIRouter, Request, Form, Depends
|
||||
from fastapi.templating import Jinja2Templates
|
||||
@@ -21,7 +21,6 @@ templates.env.filters["autolink"] = autolink
|
||||
@router.get("/")
|
||||
async def focus_view(
|
||||
request: Request,
|
||||
focus_date: Optional[str] = None,
|
||||
domain_id: Optional[str] = None,
|
||||
area_id: Optional[str] = None,
|
||||
project_id: Optional[str] = None,
|
||||
@@ -30,11 +29,10 @@ async def focus_view(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
sidebar = await get_sidebar_data(db)
|
||||
target_date = date.fromisoformat(focus_date) if focus_date else date.today()
|
||||
if not source_type:
|
||||
source_type = "tasks"
|
||||
|
||||
# --- Focus items (both tasks and list items) ---
|
||||
# --- All active focus items ---
|
||||
result = await db.execute(text("""
|
||||
SELECT df.*,
|
||||
t.title as title, t.priority, t.status as task_status,
|
||||
@@ -60,11 +58,11 @@ async def focus_view(
|
||||
LEFT JOIN projects lp ON l.project_id = lp.id
|
||||
LEFT JOIN domains ld ON l.domain_id = ld.id
|
||||
LEFT JOIN areas la ON l.area_id = la.id
|
||||
WHERE df.focus_date = :target_date AND df.is_deleted = false
|
||||
WHERE df.is_deleted = false
|
||||
AND (t.id IS NULL OR t.is_deleted = false)
|
||||
AND (li.id IS NULL OR li.is_deleted = false)
|
||||
ORDER BY df.sort_order, df.created_at
|
||||
"""), {"target_date": target_date})
|
||||
"""))
|
||||
items = [dict(r._mapping) for r in result]
|
||||
|
||||
# Group items by domain only — area/project shown as inline columns
|
||||
@@ -86,9 +84,9 @@ async def focus_view(
|
||||
avail_where = [
|
||||
"t.is_deleted = false",
|
||||
"t.status NOT IN ('done', 'cancelled')",
|
||||
"t.id NOT IN (SELECT task_id FROM daily_focus WHERE focus_date = :target_date AND is_deleted = false AND task_id IS NOT NULL)",
|
||||
"t.id NOT IN (SELECT task_id FROM daily_focus WHERE is_deleted = false AND task_id IS NOT NULL)",
|
||||
]
|
||||
avail_params = {"target_date": target_date}
|
||||
avail_params = {}
|
||||
|
||||
if search:
|
||||
avail_where.append("t.title ILIKE :search")
|
||||
@@ -123,9 +121,9 @@ async def focus_view(
|
||||
"li.is_deleted = false",
|
||||
"li.completed = false",
|
||||
"l.is_deleted = false",
|
||||
"li.id NOT IN (SELECT list_item_id FROM daily_focus WHERE focus_date = :target_date AND is_deleted = false AND list_item_id IS NOT NULL)",
|
||||
"li.id NOT IN (SELECT list_item_id FROM daily_focus WHERE is_deleted = false AND list_item_id IS NOT NULL)",
|
||||
]
|
||||
li_params = {"target_date": target_date}
|
||||
li_params = {}
|
||||
|
||||
if search:
|
||||
li_where.append("li.content ILIKE :search")
|
||||
@@ -169,7 +167,6 @@ async def focus_view(
|
||||
"items": items, "hierarchy": hierarchy,
|
||||
"available_tasks": available_tasks,
|
||||
"available_list_items": available_list_items,
|
||||
"focus_date": target_date,
|
||||
"total_estimated": total_est,
|
||||
"domains": domains, "areas": areas, "projects": projects,
|
||||
"current_domain_id": domain_id or "",
|
||||
@@ -177,29 +174,27 @@ async def focus_view(
|
||||
"current_project_id": project_id or "",
|
||||
"current_search": search or "",
|
||||
"current_source_type": source_type,
|
||||
"page_title": "Daily Focus", "active_nav": "focus",
|
||||
"page_title": "Focus", "active_nav": "focus",
|
||||
})
|
||||
|
||||
|
||||
@router.post("/add")
|
||||
async def add_to_focus(
|
||||
request: Request,
|
||||
focus_date: str = Form(...),
|
||||
task_id: Optional[str] = Form(None),
|
||||
list_item_id: Optional[str] = Form(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
repo = BaseRepository("daily_focus", db)
|
||||
parsed_date = date.fromisoformat(focus_date)
|
||||
# Get next sort order
|
||||
result = await db.execute(text("""
|
||||
SELECT COALESCE(MAX(sort_order), 0) + 10 FROM daily_focus
|
||||
WHERE focus_date = :fd AND is_deleted = false
|
||||
"""), {"fd": parsed_date})
|
||||
WHERE is_deleted = false
|
||||
"""))
|
||||
next_order = result.scalar()
|
||||
|
||||
data = {
|
||||
"focus_date": parsed_date,
|
||||
"focus_date": date.today(),
|
||||
"sort_order": next_order,
|
||||
"completed": False,
|
||||
}
|
||||
@@ -209,7 +204,7 @@ async def add_to_focus(
|
||||
data["list_item_id"] = list_item_id
|
||||
|
||||
await repo.create(data)
|
||||
return RedirectResponse(url=f"/focus?focus_date={focus_date}", status_code=303)
|
||||
return RedirectResponse(url="/focus", status_code=303)
|
||||
|
||||
|
||||
@router.post("/reorder")
|
||||
@@ -217,27 +212,24 @@ async def reorder_focus(
|
||||
request: Request,
|
||||
item_id: str = Form(...),
|
||||
direction: str = Form(...),
|
||||
focus_date: str = Form(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
repo = BaseRepository("daily_focus", db)
|
||||
parsed_date = date.fromisoformat(focus_date)
|
||||
await repo.move_in_order(item_id, direction, filters={"focus_date": parsed_date})
|
||||
return RedirectResponse(url=f"/focus?focus_date={focus_date}", status_code=303)
|
||||
await repo.move_in_order(item_id, direction)
|
||||
return RedirectResponse(url="/focus", status_code=303)
|
||||
|
||||
|
||||
@router.post("/reorder-all")
|
||||
async def reorder_all_focus(
|
||||
request: Request,
|
||||
item_ids: str = Form(...),
|
||||
focus_date: str = Form(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
repo = BaseRepository("daily_focus", db)
|
||||
ids = [i.strip() for i in item_ids.split(",") if i.strip()]
|
||||
if ids:
|
||||
await repo.reorder(ids)
|
||||
return RedirectResponse(url=f"/focus?focus_date={focus_date}", status_code=303)
|
||||
return RedirectResponse(url="/focus", status_code=303)
|
||||
|
||||
|
||||
@router.post("/{focus_id}/toggle")
|
||||
@@ -265,14 +257,11 @@ async def toggle_focus(focus_id: str, request: Request, db: AsyncSession = Depen
|
||||
"UPDATE list_items SET completed = false, completed_at = NULL, updated_at = :now WHERE id = :id"
|
||||
), {"id": item["list_item_id"], "now": now})
|
||||
await db.commit()
|
||||
focus_date = item["focus_date"] if item else date.today()
|
||||
return RedirectResponse(url=f"/focus?focus_date={focus_date}", status_code=303)
|
||||
return RedirectResponse(url="/focus", status_code=303)
|
||||
|
||||
|
||||
@router.post("/{focus_id}/remove")
|
||||
async def remove_from_focus(focus_id: str, request: Request, db: AsyncSession = Depends(get_db)):
|
||||
repo = BaseRepository("daily_focus", db)
|
||||
item = await repo.get(focus_id)
|
||||
await repo.soft_delete(focus_id)
|
||||
focus_date = item["focus_date"] if item else date.today()
|
||||
return RedirectResponse(url=f"/focus?focus_date={focus_date}", status_code=303)
|
||||
return RedirectResponse(url="/focus", status_code=303)
|
||||
|
||||
Reference in New Issue
Block a user