feat(project): 서브프로젝트 제거, 사이드 트리를 프로젝트→업무 2단계로

요구에 따라 서브프로젝트 개념을 UI 에서 제거. 좌측 트리를 프로젝트(최상위)
→ 그 안의 업무 2단계로 재구성:
- 프로젝트 노드(folder) 펼치면 소속 업무가 체크 아이콘으로 표시
- 업무 클릭 → /project/p/{id}?task={tid} 로 이동 후 해당 업무 모달 자동 오픈
- 완료 업무는 취소선. 트리에서 프로젝트 삭제(관리자)
- 과거 parent_id 서브프로젝트는 트리/홈에서 숨김(최상위만 노출)
- 홈 카드의 '서브 N' 칩 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 13:20:28 +09:00
parent a22bb1e043
commit 03916a61b1
6 changed files with 84 additions and 64 deletions
+23 -5
View File
@@ -356,12 +356,31 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse:
include_archived=False,
member_email=None if admin else user["email"],
)
tree = _build_tree(all_projects)
stages = st.list_stages(project_id=project_id)
tasks = st.list_tasks(project_id=project_id, include_subprojects=True, limit=2000)
tasks = st.list_tasks(project_id=project_id, limit=2000)
members = st.list_members(project_id=project_id)
can_manage = _can_manage(request, st, user, project_id)
subprojects = [p for p in all_projects if p.get("parent_id") == project_id]
# 좌측 트리: 프로젝트(최상위) → 그 안의 업무. (서브프로젝트 개념 제거)
nav_projects: list[dict[str, Any]] = []
for p in all_projects:
if p.get("parent_id"):
continue # 과거 서브프로젝트는 트리에서 제외
p_tasks = (
tasks if p["id"] == project_id
else st.list_tasks(project_id=p["id"], limit=500)
)
nav_projects.append({
"id": p["id"],
"name": p["name"],
"color": p.get("color") or "#4573d2",
"tasks": [
{"id": t["id"], "title": t["title"],
"color": t.get("project_color") or p.get("color") or "#4573d2",
"completed": bool(t.get("completed_at"))}
for t in p_tasks
],
})
# 달력(휴가식 월간 그리드) — ?y=&m= 로 월 이동, 기본 이번 달
today = today_kst()
@@ -387,11 +406,10 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse:
"page_title": project["name"],
"page_subtitle": project.get("description") or "프로젝트 보드",
"project": project,
"tree": tree,
"nav_projects": nav_projects,
"stages": stages,
"tasks": tasks,
"members": members,
"subprojects": subprojects,
"priority_labels": store.PRIORITY_LABELS,
"color_palette": list(store.COLOR_PALETTE),
"today": today.isoformat(),