feat(project): 달력·타임라인·보드·리스트 4개 뷰를 전체 프로젝트 기준으로 표시
- project_page 가 접근 가능한 모든 프로젝트의 업무를 집계(all_tasks)
- 달력: 전 프로젝트 bar(라벨에 [프로젝트명] 유지)
- 타임라인: 프로젝트별 행(그룹) + 행마다 전체기간 배경 + 업무 막대
- 보드: 단계 '이름' 기준 컬럼으로 전 프로젝트 묶음, 카드에 프로젝트 배지
드롭 시 해당 업무 프로젝트의 동일 이름 단계로 이동
- 리스트: '프로젝트' 컬럼 추가
- 모달 단계 선택은 해당 업무 프로젝트의 단계로 채움(타 프로젝트 편집 대비)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -376,15 +376,28 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse:
|
||||
members = st.list_members(project_id=project_id)
|
||||
can_manage = _can_manage(request, st, user, project_id)
|
||||
|
||||
# 좌측 트리: 프로젝트(최상위) → 그 안의 업무. (서브프로젝트 개념 제거)
|
||||
# 좌측 트리 + 전체 집계: 4개 뷰(달력/타임라인/보드/리스트)는 '전체 프로젝트'를 보여준다.
|
||||
# all_tasks : 접근 가능한 모든 프로젝트의 업무(뷰 데이터)
|
||||
# stages_by_project : 프로젝트별 단계(보드 이름→단계 매핑 + 모달 단계 선택용)
|
||||
# projects_meta : 프로젝트 메타(타임라인 그룹/보드 카드 배지용)
|
||||
nav_projects: list[dict[str, Any]] = []
|
||||
all_tasks: list[dict[str, Any]] = []
|
||||
stages_by_project: dict[int, list[dict[str, Any]]] = {}
|
||||
projects_meta: 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)
|
||||
else st.list_tasks(project_id=p["id"], limit=2000)
|
||||
)
|
||||
all_tasks.extend(p_tasks)
|
||||
stages_by_project[p["id"]] = st.list_stages(project_id=p["id"])
|
||||
projects_meta.append({
|
||||
"id": p["id"],
|
||||
"name": p["name"],
|
||||
"color": p.get("color") or "#4573d2",
|
||||
})
|
||||
nav_projects.append({
|
||||
"id": p["id"],
|
||||
"name": p["name"],
|
||||
@@ -397,6 +410,15 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse:
|
||||
],
|
||||
})
|
||||
|
||||
# 보드 컬럼 = 전체 프로젝트의 단계 '이름' 묶음(기본 4단계 순서를 우선).
|
||||
_order = {n: i for i, (n, _d) in enumerate(store.DEFAULT_STAGES)}
|
||||
board_stage_names: list[str] = []
|
||||
for slist in stages_by_project.values():
|
||||
for s in slist:
|
||||
if s["name"] not in board_stage_names:
|
||||
board_stage_names.append(s["name"])
|
||||
board_stage_names.sort(key=lambda n: (_order.get(n, 999), n))
|
||||
|
||||
# 달력(휴가식 월간 그리드) — ?y=&m= 로 월 이동, 기본 이번 달
|
||||
today = today_kst()
|
||||
try:
|
||||
@@ -406,7 +428,7 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse:
|
||||
year, month = today.year, today.month
|
||||
except (TypeError, ValueError):
|
||||
year, month = today.year, today.month
|
||||
weeks = _build_task_calendar(tasks, year, month)
|
||||
weeks = _build_task_calendar(all_tasks, year, month)
|
||||
prev_y, prev_m = (year - 1, 12) if month == 1 else (year, month - 1)
|
||||
next_y, next_m = (year + 1, 1) if month == 12 else (year, month + 1)
|
||||
|
||||
@@ -423,8 +445,11 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse:
|
||||
"project": project,
|
||||
"nav_projects": nav_projects,
|
||||
"stages": stages,
|
||||
"tasks": tasks,
|
||||
"tasks": all_tasks,
|
||||
"members": members,
|
||||
"projects_meta": projects_meta,
|
||||
"stages_by_project": stages_by_project,
|
||||
"board_stage_names": board_stage_names,
|
||||
"priority_labels": store.PRIORITY_LABELS,
|
||||
"color_palette": list(store.COLOR_PALETTE),
|
||||
"today": today.isoformat(),
|
||||
|
||||
Reference in New Issue
Block a user