diff --git a/app/modules/project/router.py b/app/modules/project/router.py
index 30d04d2..7712123 100644
--- a/app/modules/project/router.py
+++ b/app/modules/project/router.py
@@ -356,12 +356,31 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse:
include_archived=False,
member_email=None if admin else user["email"],
)
- tree = _build_tree(all_projects)
stages = st.list_stages(project_id=project_id)
- tasks = st.list_tasks(project_id=project_id, include_subprojects=True, limit=2000)
+ tasks = st.list_tasks(project_id=project_id, limit=2000)
members = st.list_members(project_id=project_id)
can_manage = _can_manage(request, st, user, project_id)
- subprojects = [p for p in all_projects if p.get("parent_id") == project_id]
+
+ # 좌측 트리: 프로젝트(최상위) → 그 안의 업무. (서브프로젝트 개념 제거)
+ nav_projects: list[dict[str, Any]] = []
+ for p in all_projects:
+ if p.get("parent_id"):
+ continue # 과거 서브프로젝트는 트리에서 제외
+ p_tasks = (
+ tasks if p["id"] == project_id
+ else st.list_tasks(project_id=p["id"], limit=500)
+ )
+ nav_projects.append({
+ "id": p["id"],
+ "name": p["name"],
+ "color": p.get("color") or "#4573d2",
+ "tasks": [
+ {"id": t["id"], "title": t["title"],
+ "color": t.get("project_color") or p.get("color") or "#4573d2",
+ "completed": bool(t.get("completed_at"))}
+ for t in p_tasks
+ ],
+ })
# 달력(휴가식 월간 그리드) — ?y=&m= 로 월 이동, 기본 이번 달
today = today_kst()
@@ -387,11 +406,10 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse:
"page_title": project["name"],
"page_subtitle": project.get("description") or "프로젝트 보드",
"project": project,
- "tree": tree,
+ "nav_projects": nav_projects,
"stages": stages,
"tasks": tasks,
"members": members,
- "subprojects": subprojects,
"priority_labels": store.PRIORITY_LABELS,
"color_palette": list(store.COLOR_PALETTE),
"today": today.isoformat(),
diff --git a/app/modules/project/templates/project/inbox.html b/app/modules/project/templates/project/inbox.html
index a01803c..ad0c92a 100644
--- a/app/modules/project/templates/project/inbox.html
+++ b/app/modules/project/templates/project/inbox.html
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
-
+
{% endblock %}
@@ -45,5 +45,5 @@
{% endif %}
-
+
{% endblock %}
diff --git a/app/modules/project/templates/project/index.html b/app/modules/project/templates/project/index.html
index 264a05c..f278d0f 100644
--- a/app/modules/project/templates/project/index.html
+++ b/app/modules/project/templates/project/index.html
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
-
+
{% endblock %}
@@ -43,7 +43,6 @@
{{ p.name }}
{% if p.description %}{{ p.description }}
{% endif %}
- {% if p.children %}서브 {{ p.children|length }}{% endif %}
{% if p.due_date %}마감 {{ p.due_date }}{% endif %}
@@ -101,5 +100,5 @@
-
+
{% endblock %}
diff --git a/app/modules/project/templates/project/project.html b/app/modules/project/templates/project/project.html
index 273bc59..040c358 100644
--- a/app/modules/project/templates/project/project.html
+++ b/app/modules/project/templates/project/project.html
@@ -1,49 +1,13 @@
{% extends "erp_base.html" %}
{% block head_extra %}
-
+
{% endblock %}
-{# ── 아사나식 프로젝트/서브프로젝트 재귀 트리 ── #}
-{% macro proj_tree(nodes, active_id, can_edit, depth) %}
-
-{% endmacro %}
-
{% block topbar_actions %}
notifications
@@ -71,8 +35,50 @@
{% endif %}
- {% if tree %}
- {{ proj_tree(tree, project.id, (is_admin or can_manage), 0) }}
+ {% if nav_projects %}
+
+ {% for p in nav_projects %}
+ -
+
+ {% if p.tasks %}
+
+ {% else %}
+
+ {% endif %}
+
+ folder
+ {{ p.name }}
+
+ {% if is_admin %}
+
+
+
+ {% endif %}
+
+ {% if p.tasks %}
+
+
+ {% for t in p.tasks %}
+ -
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% endfor %}
+
{% else %}
프로젝트 없음
{% endif %}
@@ -284,5 +290,5 @@
-
+
{% endblock %}
diff --git a/app/static/project.css b/app/static/project.css
index d1e61c3..eb6e0db 100644
--- a/app/static/project.css
+++ b/app/static/project.css
@@ -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; }
diff --git a/app/static/project.js b/app/static/project.js
index 889acac..ff12b57 100644
--- a/app/static/project.js
+++ b/app/static/project.js
@@ -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) {