feat: return-to-project redirects from create/edit forms

When creating or editing items from a project detail tab, users now
return to that project's tab instead of the entity's own page.
Edit links pass from_project param; forms include hidden field.
Reassigning to a different project redirects to the new project.
Decisions/meetings create from project context inserts junction rows.
File uploads from project context redirect back to project files tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 18:28:15 +00:00
parent ba9c36e691
commit c7a07ed280
18 changed files with 125 additions and 27 deletions

View File

@@ -57,6 +57,7 @@ async def create_form(
request: Request,
meeting_id: Optional[str] = None,
task_id: Optional[str] = None,
project_id: Optional[str] = None,
db: AsyncSession = Depends(get_db),
):
sidebar = await get_sidebar_data(db)
@@ -74,6 +75,7 @@ async def create_form(
"item": None,
"prefill_meeting_id": meeting_id or "",
"prefill_task_id": task_id or "",
"prefill_project_id": project_id or "",
})
@@ -87,6 +89,7 @@ async def create_decision(
decided_at: Optional[str] = Form(None),
meeting_id: Optional[str] = Form(None),
task_id: Optional[str] = Form(None),
project_id: Optional[str] = Form(None),
tags: Optional[str] = Form(None),
db: AsyncSession = Depends(get_db),
):
@@ -105,8 +108,18 @@ async def create_decision(
data["tags"] = [t.strip() for t in tags.split(",") if t.strip()]
decision = await repo.create(data)
# Link to project if created from project context
if project_id and project_id.strip():
await db.execute(text("""
INSERT INTO decision_projects (decision_id, project_id)
VALUES (:did, :pid) ON CONFLICT DO NOTHING
"""), {"did": decision["id"], "pid": project_id})
if task_id and task_id.strip():
return RedirectResponse(url=f"/tasks/{task_id}?tab=decisions", status_code=303)
if project_id and project_id.strip():
return RedirectResponse(url=f"/projects/{project_id}?tab=decisions", status_code=303)
return RedirectResponse(url=f"/decisions/{decision['id']}", status_code=303)
@@ -152,7 +165,7 @@ async def decision_detail(decision_id: str, request: Request, db: AsyncSession =
@router.get("/{decision_id}/edit")
async def edit_form(decision_id: str, request: Request, db: AsyncSession = Depends(get_db)):
async def edit_form(decision_id: str, request: Request, from_project: Optional[str] = None, db: AsyncSession = Depends(get_db)):
repo = BaseRepository("decisions", db)
sidebar = await get_sidebar_data(db)
item = await repo.get(decision_id)
@@ -178,6 +191,7 @@ async def edit_form(decision_id: str, request: Request, db: AsyncSession = Depen
"page_title": "Edit Decision", "active_nav": "decisions",
"item": item,
"prefill_meeting_id": "",
"from_project": from_project or "",
})
@@ -192,6 +206,7 @@ async def update_decision(
meeting_id: Optional[str] = Form(None),
superseded_by_id: Optional[str] = Form(None),
tags: Optional[str] = Form(None),
from_project: Optional[str] = Form(None),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("decisions", db)
@@ -208,6 +223,9 @@ async def update_decision(
data["tags"] = None
await repo.update(decision_id, data)
if from_project and from_project.strip():
return RedirectResponse(url=f"/projects/{from_project}?tab=decisions", status_code=303)
return RedirectResponse(url=f"/decisions/{decision_id}", status_code=303)