feat(project): 보드 카드를 같은 세션 안에서 드래그로 순서 변경
지금까지 같은 세션에 다시 놓으면 아무 일도 안 일어났다(다른 세션으로 옮길 때만 동작). 놓은 위치(마우스 Y좌표)를 카드들 사이 인덱스로 계산해 그 순서를 새 엔드포인트로 저장하고(tasks.sort_order, 세션 소속만 재배정 — 세션 재정렬의 reorder_stages 와 동일 패턴), 로컬 tasks 배열도 그 순서에 맞게 재배치(applyStageOrder)해 새로고침 없이 즉시 반영. 실제 3개 업무로 드래그→서버 저장→하드리로드까지 헤드리스 크롬으로 검증.
This commit is contained in:
+44
-2
@@ -1910,6 +1910,17 @@
|
||||
// 보드 드롭(프로젝트 스코프) — 세션 id 로 바로 옮긴다. 이 프로젝트가 원
|
||||
// 소속이 아닌(멀티호밍된) 업무는 원본을 안 건드리고 "이 프로젝트 안에서의
|
||||
// 세션"만 연결(POST .../links)로 바꾼다.
|
||||
// 드롭 지점(마우스 Y)이 이 컬럼의 카드들 중 어디에 해당하는지 인덱스로.
|
||||
// 드래그 중인 카드 자신은 계산에서 제외(자기 위치가 기준을 흔들지 않게).
|
||||
function cardDropIndex(body, clientY, excludeTaskId) {
|
||||
const cards = Array.prototype.slice.call(body.querySelectorAll(".pj-card"))
|
||||
.filter(function (c) { return String(c.dataset.taskId) !== String(excludeTaskId); });
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
const r = cards[i].getBoundingClientRect();
|
||||
if (clientY < r.top + r.height / 2) return i;
|
||||
}
|
||||
return cards.length;
|
||||
}
|
||||
function enableDropByStageId(body, stageId) {
|
||||
body.addEventListener("dragover", function (e) {
|
||||
if (!e.dataTransfer.types.includes("text/plain")) return;
|
||||
@@ -1922,7 +1933,24 @@
|
||||
body.classList.remove("is-over");
|
||||
const taskId = parseInt(e.dataTransfer.getData("text/plain"), 10);
|
||||
const t = tasks.find(function (x) { return x.id === taskId; });
|
||||
if (!t || t.stage_id === stageId) return;
|
||||
if (!t) return;
|
||||
if (t.stage_id === stageId) {
|
||||
// 같은 세션 안에서 카드 위치만 이동 — 세션 이동이 아니라 정렬.
|
||||
const currentOrder = tasks.filter(function (x) { return x.stage_id === stageId; });
|
||||
const idx = cardDropIndex(body, e.clientY, taskId);
|
||||
const withoutDragged = currentOrder.filter(function (x) { return x.id !== taskId; });
|
||||
withoutDragged.splice(idx, 0, t);
|
||||
const orderedIds = withoutDragged.map(function (x) { return x.id; });
|
||||
const beforeIds = currentOrder.map(function (x) { return x.id; });
|
||||
if (beforeIds.join(",") === orderedIds.join(",")) return; // 놓은 자리가 원래 자리와 같으면 아무것도 안 함
|
||||
try {
|
||||
await api("PUT", "/project/api/projects/" + projectId + "/stages/" + stageId + "/tasks/order",
|
||||
{ ordered_ids: orderedIds });
|
||||
applyStageOrder(stageId, orderedIds);
|
||||
renderBoard();
|
||||
} catch (err) { showToast("순서 변경 실패: " + err.message, "error"); }
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (t.project_id === projectId) {
|
||||
const res = await api("PUT", "/project/api/tasks/" + taskId, { stage_id: stageId });
|
||||
@@ -1934,7 +1962,7 @@
|
||||
t.is_linked = true;
|
||||
}
|
||||
renderBoard();
|
||||
} catch (err) { alert("이동 실패: " + err.message); }
|
||||
} catch (err) { showToast("이동 실패: " + err.message, "error"); }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2154,6 +2182,20 @@
|
||||
else tasks.push(task);
|
||||
}
|
||||
|
||||
// 같은 세션 안 카드 드래그 재정렬 — tasks 배열은 여러 세션이 뒤섞인
|
||||
// 하나의 배열이라, 이 세션이 차지하던 자리(인덱스)만 그대로 두고 그
|
||||
// 자리에 들어갈 값만 새 순서대로 바꿔치기한다(다른 세션 업무의 상대
|
||||
// 위치는 건드리지 않는다).
|
||||
function applyStageOrder(stageId, orderedIds) {
|
||||
const orderMap = {};
|
||||
orderedIds.forEach(function (id, i) { orderMap[id] = i; });
|
||||
const idxs = [];
|
||||
tasks.forEach(function (t, i) { if (t.stage_id === stageId) idxs.push(i); });
|
||||
const sorted = idxs.map(function (i) { return tasks[i]; })
|
||||
.sort(function (a, b) { return orderMap[a.id] - orderMap[b.id]; });
|
||||
idxs.forEach(function (i, k) { tasks[i] = sorted[k]; });
|
||||
}
|
||||
|
||||
function refreshActiveView() {
|
||||
// 모든 뷰 + 좌측 트리 클라이언트 재렌더(새로고침 없음).
|
||||
renderView(currentView);
|
||||
|
||||
Reference in New Issue
Block a user