diff --git a/app/modules/project/router.py b/app/modules/project/router.py index 2d37f8d..30645ad 100644 --- a/app/modules/project/router.py +++ b/app/modules/project/router.py @@ -457,6 +457,7 @@ async def index(request: Request) -> HTMLResponse: "nav_items": build_erp_nav(user, active="project"), "page_title": "프로젝트 관리", "page_subtitle": "달력·타임라인·보드로 프로젝트를 관리하세요.", + "force_sidebar_collapsed": True, "active_projects": active_projects, "done_projects": done_projects, "my_projects": my_projects, @@ -552,6 +553,26 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse: if u.get("picture") } + # 우측 "내 업무" 패널 — 홈 화면과 동일한 쿼리/그룹핑(프로젝트별)을 재사용. + my_tasks = st.list_tasks(assignee_email=user["email"], limit=500) + for t in my_tasks: + t["start_label"] = _fmt_date_kr(t.get("start_date")) + t["due_label"] = _fmt_date_kr(t.get("due_date")) + my_groups: dict[int, dict[str, Any]] = {} + for t in my_tasks: + pid = t.get("project_id") + g = my_groups.get(pid) + if g is None: + g = { + "id": pid, + "name": t.get("project_name") or "프로젝트", + "color": t.get("project_color") or "#4573d2", + "tasks": [], + } + my_groups[pid] = g + g["tasks"].append(t) + my_projects = list(my_groups.values()) + # 달력(휴가식 월간 그리드) — ?y=&m= 로 월 이동, 기본 이번 달 today = today_kst() try: @@ -575,8 +596,11 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse: "nav_items": build_erp_nav(user, active="project"), "page_title": project["name"], "page_subtitle": project.get("description") or "프로젝트 보드", + "force_sidebar_collapsed": True, "project": project, "current_project_id": project_id, + "my_projects": my_projects, + "my_tasks": my_tasks, "nav_projects": nav_projects, "stages": stages, "tasks": all_tasks, @@ -1331,6 +1355,7 @@ async def inbox_page(request: Request) -> HTMLResponse: "nav_items": build_erp_nav(user, active="project"), "page_title": "알림센터", "page_subtitle": "내게 온 프로젝트 알림", + "force_sidebar_collapsed": True, "notifications": items, }, ) diff --git a/app/modules/project/templates/project/inbox.html b/app/modules/project/templates/project/inbox.html index e4676fd..50496e6 100644 --- a/app/modules/project/templates/project/inbox.html +++ b/app/modules/project/templates/project/inbox.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + {% endblock %} @@ -45,5 +45,5 @@ {% endif %} - + {% endblock %} diff --git a/app/modules/project/templates/project/index.html b/app/modules/project/templates/project/index.html index c8a1d51..e81f37a 100644 --- a/app/modules/project/templates/project/index.html +++ b/app/modules/project/templates/project/index.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + {% endblock %} @@ -376,5 +376,5 @@ - + {% endblock %} diff --git a/app/modules/project/templates/project/project.html b/app/modules/project/templates/project/project.html index 87dab93..ceec7dd 100644 --- a/app/modules/project/templates/project/project.html +++ b/app/modules/project/templates/project/project.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + @@ -152,6 +152,50 @@ + + + @@ -324,10 +368,11 @@ "projects": {{ projects_meta | tojson }}, "stagesByProject": {{ stages_by_project | tojson }}, "boardStages": {{ board_stage_names | tojson }}, - "avatars": {{ avatars | tojson }} + "avatars": {{ avatars | tojson }}, + "myTasks": {{ my_tasks | tojson }} } - + {% endblock %} diff --git a/app/static/project.css b/app/static/project.css index a2d46cc..ed656a8 100644 --- a/app/static/project.css +++ b/app/static/project.css @@ -126,8 +126,17 @@ .pj-mytask-title { font-size: 13.5px; font-weight: 600; } .pj-mytask-meta { margin-left: auto; font-size: 11px; color: #9aa1a9; } -/* ── 프로젝트 페이지 레이아웃 ── */ -.pj-layout { display: grid; grid-template-columns: 230px 1fr; gap: 22px; align-items: start; } +/* ── 프로젝트 페이지 레이아웃 (좌: 프로젝트 트리 / 가운데: 뷰 / 우: 내 업무) ── + 가운데 칸에 min-width:0 이 없으면 보드에 세션이 많이 늘어날 때 grid 트랙 + 자체가 넓어져 페이지 전체가 가로 스크롤된다(overflow-x:auto 는 .pj-board + 안에서만 작동해야 한다) — 반드시 필요한 규칙. */ +.pj-layout { display: grid; grid-template-columns: 230px 1fr 260px; gap: 22px; align-items: start; } +.pj-main { min-width: 0; } +.pj-project-mytasks { min-width: 0; } +@media (max-width: 1280px) { + .pj-layout { grid-template-columns: 230px 1fr; } + .pj-project-mytasks { display: none; } +} @media (max-width: 900px) { .pj-layout { grid-template-columns: 1fr; } } .pj-side-block { margin-bottom: 22px; } diff --git a/app/static/project.js b/app/static/project.js index 4f649c1..f45147e 100644 --- a/app/static/project.js +++ b/app/static/project.js @@ -614,6 +614,17 @@ const stagesByProject = data.stagesByProject || {}; const boardStages = data.boardStages || []; const avatars = data.avatars || {}; + const myTasksById = {}; + (data.myTasks || []).forEach(function (t) { myTasksById[t.id] = t; }); + // 우측 "내 업무" 패널 — openTaskModal 은 이 함수 뒤쪽에서 선언되지만 + // function 선언은 호이스팅되어 여기서 참조해도 문제없다. + document.querySelectorAll(".pj-project-mytasks .pj-task-popup-link").forEach(function (a) { + a.addEventListener("click", function (e) { + e.preventDefault(); + const t = myTasksById[a.dataset.taskId]; + if (t) openTaskModal(t); + }); + }); // 담당자/작성자 아이콘 = 구글 프로필 이미지(원형). 없으면 기본 아이콘. function avatarHtml(email, cls) { const url = email ? avatars[String(email).toLowerCase()] : ""; diff --git a/app/templates/erp_base.html b/app/templates/erp_base.html index 8929d05..1470b7f 100644 --- a/app/templates/erp_base.html +++ b/app/templates/erp_base.html @@ -15,7 +15,11 @@