diff --git a/app/modules/project/templates/project/inbox.html b/app/modules/project/templates/project/inbox.html index f184022..3bb37f3 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 0f559be..23e1a15 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 %} @@ -101,5 +101,5 @@ - + {% endblock %} diff --git a/app/modules/project/templates/project/project.html b/app/modules/project/templates/project/project.html index e7137de..dd65ee5 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 %} - + @@ -105,7 +105,8 @@
{% for cell in week.days %} -
+
{{ cell.day }}
@@ -255,5 +256,5 @@ - + {% endblock %} diff --git a/app/static/project.css b/app/static/project.css index b2c018e..0e9ebda 100644 --- a/app/static/project.css +++ b/app/static/project.css @@ -137,6 +137,10 @@ .pj-bar-l { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: 0; } .pj-bar-r { border-top-right-radius: 0; border-bottom-right-radius: 0; margin-right: 0; } .pj-bar-done { opacity: .75; text-decoration: line-through; } +/* 드래그 이동 — 드래그 중엔 bar 가 이벤트 가로채지 않게(날짜 셀이 드롭 받도록) */ +.pj-cal-dragging .pj-bar { pointer-events: none; } +.pj-bar[draggable="true"] { cursor: grab; } +.pj-day.pj-drop-over { background: #e6ecfa; box-shadow: inset 0 0 0 2px #aebfe6; } /* ── 타임라인 컨테이너 ── */ #pj-timeline { background: #fff; border-radius: 12px; padding: 6px; } diff --git a/app/static/project.js b/app/static/project.js index 73b0bbd..7872ac7 100644 --- a/app/static/project.js +++ b/app/static/project.js @@ -127,11 +127,26 @@ } // ── 달력 (휴가식 서버 렌더 그리드) ── - // bar 는 서버가 그려둠. 클릭하면 업무 모달 열기(관리 권한 시). + // bar 는 서버가 그려둠. 클릭=업무 모달, 드래그=일정 이동(관리 권한 시). let calendarWired = false; + + // 날짜 문자열 더하기(일 단위). 'YYYY-MM-DD' 입출력. + function addDays(dstr, days) { + if (!dstr) return null; + const d = new Date(dstr + "T00:00:00"); + d.setDate(d.getDate() + days); + const y = d.getFullYear(), m = String(d.getMonth() + 1).padStart(2, "0"), dd = String(d.getDate()).padStart(2, "0"); + return y + "-" + m + "-" + dd; + } + function dayDiff(a, b) { + return Math.round((new Date(b + "T00:00:00") - new Date(a + "T00:00:00")) / 86400000); + } + function wireCalendar() { if (calendarWired) return; calendarWired = true; + const cal = document.querySelector(".pj-cal"); + document.querySelectorAll(".pj-bar[data-task-id]").forEach(function (bar) { bar.addEventListener("click", function (e) { e.preventDefault(); @@ -139,6 +154,45 @@ const t = tasks.find(function (x) { return String(x.id) === bar.dataset.taskId; }); if (t) openTaskModal(t); }); + if (canManage) { + bar.draggable = true; + bar.addEventListener("dragstart", function (e) { + e.dataTransfer.setData("text/plain", bar.dataset.taskId); + e.dataTransfer.effectAllowed = "move"; + if (cal) cal.classList.add("pj-cal-dragging"); + }); + bar.addEventListener("dragend", function () { + if (cal) cal.classList.remove("pj-cal-dragging"); + document.querySelectorAll(".pj-day.pj-drop-over").forEach(function (d) { d.classList.remove("pj-drop-over"); }); + }); + } + }); + + if (!canManage || !cal) return; + document.querySelectorAll(".pj-day[data-date]").forEach(function (cell) { + cell.addEventListener("dragover", function (e) { e.preventDefault(); cell.classList.add("pj-drop-over"); }); + cell.addEventListener("dragleave", function () { cell.classList.remove("pj-drop-over"); }); + cell.addEventListener("drop", async function (e) { + e.preventDefault(); + cell.classList.remove("pj-drop-over"); + const taskId = e.dataTransfer.getData("text/plain"); + const t = tasks.find(function (x) { return String(x.id) === String(taskId); }); + if (!t) return; + const dropDate = cell.dataset.date; + // 앵커(시작일 우선, 없으면 마감일) 기준 이동량 → 기간 유지하며 시프트 + const anchor = t.start_date || t.due_date; + if (!anchor) return; + const delta = dayDiff(anchor, dropDate); + if (delta === 0) return; + const payload = { + start_date: t.start_date ? addDays(t.start_date, delta) : null, + due_date: t.due_date ? addDays(t.due_date, delta) : null, + }; + try { + await api("PUT", "/project/api/tasks/" + taskId, payload); + location.reload(); + } catch (err) { alert("이동 실패: " + err.message); } + }); }); }