feat(project): 홈 카드에 세션 추가, 새 업무 팝업 480x700 고정
- 홈 프로젝트 카드 hover 액션에 "세션 추가" 아이콘 추가(기존 세션 추가
모달을 그대로 재사용, 카드별 projectId 를 들고 다녀 대상 지정)
- "새 업무" 작성 팝업(하위업무/첨부/연결/댓글 칸이 다 숨어 홑겹인 상태)만
480x700 고정 크기로("업무 편집"은 기존 800x1100 그대로), 홈/프로젝트
화면 공용 setTaskModalCompact() 로 토글
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260917s" />
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260917t" />
|
||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||
{% endblock %}
|
||||
|
||||
@@ -45,5 +45,5 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script src="/static/project.js?v=20260917t" defer></script>
|
||||
<script src="/static/project.js?v=20260917u" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260917s" />
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260917t" />
|
||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||
{% endblock %}
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
</a>
|
||||
<div class="pj-card-actions">
|
||||
{% if p.can_add_task %}
|
||||
<button type="button" class="pj-icon-btn pj-card-addstage" data-project-id="{{ p.id }}" data-project-name="{{ p.name }}" 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>
|
||||
@@ -242,6 +245,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 세션 추가 모달 — 홈 카드 hover 의 "세션 추가" 아이콘. 프로젝트 화면
|
||||
보드의 "+ 세션 추가"와 같은 폼(이름+색상)을 그대로 재사용한다. -->
|
||||
<div class="pj-modal" id="pj-modal-stage" hidden>
|
||||
<div class="pj-modal-card">
|
||||
<h3 id="pj-stage-modal-title">새 세션</h3>
|
||||
<input type="hidden" id="pj-stg-id" />
|
||||
<label>이름<input type="text" id="pj-stg-name" placeholder="세션 이름" /></label>
|
||||
<label>색상
|
||||
<span class="pj-color-palette" id="pj-stg-colors"></span>
|
||||
</label>
|
||||
<div class="pj-modal-actions">
|
||||
<span style="flex:1"></span>
|
||||
<button type="button" class="pj-btn" data-close>취소</button>
|
||||
<button type="button" class="pj-btn pj-btn-primary" id="pj-save-stage">저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 업무 편집 모달 — 카드/내 업무에서 클릭 시 페이지 이동 없이 팝업으로 편집 -->
|
||||
<div class="pj-modal" id="pj-modal-task" hidden>
|
||||
<div class="pj-modal-card pj-task-modal-card">
|
||||
@@ -398,6 +419,7 @@
|
||||
</script>
|
||||
<script>
|
||||
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
|
||||
window.PJ_STAGE_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772","#e8398a","#a15c43"] | tojson }};
|
||||
</script>
|
||||
<script src="/static/project.js?v=20260917t" defer></script>
|
||||
<script src="/static/project.js?v=20260917u" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260917s" />
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260917t" />
|
||||
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
|
||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||
<!-- 타임라인 vis-timeline — self-host -->
|
||||
@@ -538,5 +538,5 @@
|
||||
</script>
|
||||
|
||||
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
|
||||
<script src="/static/project.js?v=20260917t" defer></script>
|
||||
<script src="/static/project.js?v=20260917u" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -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
@@ -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 : "";
|
||||
|
||||
Reference in New Issue
Block a user