feat(project): 내 업무 패널에 프로젝트별 전체/진행/완료 개수 배지

프로젝트 이름 오른쪽에 "전체5 진행2 완료3" 형식으로 표시, 숫자는 색깔
있는 동그라미 안에(전체=회색, 진행=파랑, 완료=초록). 서버 렌더용 카운트는
_group_my_tasks() 헬퍼로 통합(홈/프로젝트 화면 중복 제거), 실시간 갱신은
DOM의 실제 <li> 개수를 다시 세는 updateMyTreeCounts()를 기존 sync 함수
(syncMyTaskPanel/syncProjectMyTaskPanel) 안에서 호출해 처리.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 14:30:34 +09:00
parent 397ca8684a
commit cc73f79b7c
6 changed files with 87 additions and 39 deletions
+32 -4
View File
@@ -157,6 +157,22 @@
return "rgba(" + r + "," + g + "," + b + "," + alpha + ")";
}
// "내 업무" 그룹 헤더의 전체/진행/완료 개수 배지 — 그 그룹 안의 실제
// <li> 개수를 다시 세어 채운다(서버 재조회 없이, DOM 이 곧 정답).
function updateMyTreeCounts(groupEl) {
if (!groupEl) return;
const items = groupEl.querySelectorAll(".pj-mytree-tasks > .pj-mytree-task");
let done = 0;
items.forEach(function (li) { if (li.classList.contains("is-done")) done++; });
const total = items.length;
const totalEl = groupEl.querySelector(".pj-mtc-total");
const openEl = groupEl.querySelector(".pj-mtc-open");
const doneEl = groupEl.querySelector(".pj-mtc-done");
if (totalEl) totalEl.textContent = total;
if (openEl) openEl.textContent = total - done;
if (doneEl) doneEl.textContent = done;
}
// 업무 모달(#pj-modal-task) 크기 — 기존 업무 편집은 하위업무/첨부/연결/
// 댓글까지 보여야 해서 800x1100 고정, "새 업무" 작성은 그 칸들이 전부
// 숨는 홑겹 폼이라 480x700 로 작게(홈 카드·프로젝트 화면 양쪽에서 공용).
@@ -816,12 +832,17 @@
// "내 업무" 패널(우측) 동기화 — 이 프로젝트에 내 업무 그룹이 이미 있을
// 때만 patch 한다(그룹 자체가 없던 경우는 드물어 다음 새로고침까지 생략).
function syncMyTaskPanel(t, removed) {
const group = document.querySelector('.pj-mytree-group[data-group-id="' + t.project_id + '"] .pj-mytree-tasks');
const groupEl = document.querySelector('.pj-mytree-group[data-group-id="' + t.project_id + '"]');
const group = groupEl ? groupEl.querySelector(".pj-mytree-tasks") : null;
if (!group) return;
const isMine = !removed && t.assignee_email && String(t.assignee_email).toLowerCase() === me;
const existingLink = group.querySelector('.pj-task-popup-link[data-task-id="' + t.id + '"]');
const existingLi = existingLink ? existingLink.closest("li") : null;
if (!isMine) { if (existingLi) existingLi.remove(); return; }
if (!isMine) {
if (existingLi) existingLi.remove();
updateMyTreeCounts(groupEl);
return;
}
const dates = t.start_date || t.due_date
? '<span class="pj-mytree-dates">' +
(t.start_date && t.due_date ? fmtDateKr(t.start_date) + " ~ " + fmtDateKr(t.due_date)
@@ -844,6 +865,7 @@
wireHomeTaskPopupLink(newLi.querySelector(".pj-task-popup-link"), t);
if (cmt) cmt.wireBadge(newLi.querySelector(".pj-cmt-badge"));
if (existingLi) existingLi.replaceWith(newLi); else group.appendChild(newLi);
updateMyTreeCounts(groupEl);
}
// ── 완료 프로젝트 섹션 접기/펼치기 (localStorage 기억) ──
@@ -1431,17 +1453,23 @@
"</a></li>";
}
function syncProjectMyTaskPanel(t, removed) {
const group = document.querySelector('.pj-project-mytasks .pj-mytree-group[data-group-id="' + t.project_id + '"] .pj-mytree-tasks');
const groupEl = document.querySelector('.pj-project-mytasks .pj-mytree-group[data-group-id="' + t.project_id + '"]');
const group = groupEl ? groupEl.querySelector(".pj-mytree-tasks") : null;
if (!group) return; // 이 프로젝트에 내 업무가 하나도 없었으면 다음 새로고침까지 생략.
const existingLink = group.querySelector('.pj-task-popup-link[data-task-id="' + t.id + '"]');
const existingLi = existingLink ? existingLink.closest("li") : null;
if (removed) { if (existingLi) existingLi.remove(); return; }
if (removed) {
if (existingLi) existingLi.remove();
updateMyTreeCounts(groupEl);
return;
}
const wrap = document.createElement("div");
wrap.innerHTML = myTaskRowHtml(t);
const newLi = wrap.firstElementChild;
wireMyTaskPopupLink(newLi.querySelector(".pj-task-popup-link"));
if (cmt) cmt.wireBadge(newLi.querySelector(".pj-cmt-badge"));
if (existingLi) existingLi.replaceWith(newLi); else group.appendChild(newLi);
updateMyTreeCounts(groupEl);
}
// 내 업무 항목 우클릭 메뉴 — 열기(프로젝트 페이지로 이동)/업무 추가/수정
// (팝업 편집)/완료토글/삭제. "업무 추가"는 현재 열려 있는 프로젝트로만