feat(project): 홈 카드에 세션 추가, 새 업무 팝업 480x700 고정

- 홈 프로젝트 카드 hover 액션에 "세션 추가" 아이콘 추가(기존 세션 추가
  모달을 그대로 재사용, 카드별 projectId 를 들고 다녀 대상 지정)
- "새 업무" 작성 팝업(하위업무/첨부/연결/댓글 칸이 다 숨어 홑겹인 상태)만
  480x700 고정 크기로("업무 편집"은 기존 800x1100 그대로), 홈/프로젝트
  화면 공용 setTaskModalCompact() 로 토글

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 14:15:36 +09:00
parent af11927823
commit 29ce3554b1
5 changed files with 88 additions and 8 deletions
+9
View File
@@ -517,6 +517,15 @@
.pj-modal-col-main, .pj-modal-col-side { overflow: visible; height: auto; padding-right: 0; }
.pj-modal-col-side .pj-task-extra:first-child { border-top: 1px solid #eef0f2; margin-top: 14px; padding-top: 12px; }
}
/* "새 업무" 작성 — 하위업무/첨부/연결/댓글 칸이 전부 숨는 홑겹 폼이라
800x1100 대신 작게 고정(뷰포트 너비와 무관하게 항상 이 크기). */
.pj-task-modal-card.pj-modal-compact {
width: 480px; max-width: 96vw; height: 700px; max-height: 90vh;
display: block; overflow-y: auto;
}
.pj-task-modal-card.pj-modal-compact .pj-modal-body { display: block; height: auto; }
.pj-task-modal-card.pj-modal-compact .pj-modal-col-main,
.pj-task-modal-card.pj-modal-compact .pj-modal-col-side { overflow: visible; height: auto; padding-right: 0; }
.pj-modal-card h3 { margin: 0 0 16px; font-size: 17px; }
.pj-modal-hint { margin: -6px 0 14px; font-size: 12px; color: #6b7280; }
+51 -2
View File
@@ -157,6 +157,14 @@
return "rgba(" + r + "," + g + "," + b + "," + alpha + ")";
}
// 업무 모달(#pj-modal-task) 크기 — 기존 업무 편집은 하위업무/첨부/연결/
// 댓글까지 보여야 해서 800x1100 고정, "새 업무" 작성은 그 칸들이 전부
// 숨는 홑겹 폼이라 480x700 로 작게(홈 카드·프로젝트 화면 양쪽에서 공용).
function setTaskModalCompact(compact) {
const card = document.querySelector("#pj-modal-task .pj-task-modal-card");
if (card) card.classList.toggle("pj-modal-compact", !!compact);
}
// 멤버 선택 후보 카드(아이콘 위 / 이름 가운데 / 체크박스 아래) — 새 프로젝트
// 모달(홈·프로젝트 페이지)과 멤버 배정 모달 3곳이 전부 이 함수 하나를 쓴다.
// /api/assignable-users 응답의 avatar 필드를 그대로 쓰므로 페이지별
@@ -566,6 +574,8 @@
(p.due_date ? '<span class="pj-chip">마감 ' + fmtDateKr(p.due_date) + "</span>" : "") +
"</div></div></a>" +
'<div class="pj-card-actions">' +
'<button type="button" class="pj-icon-btn pj-card-addstage" title="세션 추가">' +
'<span class="material-symbols-outlined">view_column</span></button>' +
'<button type="button" class="pj-icon-btn pj-card-addtask" data-project-id="' + p.id + '" title="업무 추가">' +
'<span class="material-symbols-outlined">add_task</span></button>' +
'<button type="button" class="pj-icon-btn pj-card-edit" title="프로젝트 수정">' +
@@ -575,8 +585,38 @@
wrap.innerHTML = html;
return wrap.firstElementChild;
}
// 카드 하나에 수정/업무추가/우클릭 메뉴를 건다 — 최초 로드된 카드·새로
// 만든 카드 모두 이 함수 하나로 통일(initHome 맨 끝에서 전체 카드에 호출).
// ── 세션 추가(홈 카드 hover 아이콘) — 프로젝트 화면 보드의 "+ 세션 추가"와
// 같은 폼(이름+색상)을 그대로 재사용. 홈에는 "현재 프로젝트" 개념이
// 없으므로 어느 카드에서 눌렀는지 projectId 를 직접 들고 다닌다.
const stageModal = el("pj-modal-stage");
let getStageColor = null;
let stageModalProjectId = null;
if (stageModal) wireModalClose(stageModal);
function openStageModalForProject(projectId, projectName) {
if (!stageModal) return;
stageModalProjectId = projectId;
el("pj-stage-modal-title").textContent = (projectName ? "\"" + projectName + "\" " : "") + "세션 추가";
el("pj-stg-id").value = "";
el("pj-stg-name").value = "";
getStageColor = renderColorPalette(el("pj-stg-colors"), null, null, window.PJ_STAGE_COLORS);
openModal(stageModal);
}
const saveStageBtn = el("pj-save-stage");
if (saveStageBtn) saveStageBtn.addEventListener("click", async function () {
const name = el("pj-stg-name").value.trim();
if (!name) { alert("세션 이름을 입력하세요."); return; }
if (!stageModalProjectId) return;
const color = getStageColor ? getStageColor() : null;
try {
await api("POST", "/project/api/projects/" + stageModalProjectId + "/stages", { name: name, color: color });
closeModal(stageModal);
showToast("세션이 추가되었습니다.", "success");
} catch (e) { showToast("추가 실패: " + e.message, "error"); }
});
// 카드 하나에 수정/업무추가/세션추가/우클릭 메뉴를 건다 — 최초 로드된
// 카드·새로 만든 카드 모두 이 함수 하나로 통일(initHome 맨 끝에서 전체
// 카드에 호출).
function wireProjectCard(card) {
if (isAdmin) {
card.draggable = true;
@@ -595,12 +635,18 @@
e.preventDefault(); e.stopPropagation();
if (openNewTaskModalRef) openNewTaskModalRef(card.dataset.id, card.dataset.name);
});
const addStageBtn = card.querySelector(".pj-card-addstage");
if (addStageBtn) addStageBtn.addEventListener("click", function (e) {
e.preventDefault(); e.stopPropagation();
openStageModalForProject(card.dataset.id, card.dataset.name);
});
card.addEventListener("contextmenu", function (e) {
if (!card.querySelector(".pj-card-edit")) return; // 관리 권한 없는 카드는 메뉴 없음
e.preventDefault();
showContextMenu(e.clientX, e.clientY, [
{ label: "열기", onClick: function () { location.href = "/project/p/" + card.dataset.id; } },
{ label: "업무 추가", onClick: function () { if (openNewTaskModalRef) openNewTaskModalRef(card.dataset.id, card.dataset.name); } },
{ label: "세션 추가", onClick: function () { openStageModalForProject(card.dataset.id, card.dataset.name); } },
{ label: "수정", onClick: function () { openCardEditModal(card); } },
null,
{ label: "삭제", danger: true, onClick: function () { deleteProjectCard(card); } },
@@ -1085,6 +1131,7 @@
});
async function openHomeTaskModal(task) {
setTaskModalCompact(false);
el("pj-task-modal-title").textContent = "[" + (task.project_name || "") + "]의 업무 편집";
el("pj-t-id").value = task.id;
el("pj-t-project-id").value = task.project_id;
@@ -1122,6 +1169,7 @@
// 카드 이름 옆 "업무 추가" 버튼 — 같은 모달을 새 업무 작성용으로 초기화해 연다.
async function openHomeNewTaskModal(projectId, projectName) {
setTaskModalCompact(true);
el("pj-task-modal-title").textContent = "[" + (projectName || "") + "]의 새 업무";
el("pj-t-id").value = "";
el("pj-t-project-id").value = projectId;
@@ -2920,6 +2968,7 @@
}
function openTaskModal(task, presetStageId) {
setTaskModalCompact(!task);
const projName = task ? (task.project_name || currentProject.name || "") : (currentProject.name || "");
el("pj-task-modal-title").textContent = "[" + projName + "]의 " + (task ? "업무 편집" : "새 업무");
el("pj-t-id").value = task ? task.id : "";