fix(project): 프로젝트 목록 + 버튼도 새 프로젝트 팝업으로 열리게 수정

좌측 사이드바 "+ " 버튼이 prompt() 두 번(이름/마감일)으로 대충 만들던 걸
홈 화면과 같은 새 프로젝트 모달(설명·날짜·색상·멤버 선택)로 통일.
project.html 에 홈과 동일한 #pj-modal-project 마크업 추가, project.js 는
같은 저장 로직을 쓰되 결과는 mergeProject() 로 사이드바 트리에 patch.
This commit is contained in:
2026-09-16 12:53:26 +09:00
parent 27d91b9f6b
commit 3a50e81191
4 changed files with 79 additions and 13 deletions
@@ -45,5 +45,5 @@
{% endif %} {% endif %}
</div> </div>
<script src="/static/project.js?v=20260917e" defer></script> <script src="/static/project.js?v=20260917f" defer></script>
{% endblock %} {% endblock %}
@@ -393,5 +393,5 @@
<script> <script>
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }}; window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
</script> </script>
<script src="/static/project.js?v=20260917e" defer></script> <script src="/static/project.js?v=20260917f" defer></script>
{% endblock %} {% endblock %}
@@ -223,6 +223,29 @@
</div> </div>
</div> </div>
<!-- 새 프로젝트 모달 — 좌측 프로젝트 목록의 + 버튼(홈 화면과 동일 폼) -->
<div class="pj-modal" id="pj-modal-project" hidden>
<div class="pj-modal-card">
<h3>새 프로젝트</h3>
<label>이름<input type="text" id="pj-f-name" placeholder="프로젝트 이름" /></label>
<label>설명<textarea id="pj-f-desc" rows="2"></textarea></label>
<div class="pj-form-row">
<label>시작일<input type="date" id="pj-f-start" /></label>
<label>마감일<input type="date" id="pj-f-due" /></label>
</div>
<label>색상
<span class="pj-color-palette" id="pj-f-colors"></span>
</label>
<div class="pj-field-label">멤버
<div class="pj-member-picker" id="pj-f-members">불러오는 중…</div>
</div>
<div class="pj-modal-actions">
<button type="button" class="pj-btn" data-close>취소</button>
<button type="button" class="pj-btn pj-btn-primary" id="pj-save-project">만들기</button>
</div>
</div>
</div>
<!-- 프로젝트 수정 모달 — 좌측 프로젝트 목록의 ✎ 버튼 --> <!-- 프로젝트 수정 모달 — 좌측 프로젝트 목록의 ✎ 버튼 -->
<div class="pj-modal" id="pj-modal-edit" hidden> <div class="pj-modal" id="pj-modal-edit" hidden>
<div class="pj-modal-card"> <div class="pj-modal-card">
@@ -438,5 +461,5 @@
</script> </script>
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script> <script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
<script src="/static/project.js?v=20260917e" defer></script> <script src="/static/project.js?v=20260917f" defer></script>
{% endblock %} {% endblock %}
+53 -10
View File
@@ -2487,16 +2487,59 @@
// 새 최상위 프로젝트 (관리자) // 새 최상위 프로젝트 (관리자)
const newProjSide = el("pj-new-project-side"); const newProjSide = el("pj-new-project-side");
if (newProjSide) newProjSide.addEventListener("click", async function () { const newProjModal = el("pj-modal-project");
const name = prompt("새 프로젝트 이름"); if (newProjSide && newProjModal) {
if (!name || !name.trim()) return; wireModalClose(newProjModal);
const due = prompt("마감일 (YYYY-MM-DD, 없으면 취소 없이 비워두고 확인)") || ""; const getNewProjColor = renderColorPalette(el("pj-f-colors"));
try { const newProjMembersBox = el("pj-f-members");
const res = await api("POST", "/project/api/projects", { name: name.trim(), due_date: due.trim() || null }); newProjSide.addEventListener("click", async function () {
stagesByProject[res.project.id] = []; openModal(newProjModal);
mergeProject(res.project); if (newProjMembersBox) {
} catch (e) { showToast("생성 실패: " + e.message, "error"); } // 멤버 배정은 관리자 전용 API — 관리자가 아니면 시도하지 않고 안내만.
}); if (!isAdmin) {
newProjMembersBox.innerHTML = "<span class='pj-empty-sm'>멤버 배정은 관리자가 나중에 할 수 있습니다.</span>";
} else {
newProjMembersBox.innerHTML = "불러오는 중…";
try {
const res = await api("GET", "/project/api/assignable-users");
const users = res.users || [];
newProjMembersBox.innerHTML = users.length ? users.map(function (u) {
return '<label class="pj-member-pick"><input type="checkbox" value="' + u.email + '" /> ' +
escapeHtml(u.id) + "</label>";
}).join("") : "<span class='pj-empty-sm'>배정 가능한 직원이 없습니다.</span>";
} catch (e) {
newProjMembersBox.innerHTML = "<span class='pj-empty-sm'>불러오기 실패</span>";
}
}
}
});
el("pj-save-project").addEventListener("click", async function () {
const name = el("pj-f-name").value.trim();
if (!name) { alert("이름을 입력하세요."); return; }
const memberEmails = newProjMembersBox
? Array.from(newProjMembersBox.querySelectorAll("input[type=checkbox]:checked")).map(function (c) { return c.value; })
: [];
try {
const res = await api("POST", "/project/api/projects", {
name: name,
description: el("pj-f-desc").value.trim(),
start_date: el("pj-f-start").value || null,
due_date: el("pj-f-due").value || null,
color: getNewProjColor(),
});
const pid = res.project.id;
for (const email of memberEmails) {
try { await api("POST", "/project/api/projects/" + pid + "/members", { user_email: email }); }
catch (_) {}
}
stagesByProject[pid] = [];
mergeProject(res.project);
closeModal(newProjModal);
el("pj-f-name").value = ""; el("pj-f-desc").value = "";
el("pj-f-start").value = ""; el("pj-f-due").value = "";
} catch (e) { showToast("생성 실패: " + e.message, "error"); }
});
}
// ── 좌측 프로젝트 목록 ✎ 버튼 — 프로젝트 수정 모달(홈 화면과 동일). // ── 좌측 프로젝트 목록 ✎ 버튼 — 프로젝트 수정 모달(홈 화면과 동일).
// 트리는 renderTree() 로 매번 다시 그려지므로(라이브 새로고침 포함) 열기 // 트리는 renderTree() 로 매번 다시 그려지므로(라이브 새로고침 포함) 열기