feat(project): 서브프로젝트 제거, 사이드 트리를 프로젝트→업무 2단계로

요구에 따라 서브프로젝트 개념을 UI 에서 제거. 좌측 트리를 프로젝트(최상위)
→ 그 안의 업무 2단계로 재구성:
- 프로젝트 노드(folder) 펼치면 소속 업무가 체크 아이콘으로 표시
- 업무 클릭 → /project/p/{id}?task={tid} 로 이동 후 해당 업무 모달 자동 오픈
- 완료 업무는 취소선. 트리에서 프로젝트 삭제(관리자)
- 과거 parent_id 서브프로젝트는 트리/홈에서 숨김(최상위만 노출)
- 홈 카드의 '서브 N' 칩 제거

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 13:20:28 +09:00
parent a22bb1e043
commit 03916a61b1
6 changed files with 84 additions and 64 deletions
+2
View File
@@ -85,6 +85,8 @@
.pj-tree-acts { display: none; gap: 0; flex: 0 0 auto; }
.pj-tree-row:hover .pj-tree-acts { display: inline-flex; }
.pj-tree-acts .pj-icon-btn { width: 22px; height: 22px; font-size: 14px; }
.pj-tree-done { text-decoration: line-through; color: #9aa1a9; }
.pj-tree-task .pj-tree-name { font-size: 13px; color: #444; }
/* 구글 머티리얼 심볼 아이콘(담당자) */
.material-symbols-outlined { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; line-height: 1; vertical-align: middle; }
+9 -14
View File
@@ -637,23 +637,11 @@
}
})();
// 서브프로젝트 추가 (트리 각 노드)
document.querySelectorAll(".pj-tree-addsub").forEach(function (b) {
b.addEventListener("click", async function (e) {
e.preventDefault(); e.stopPropagation();
const name = prompt("서브프로젝트 이름");
if (!name || !name.trim()) return;
try {
await api("POST", "/project/api/projects/" + b.dataset.id + "/subprojects", { name: name.trim() });
location.reload();
} catch (err) { alert("생성 실패: " + err.message); }
});
});
// 프로젝트/서브 삭제 (트리)
// 프로젝트 삭제 (트리)
document.querySelectorAll(".pj-tree-del").forEach(function (b) {
b.addEventListener("click", async function (e) {
e.preventDefault(); e.stopPropagation();
if (!confirm("'" + (b.dataset.name || "") + "' 삭제할까요? 하위 서브프로젝트·업무도 모두 삭제됩니다.")) return;
if (!confirm("'" + (b.dataset.name || "") + "' 프로젝트를 삭제할까요? 안의 업무도 모두 삭제됩니다.")) return;
try {
await api("DELETE", "/project/api/projects/" + b.dataset.id);
if (String(b.dataset.id) === String(projectId)) location.href = "/project/";
@@ -720,6 +708,13 @@
// 최초 렌더 — 달력은 서버 렌더, 클릭 핸들러만 연결
wireCalendar();
// 사이드 트리에서 업무 클릭(?task=ID)으로 들어오면 해당 업무 모달 자동 열기
const taskParam = new URLSearchParams(location.search).get("task");
if (taskParam) {
const t = tasks.find(function (x) { return String(x.id) === String(taskParam); });
if (t) openTaskModal(t);
}
}
function idOf(email) {