feat(project): 홈 카드/내 업무 패널 업무 항목에도 우클릭 메뉴(완료 포함) 추가

지금까지 홈 카드 안 업무 행·"내 업무" 패널 행은 자체 우클릭 메뉴가 없어서
이벤트가 부모 프로젝트 카드로 버블링돼 엉뚱하게 프로젝트 메뉴가 뜨던 문제를
같이 고침(stopPropagation 누락). 열기(프로젝트 페이지로 이동)/업무 추가/
수정(팝업 편집)/완료토글/삭제 5항목을 공용 함수(homeTaskContextItems)로
묶어 두 곳(카드 안 업무 행, 내 업무 패널)에서 그대로 재사용.
This commit is contained in:
2026-09-16 15:10:13 +09:00
parent eb1bae9fc6
commit 2773d8858d
4 changed files with 56 additions and 20 deletions
+53 -17
View File
@@ -362,6 +362,56 @@
if (prog) prog.remove();
}
}
// 업무 우클릭 메뉴 — 홈 카드 안 업무 행·"내 업무" 패널 행 공용(둘 다 같은
// .pj-task-popup-link 를 씀). 열기=프로젝트 페이지로 실제 이동, 수정=이
// 자리에서 팝업 편집 — 서로 다른 동작이라 항목을 나눴다. 카드 자체의
// 우클릭 메뉴와 겹치지 않도록 stopPropagation 필수(안 하면 이벤트가
// 버블링돼 카드 메뉴가 나중에 열려 덮어써 버린다).
function homeTaskContextItems(t) {
return [
{ label: "열기", onClick: function () { location.href = "/project/p/" + t.project_id + "?task=" + t.id; } },
{ label: "업무 추가", onClick: function () { if (openNewTaskModalRef) openNewTaskModalRef(t.project_id, t.project_name); } },
{ label: "수정", onClick: function () { if (openHomeTaskModalRef) openHomeTaskModalRef(t); } },
{
label: t.completed_at ? "완료 취소" : "완료로 표시",
onClick: async function () {
try {
const res = await api("PUT", "/project/api/tasks/" + t.id, { completed: !t.completed_at });
upsertHomeTaskRow(res.task);
} catch (e) { showToast("처리 실패: " + e.message, "error"); }
},
},
null,
{
label: "삭제", danger: true,
onClick: async function () {
if (!confirm("이 업무를 삭제할까요?")) return;
try {
await api("DELETE", "/project/api/tasks/" + t.id);
removeHomeTaskRow(t.id, t.project_id);
} catch (e) { showToast("삭제 실패: " + e.message, "error"); }
},
},
];
}
// .pj-task-popup-link 하나에 클릭(팝업 열기)+우클릭(메뉴) 배선 — 최초
// 로드된 행과 optimistic 하게 새로 그려진 행(upsertHomeTaskRow/
// syncMyTaskPanel) 모두 이 함수 하나로 통일.
function wireHomeTaskPopupLink(a, taskOverride) {
a.addEventListener("click", function (e) {
const task = taskOverride || homeTasks[a.dataset.taskId];
if (!task) return; // 정보 없으면 기존 링크 이동으로 폴백
e.preventDefault();
if (openHomeTaskModalRef) openHomeTaskModalRef(task);
});
a.addEventListener("contextmenu", function (e) {
const task = taskOverride || homeTasks[a.dataset.taskId];
if (!task) return;
e.preventDefault();
e.stopPropagation();
showContextMenu(e.clientX, e.clientY, homeTaskContextItems(task));
});
}
function upsertHomeTaskRow(t) {
const card = document.querySelector('.pj-project-card[data-id="' + t.project_id + '"]');
if (!card) { syncMyTaskPanel(t); return; }
@@ -377,10 +427,7 @@
const wrap = document.createElement("div");
wrap.innerHTML = homeTaskRowHtml(t);
const newLi = wrap.firstElementChild;
newLi.querySelector(".pj-task-popup-link").addEventListener("click", function (e) {
e.preventDefault();
if (openHomeTaskModalRef) openHomeTaskModalRef(t);
});
wireHomeTaskPopupLink(newLi.querySelector(".pj-task-popup-link"), t);
const badge = newLi.querySelector(".pj-cmt-badge");
if (badge) {
badge.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); });
@@ -444,10 +491,7 @@
const wrap = document.createElement("div");
wrap.innerHTML = html;
const newLi = wrap.firstElementChild;
newLi.querySelector(".pj-task-popup-link").addEventListener("click", function (e) {
e.preventDefault();
if (openHomeTaskModalRef) openHomeTaskModalRef(t);
});
wireHomeTaskPopupLink(newLi.querySelector(".pj-task-popup-link"), t);
if (existingLi) existingLi.replaceWith(newLi); else group.appendChild(newLi);
}
@@ -880,15 +924,7 @@
catch (e) { showToast("삭제 실패: " + e.message, "error"); }
});
document.querySelectorAll(".pj-task-popup-link").forEach(function (a) {
a.addEventListener("click", function (e) {
const tid = a.dataset.taskId;
const task = homeTasks[tid];
if (!task) return; // 정보 없으면 기존 링크 이동으로 폴백
e.preventDefault();
openHomeTaskModal(task);
});
});
document.querySelectorAll(".pj-task-popup-link").forEach(function (a) { wireHomeTaskPopupLink(a); });
}
// ── 댓글 말풍선(카드/내 업무) — 더블클릭 시 댓글만 보는 채팅형 팝업 ──