feat(project): 좌측 프로젝트 트리도 완료 프로젝트 별도 섹션으로 분류

홈 카드와 같은 원칙 — "프로젝트" 목록 아래 "완료 프로젝트" 섹션을 따로
둔다. 서버 렌더(project.html, rejectattr/selectattr로 분리)와 클라이언트
재렌더(project.js renderTree) 둘 다 반영. 완료 섹션이 처음엔 없어도
(완료 프로젝트가 하나도 없던 상태로 열었을 때) 완료로 바뀌는 순간 JS가
동적으로 섹션을 만들고, 마지막 하나가 해제/삭제되면 다시 치운다. 드래그
재정렬은 각 섹션 안에서만(홈 카드와 동일).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 15:50:59 +09:00
parent 690fc8bb12
commit 8ea0db9245
5 changed files with 51 additions and 11 deletions
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260918c" />
<link rel="stylesheet" href="/static/project.css?v=20260918d" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %}
@@ -45,5 +45,5 @@
{% endif %}
</div>
<script src="/static/project.js?v=20260918c" defer></script>
<script src="/static/project.js?v=20260918d" defer></script>
{% endblock %}
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260918c" />
<link rel="stylesheet" href="/static/project.css?v=20260918d" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %}
@@ -428,5 +428,5 @@
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=20260918c" defer></script>
<script src="/static/project.js?v=20260918d" defer></script>
{% endblock %}
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260918c" />
<link rel="stylesheet" href="/static/project.css?v=20260918d" />
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
<!-- 타임라인 vis-timeline — self-host -->
@@ -36,9 +36,7 @@
<button type="button" class="pj-icon-btn" id="pj-new-project-side" title="새 프로젝트">+</button>
{% endif %}
</div>
{% if nav_projects %}
<ul class="pj-tree" id="pj-tree-root">
{% for p in nav_projects %}
{% macro nav_li(p) %}
<li class="pj-tree-li" data-id="{{ p.id }}"
data-name="{{ p.name }}" data-desc="{{ p.description or '' }}"
data-start="{{ p.start_date or '' }}" data-due="{{ p.due_date or '' }}"
@@ -57,11 +55,22 @@
{% endif %}
</div>
</li>
{% endfor %}
{% endmacro %}
{% set nav_active = nav_projects | rejectattr("status", "equalto", "completed") | list %}
{% set nav_done = nav_projects | selectattr("status", "equalto", "completed") | list %}
{% if nav_active %}
<ul class="pj-tree" id="pj-tree-root">
{% for p in nav_active %}{{ nav_li(p) }}{% endfor %}
</ul>
{% else %}
<div class="pj-empty-sm">프로젝트 없음</div>
{% endif %}
{% if nav_done %}
<div class="pj-side-head pj-side-head-done"><span>완료 프로젝트</span></div>
<ul class="pj-tree" id="pj-tree-root-done">
{% for p in nav_done %}{{ nav_li(p) }}{% endfor %}
</ul>
{% endif %}
</div>
<div class="pj-side-block">
@@ -544,5 +553,5 @@
</script>
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
<script src="/static/project.js?v=20260918c" defer></script>
<script src="/static/project.js?v=20260918d" defer></script>
{% endblock %}
+3
View File
@@ -216,6 +216,9 @@
.pj-side-block { margin-bottom: 22px; }
.pj-side-head { display: flex; align-items: center; justify-content: space-between; font-size: 14px; font-weight: 800; color: #6b7280; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 8px; }
.pj-side-head-done { margin-top: 14px; }
#pj-tree-root-done .pj-tree-avatar,
#pj-tree-root-done .pj-tree-name { opacity: .6; }
/* 굵게 지정해도 연한 회색 + 12px 라서 눈에 잘 안 띄었음 — 텍스트만 진한
잉크색으로 대비를 올린다("+" 버튼은 원래 색 유지). */
.pj-side-head span { color: var(--pj-ink); }
+29 -1
View File
@@ -3338,8 +3338,35 @@
function renderTree() {
const rootUl = el("pj-tree-root");
if (!rootUl) return;
rootUl.innerHTML = projectsMeta.map(treeProjLi).join("");
const active = projectsMeta.filter(function (p) { return p.status !== "completed"; });
const done = projectsMeta.filter(function (p) { return p.status === "completed"; });
rootUl.innerHTML = active.map(treeProjLi).join("");
wireTreeHandlers(rootUl);
// "완료 프로젝트" 섹션 — 처음 페이지를 열었을 때 완료 프로젝트가
// 하나도 없었으면 서버가 아예 안 그려서 DOM에 없다. 완료로 바뀌는
// 순간 필요해지면 그때 만든다(반대로 마지막 하나가 해제/삭제되면 통째로 치운다).
let doneUl = el("pj-tree-root-done");
if (done.length) {
if (!doneUl) {
const block = rootUl.closest(".pj-side-block");
const head = document.createElement("div");
head.className = "pj-side-head pj-side-head-done";
head.innerHTML = "<span>완료 프로젝트</span>";
doneUl = document.createElement("ul");
doneUl.className = "pj-tree";
doneUl.id = "pj-tree-root-done";
block.appendChild(head);
block.appendChild(doneUl);
wireTreeDrag(doneUl);
}
doneUl.innerHTML = done.map(treeProjLi).join("");
wireTreeHandlers(doneUl);
} else if (doneUl) {
const head = doneUl.previousElementSibling;
if (head && head.classList.contains("pj-side-head-done")) head.remove();
doneUl.remove();
}
}
// 좌측 프로젝트 목록 드래그 순서 변경 — 홈 화면 카드 정렬과 같은 API/원칙
// (관리자 전용, 최상위 프로젝트만). rootUl 자체는 renderTree() 가 innerHTML
@@ -3398,6 +3425,7 @@
}
renderTree();
wireTreeDrag(el("pj-tree-root"));
wireTreeDrag(el("pj-tree-root-done"));
// 새 최상위 프로젝트 (관리자)
const newProjSide = el("pj-new-project-side");