diff --git a/app/modules/project/templates/project/inbox.html b/app/modules/project/templates/project/inbox.html index 8b9281f..06c2697 100644 --- a/app/modules/project/templates/project/inbox.html +++ b/app/modules/project/templates/project/inbox.html @@ -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 0e17b32..cf115ec 100644 --- a/app/modules/project/templates/project/index.html +++ b/app/modules/project/templates/project/index.html @@ -393,5 +393,5 @@ - + {% endblock %} diff --git a/app/modules/project/templates/project/project.html b/app/modules/project/templates/project/project.html index 68c5be2..7213f61 100644 --- a/app/modules/project/templates/project/project.html +++ b/app/modules/project/templates/project/project.html @@ -461,5 +461,5 @@ - + {% endblock %} diff --git a/app/static/project.js b/app/static/project.js index ff5d25d..3d8dcad 100644 --- a/app/static/project.js +++ b/app/static/project.js @@ -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); }); } // ── 댓글 말풍선(카드/내 업무) — 더블클릭 시 댓글만 보는 채팅형 팝업 ──