diff --git a/routers/projects.py b/routers/projects.py index 38b5583..e8e50bf 100644 --- a/routers/projects.py +++ b/routers/projects.py @@ -419,25 +419,108 @@ async def remove_contact( return RedirectResponse(url=f"/projects/{project_id}?tab=contacts", status_code=303) -# ---- Link unlinking ---- +# ---- Entity unlink / delete from project tabs ---- +# Tasks (FK: tasks.project_id) +@router.post("/{project_id}/tasks/{task_id}/unlink") +async def unlink_task(project_id: str, task_id: str, db=Depends(get_db)): + await db.execute(text( + "UPDATE tasks SET project_id = NULL, updated_at = now() WHERE id = :tid AND project_id = :pid" + ), {"tid": task_id, "pid": project_id}) + await db.commit() + return RedirectResponse(url=f"/projects/{project_id}?tab=tasks", status_code=303) + +@router.post("/{project_id}/tasks/{task_id}/delete") +async def delete_project_task(project_id: str, task_id: str, db=Depends(get_db)): + await BaseRepository("tasks", db).soft_delete(task_id) + return RedirectResponse(url=f"/projects/{project_id}?tab=tasks", status_code=303) + +# Notes (FK: notes.project_id) +@router.post("/{project_id}/notes/{note_id}/unlink") +async def unlink_note(project_id: str, note_id: str, db=Depends(get_db)): + await db.execute(text( + "UPDATE notes SET project_id = NULL, updated_at = now() WHERE id = :nid AND project_id = :pid" + ), {"nid": note_id, "pid": project_id}) + await db.commit() + return RedirectResponse(url=f"/projects/{project_id}?tab=notes", status_code=303) + +@router.post("/{project_id}/notes/{note_id}/delete") +async def delete_project_note(project_id: str, note_id: str, db=Depends(get_db)): + await BaseRepository("notes", db).soft_delete(note_id) + return RedirectResponse(url=f"/projects/{project_id}?tab=notes", status_code=303) + +# Links (FK: links.project_id) @router.post("/{project_id}/links/{link_id}/unlink") -async def unlink_link( - project_id: str, link_id: str, - db: AsyncSession = Depends(get_db), -): +async def unlink_link(project_id: str, link_id: str, db=Depends(get_db)): await db.execute(text( "UPDATE links SET project_id = NULL, updated_at = now() WHERE id = :lid AND project_id = :pid" ), {"lid": link_id, "pid": project_id}) await db.commit() return RedirectResponse(url=f"/projects/{project_id}?tab=links", status_code=303) - @router.post("/{project_id}/links/{link_id}/delete") -async def delete_project_link( - project_id: str, link_id: str, - db: AsyncSession = Depends(get_db), -): - repo = BaseRepository("links", db) - await repo.soft_delete(link_id) +async def delete_project_link(project_id: str, link_id: str, db=Depends(get_db)): + await BaseRepository("links", db).soft_delete(link_id) return RedirectResponse(url=f"/projects/{project_id}?tab=links", status_code=303) + +# Files (junction: file_mappings) +@router.post("/{project_id}/files/{file_id}/unlink") +async def unlink_file(project_id: str, file_id: str, db=Depends(get_db)): + await db.execute(text( + "DELETE FROM file_mappings WHERE file_id = :fid AND context_type = 'project' AND context_id = :pid::uuid" + ), {"fid": file_id, "pid": project_id}) + await db.commit() + return RedirectResponse(url=f"/projects/{project_id}?tab=files", status_code=303) + +@router.post("/{project_id}/files/{file_id}/delete") +async def delete_project_file(project_id: str, file_id: str, db=Depends(get_db)): + await BaseRepository("files", db).soft_delete(file_id) + return RedirectResponse(url=f"/projects/{project_id}?tab=files", status_code=303) + +# Lists (FK: lists.project_id) +@router.post("/{project_id}/lists/{list_id}/unlink") +async def unlink_list(project_id: str, list_id: str, db=Depends(get_db)): + await db.execute(text( + "UPDATE lists SET project_id = NULL, updated_at = now() WHERE id = :lid AND project_id = :pid" + ), {"lid": list_id, "pid": project_id}) + await db.commit() + return RedirectResponse(url=f"/projects/{project_id}?tab=lists", status_code=303) + +@router.post("/{project_id}/lists/{list_id}/delete") +async def delete_project_list(project_id: str, list_id: str, db=Depends(get_db)): + await BaseRepository("lists", db).soft_delete(list_id) + return RedirectResponse(url=f"/projects/{project_id}?tab=lists", status_code=303) + +# Decisions (junction: decision_projects) +@router.post("/{project_id}/decisions/{decision_id}/unlink") +async def unlink_decision(project_id: str, decision_id: str, db=Depends(get_db)): + await db.execute(text( + "DELETE FROM decision_projects WHERE decision_id = :did AND project_id = :pid" + ), {"did": decision_id, "pid": project_id}) + await db.commit() + return RedirectResponse(url=f"/projects/{project_id}?tab=decisions", status_code=303) + +@router.post("/{project_id}/decisions/{decision_id}/delete") +async def delete_project_decision(project_id: str, decision_id: str, db=Depends(get_db)): + await BaseRepository("decisions", db).soft_delete(decision_id) + return RedirectResponse(url=f"/projects/{project_id}?tab=decisions", status_code=303) + +# Meetings (junction: project_meetings) — remove already exists, add delete +@router.post("/{project_id}/meetings/{meeting_id}/delete") +async def delete_project_meeting(project_id: str, meeting_id: str, db=Depends(get_db)): + await BaseRepository("meetings", db).soft_delete(meeting_id) + return RedirectResponse(url=f"/projects/{project_id}?tab=meetings", status_code=303) + +# Focus (FK: daily_focus.project_id) +@router.post("/{project_id}/focus/{focus_id}/unlink") +async def unlink_focus(project_id: str, focus_id: str, db=Depends(get_db)): + await db.execute(text( + "UPDATE daily_focus SET project_id = NULL, updated_at = now() WHERE id = :fid AND project_id = :pid" + ), {"fid": focus_id, "pid": project_id}) + await db.commit() + return RedirectResponse(url=f"/projects/{project_id}?tab=focus", status_code=303) + +@router.post("/{project_id}/focus/{focus_id}/delete") +async def delete_project_focus(project_id: str, focus_id: str, db=Depends(get_db)): + await BaseRepository("daily_focus", db).soft_delete(focus_id) + return RedirectResponse(url=f"/projects/{project_id}?tab=focus", status_code=303) diff --git a/templates/project_detail.html b/templates/project_detail.html index e76f6d2..0713630 100644 --- a/templates/project_detail.html +++ b/templates/project_detail.html @@ -68,6 +68,12 @@ {{ t.status|replace('_', ' ') }}
Edit +
+ +
+
+ +
{% else %} @@ -80,6 +86,15 @@
{{ n.title }} {{ n.updated_at.strftime('%Y-%m-%d') if n.updated_at else '' }} +
+ Edit +
+ +
+
+ +
+
{% else %}
No notes yet
@@ -111,6 +126,14 @@
{{ f.original_filename }} {{ f.created_at.strftime('%Y-%m-%d') if f.created_at else '' }} +
+
+ +
+
+ +
+
{% else %}
No files attached to this project
@@ -122,6 +145,15 @@
{{ l.name }} {{ l.item_count }} items +
+ Edit +
+ +
+
+ +
+
{% else %}
No lists linked to this project
@@ -133,6 +165,15 @@
{{ d.title }} {{ d.status }} +
+ Edit +
+ +
+
+ +
+
{% else %}
No decisions linked to this project
@@ -160,8 +201,12 @@ {{ m.meeting_date }} {{ m.status }}
+ Edit
- + +
+
+
@@ -172,11 +217,17 @@ {% elif tab == 'focus' %} {% for f in tab_data %}
- {{ f.title }} + {{ f.title }} {% if f.domain_name %}{{ f.domain_name }}{% endif %} {{ f.created_at.strftime('%Y-%m-%d') if f.created_at else '' }}
Edit +
+ +
+
+ +
{% else %} @@ -211,8 +262,12 @@ {% if c.role %}{{ c.role }}{% endif %} {{ c.linked_at.strftime('%Y-%m-%d') if c.linked_at else '' }}
+ Edit
- + +
+
+