feat(project): 완료 토글, 설명 이미지 크기조절/삭제, 연결폼 가독성, 모달 고정크기 800x1100
- 업무 편집 모달에 "완료로 표시" 버튼 추가(완료 시 리스트뷰는 기존 취소선 스타일 그대로 적용됨). 완료 상태에 따라 버튼 색/문구 토글. - 설명란 이미지 클릭 시 선택되어 우하단 드래그로 크기 조절, × 버튼으로 삭제 가능. 크기는 style이 아닌 width 속성으로 저장해 서버 sanitize 후에도 유지. - 연결된 프로젝트 select 를 세로 배치+작은 폰트로 바꿔 글자 잘림 해결. - 업무 편집 모달을 800x1100 고정 크기로 바꾸고 좌(메타/설명)·우(하위업무· 첨부·연결·댓글) 칼럼이 각자 내부 스크롤하도록 재정비. fix(shell): 사이드바 접힘 상태를 localStorage에서 첫 페인트 전에 반영해 페이지 이동 시 "펼쳐졌다 다시 접히는" 깜빡임 제거. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+99
-1
@@ -348,6 +348,8 @@
|
||||
el("pj-t-dtime").disabled = !useTime;
|
||||
el("pj-t-open-project").href = "/project/p/" + task.project_id + "?task=" + task.id;
|
||||
el("pj-delete-task").hidden = false;
|
||||
el("pj-toggle-complete").hidden = false;
|
||||
setHomeCompleteBtn(!!task.completed_at);
|
||||
el("pj-t-open-project").hidden = false;
|
||||
el("pj-attach-block").hidden = false;
|
||||
el("pj-comment-block").hidden = false;
|
||||
@@ -377,6 +379,7 @@
|
||||
el("pj-t-stime").value = ""; el("pj-t-stime").disabled = true;
|
||||
el("pj-t-dtime").value = ""; el("pj-t-dtime").disabled = true;
|
||||
el("pj-delete-task").hidden = true;
|
||||
el("pj-toggle-complete").hidden = true;
|
||||
el("pj-t-open-project").hidden = true;
|
||||
el("pj-attach-block").hidden = true;
|
||||
el("pj-comment-block").hidden = true;
|
||||
@@ -385,6 +388,21 @@
|
||||
openModal(homeTaskModal);
|
||||
await Promise.all([fillAssignee(projectId, ""), fillStage(projectId, null)]);
|
||||
}
|
||||
function setHomeCompleteBtn(done) {
|
||||
const btn = el("pj-toggle-complete");
|
||||
btn.textContent = done ? "↩ 완료 취소" : "✓ 완료로 표시";
|
||||
btn.classList.toggle("is-done", done);
|
||||
btn.dataset.completed = done ? "1" : "";
|
||||
}
|
||||
el("pj-toggle-complete").addEventListener("click", async function () {
|
||||
const id = el("pj-t-id").value;
|
||||
if (!id) return;
|
||||
const done = el("pj-toggle-complete").dataset.completed === "1";
|
||||
try {
|
||||
await api("PUT", "/project/api/tasks/" + id, { completed: !done });
|
||||
location.reload();
|
||||
} catch (e) { alert("처리 실패: " + e.message); }
|
||||
});
|
||||
document.querySelectorAll(".pj-card-addtask").forEach(function (btn) {
|
||||
btn.addEventListener("click", function (e) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
@@ -1584,6 +1602,8 @@
|
||||
el("pj-t-stime").disabled = !useTime;
|
||||
el("pj-t-dtime").disabled = !useTime;
|
||||
el("pj-delete-task").hidden = !task;
|
||||
el("pj-toggle-complete").hidden = !task;
|
||||
if (task) setCompleteBtn(!!task.completed_at);
|
||||
// 댓글·첨부·하위업무·연결은 기존 업무 편집 시에만
|
||||
currentTaskId = task ? task.id : null;
|
||||
el("pj-attach-block").hidden = !task;
|
||||
@@ -1594,6 +1614,24 @@
|
||||
openModal(taskModal);
|
||||
}
|
||||
|
||||
function setCompleteBtn(done) {
|
||||
const btn = el("pj-toggle-complete");
|
||||
btn.textContent = done ? "↩ 완료 취소" : "✓ 완료로 표시";
|
||||
btn.classList.toggle("is-done", done);
|
||||
btn.dataset.completed = done ? "1" : "";
|
||||
}
|
||||
el("pj-toggle-complete").addEventListener("click", async function () {
|
||||
const id = el("pj-t-id").value;
|
||||
if (!id) return;
|
||||
const done = el("pj-toggle-complete").dataset.completed === "1";
|
||||
try {
|
||||
const res = await api("PUT", "/project/api/tasks/" + id, { completed: !done });
|
||||
mergeTask(res.task);
|
||||
setCompleteBtn(!!res.task.completed_at);
|
||||
refreshActiveView();
|
||||
} catch (e) { alert("처리 실패: " + e.message); }
|
||||
});
|
||||
|
||||
// ── 첨부파일 ──
|
||||
function fmtSize(n) {
|
||||
if (n < 1024) return n + " B";
|
||||
@@ -1906,10 +1944,70 @@
|
||||
function initDescEditor() {
|
||||
const box = el("pj-t-desc");
|
||||
if (!box) return null;
|
||||
const wrap = box.parentElement;
|
||||
const imgInput = el("pj-desc-img-input");
|
||||
const hint = el("pj-desc-hint");
|
||||
let taskId = null;
|
||||
|
||||
// ── 설명란 이미지 선택 → 크기 조절 핸들 + 삭제 버튼 오버레이 ──
|
||||
// 크기는 style이 아니라 width 속성으로 저장한다(서버 sanitize가 style은
|
||||
// 버리지만 img width/height 속성은 허용 목록에 있어 저장 후에도 유지된다).
|
||||
const imgTools = document.createElement("div");
|
||||
imgTools.className = "pj-img-tools";
|
||||
imgTools.hidden = true;
|
||||
imgTools.innerHTML =
|
||||
'<button type="button" class="pj-img-del" title="이미지 삭제">×</button>' +
|
||||
'<span class="pj-img-handle" title="드래그로 크기 조절"></span>';
|
||||
wrap.appendChild(imgTools);
|
||||
const imgDelBtn = imgTools.querySelector(".pj-img-del");
|
||||
const imgHandle = imgTools.querySelector(".pj-img-handle");
|
||||
let selectedImg = null;
|
||||
|
||||
function positionImgTools() {
|
||||
if (!selectedImg || !selectedImg.isConnected) { selectedImg = null; imgTools.hidden = true; return; }
|
||||
imgTools.style.left = selectedImg.offsetLeft + "px";
|
||||
imgTools.style.top = selectedImg.offsetTop + "px";
|
||||
imgTools.style.width = selectedImg.offsetWidth + "px";
|
||||
imgTools.style.height = selectedImg.offsetHeight + "px";
|
||||
imgTools.hidden = false;
|
||||
}
|
||||
function selectImg(img) {
|
||||
if (selectedImg) selectedImg.classList.remove("pj-img-selected");
|
||||
selectedImg = img || null;
|
||||
if (selectedImg) { selectedImg.classList.add("pj-img-selected"); positionImgTools(); }
|
||||
else { imgTools.hidden = true; }
|
||||
}
|
||||
box.addEventListener("click", function (e) {
|
||||
if (e.target.tagName === "IMG") selectImg(e.target);
|
||||
else selectImg(null);
|
||||
});
|
||||
document.addEventListener("click", function (e) {
|
||||
if (selectedImg && !wrap.contains(e.target)) selectImg(null);
|
||||
});
|
||||
imgDelBtn.addEventListener("click", function () {
|
||||
if (selectedImg) selectedImg.remove();
|
||||
selectImg(null);
|
||||
});
|
||||
let dragging = false, dragStartX = 0, dragStartW = 0;
|
||||
imgHandle.addEventListener("mousedown", function (e) {
|
||||
if (!selectedImg) return;
|
||||
e.preventDefault();
|
||||
dragging = true;
|
||||
dragStartX = e.clientX;
|
||||
dragStartW = selectedImg.offsetWidth;
|
||||
});
|
||||
document.addEventListener("mousemove", function (e) {
|
||||
if (!dragging || !selectedImg) return;
|
||||
let newW = Math.round(dragStartW + (e.clientX - dragStartX));
|
||||
newW = Math.max(40, Math.min(newW, wrap.clientWidth || 9999));
|
||||
selectedImg.setAttribute("width", String(newW));
|
||||
selectedImg.removeAttribute("height");
|
||||
selectedImg.style.width = "";
|
||||
selectedImg.style.height = "";
|
||||
positionImgTools();
|
||||
});
|
||||
document.addEventListener("mouseup", function () { dragging = false; });
|
||||
|
||||
function setTaskId(id) {
|
||||
taskId = id;
|
||||
const enabled = !!id;
|
||||
@@ -1973,7 +2071,7 @@
|
||||
|
||||
return {
|
||||
setTaskId: setTaskId,
|
||||
setHtml: function (html) { box.innerHTML = html || ""; },
|
||||
setHtml: function (html) { selectImg(null); box.innerHTML = html || ""; },
|
||||
getHtml: function () { return box.innerHTML; },
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user