feat(project): 내 업무 패널 스크롤 + 프로젝트 폴더 펼치기/접기 토글

- .pj-home-side 에 max-height+overflow-y:auto 추가 — 목록이 길어지면
  패널 안에서만 스크롤(프로젝트 상세 화면은 기존 height:100% 우선 적용).
- "내 업무"의 프로젝트별 폴더 아이콘을 별도 토글 버튼으로 분리(이름은
  계속 프로젝트로 이동하는 링크) — 클릭 시 하위 업무 목록 펼침/접힘,
  아이콘도 folder_open/folder 로 전환. localStorage로 접힘 상태 기억.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 23:08:52 +09:00
parent 29ce3c7730
commit ecd4bd829e
5 changed files with 67 additions and 19 deletions
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260916l" />
<link rel="stylesheet" href="/static/project.css?v=20260916m" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %}
@@ -45,5 +45,5 @@
{% endif %}
</div>
<script src="/static/project.js?v=20260916m" defer></script>
<script src="/static/project.js?v=20260916n" defer></script>
{% endblock %}
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260916l" />
<link rel="stylesheet" href="/static/project.css?v=20260916m" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %}
@@ -150,11 +150,15 @@
{% else %}
<div class="pj-mytree">
{% for p in my_projects %}
<div class="pj-mytree-group">
<a class="pj-mytree-proj" href="/project/p/{{ p.id }}">
<span class="material-symbols-outlined" style="color: {{ p.color }}; font-size:18px;">folder</span>
<span class="pj-mytree-proj-name">{{ p.name }}</span>
</a>
<div class="pj-mytree-group" data-group-id="{{ p.id }}">
<div class="pj-mytree-head">
<button type="button" class="pj-mytree-toggle" title="펼치기/접기">
<span class="material-symbols-outlined" style="color: {{ p.color }}; font-size:18px;">folder_open</span>
</button>
<a class="pj-mytree-proj" href="/project/p/{{ p.id }}">
<span class="pj-mytree-proj-name">{{ p.name }}</span>
</a>
</div>
<ul class="pj-mytree-tasks">
{% for t in p.tasks %}
<li class="pj-mytree-task {% if t.completed_at %}is-done{% endif %}">
@@ -376,5 +380,5 @@
<script>
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
</script>
<script src="/static/project.js?v=20260916m" defer></script>
<script src="/static/project.js?v=20260916n" defer></script>
{% endblock %}
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260916l" />
<link rel="stylesheet" href="/static/project.css?v=20260916m" />
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
<!-- 타임라인 vis-timeline — self-host -->
@@ -175,11 +175,15 @@
{% else %}
<div class="pj-mytree">
{% for p in my_projects %}
<div class="pj-mytree-group">
<a class="pj-mytree-proj" href="/project/p/{{ p.id }}">
<span class="material-symbols-outlined" style="color: {{ p.color }}; font-size:18px;">folder</span>
<span class="pj-mytree-proj-name">{{ p.name }}</span>
</a>
<div class="pj-mytree-group" data-group-id="{{ p.id }}">
<div class="pj-mytree-head">
<button type="button" class="pj-mytree-toggle" title="펼치기/접기">
<span class="material-symbols-outlined" style="color: {{ p.color }}; font-size:18px;">folder_open</span>
</button>
<a class="pj-mytree-proj" href="/project/p/{{ p.id }}">
<span class="pj-mytree-proj-name">{{ p.name }}</span>
</a>
</div>
<ul class="pj-mytree-tasks">
{% for t in p.tasks %}
<li class="pj-mytree-task {% if t.completed_at %}is-done{% endif %}">
@@ -388,5 +392,5 @@
</script>
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
<script src="/static/project.js?v=20260916m" defer></script>
<script src="/static/project.js?v=20260916n" defer></script>
{% endblock %}
+13 -3
View File
@@ -116,7 +116,10 @@
.pj-section-head-done { margin-top: 22px; }
.pj-section-head-done h2 { font-size: 14px; color: #6b7280; }
.pj-home-side { width: 300px; border-left: 1px solid #eef0f2; padding-left: 24px; }
/* max-height 는 항상 값을 갖는 안전한 fallback(홈 화면처럼 부모가 고정
높이가 아닌 곳에서도 동작) — 프로젝트 상세 화면은 .pj-project-mytasks 가
더 구체적인 height:100% 로 덮어써서 화면 꽉 채움 레이아웃을 그대로 쓴다. */
.pj-home-side { width: 300px; border-left: 1px solid #eef0f2; padding-left: 24px; max-height: calc(100vh - 160px); overflow-y: auto; }
@media (max-width: 980px) { .pj-home-side { border-left: none; padding-left: 0; } }
.pj-mytask-list { list-style: none; margin: 0; padding: 0; }
.pj-mytask a { display: flex; align-items: center; gap: 8px; padding: 9px 6px; border-radius: 8px; text-decoration: none; color: inherit; }
@@ -294,11 +297,18 @@
.pj-th-resize { position: absolute; top: 0; right: -3px; width: 8px; height: 100%; cursor: col-resize; z-index: 2; }
.pj-th-resize:hover { background: linear-gradient(90deg, transparent 40%, #c9d4ea 40%, #c9d4ea 60%, transparent 60%); }
/* ── 내 업무 트리(홈) ── */
/* ── 내 업무 트리(홈) — 폴더 아이콘은 토글 버튼(열림/닫힘), 이름은 별도 링크 ── */
.pj-mytree-group { margin-bottom: 14px; }
.pj-mytree-proj { display: flex; align-items: center; gap: 7px; text-decoration: none; color: #1e1f21; font-weight: 700; font-size: 13.5px; padding: 4px 2px; border-radius: 7px; }
.pj-mytree-head { display: flex; align-items: center; gap: 2px; }
.pj-mytree-toggle {
flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
width: 26px; height: 26px; padding: 0; border: none; background: transparent; border-radius: 7px; cursor: pointer;
}
.pj-mytree-toggle:hover { background: #f1f2f4; }
.pj-mytree-proj { display: flex; align-items: center; gap: 7px; text-decoration: none; color: #1e1f21; font-weight: 700; font-size: 13.5px; padding: 4px 2px; border-radius: 7px; flex: 1 1 auto; min-width: 0; }
.pj-mytree-proj:hover { background: #f6f7f8; }
.pj-mytree-tasks { list-style: none; margin: 2px 0 0; padding: 0 0 0 8px; border-left: 2px solid #eef0f2; margin-left: 10px; }
.pj-mytree-group.is-collapsed .pj-mytree-tasks { display: none; }
.pj-mytree-task a { display: flex; align-items: flex-start; gap: 7px; padding: 6px 6px; border-radius: 7px; text-decoration: none; color: inherit; }
.pj-mytree-task a:hover { background: #f6f7f8; }
/* 댓글 아이콘 자리 — 댓글이 없어도 폭을 고정해 제목이 항상 같은 위치에서 시작(세로 정렬)되게 한다. */
+30
View File
@@ -2441,11 +2441,41 @@
setTimeout(poll, 8000);
}
// ── "내 업무" 패널 — 프로젝트별 폴더 아이콘 클릭으로 하위 업무 목록
// 펼치기/접기. 홈 화면·프로젝트 상세 화면 둘 다 같은 마크업이라 공용으로
// 한 번만 처리한다(각 initXxx 와 무관, 서버 렌더 마크업 그대로 사용).
function wireMyTreeToggles() {
const KEY = "pj_mytree_collapsed";
let collapsed;
try { collapsed = new Set(JSON.parse(localStorage.getItem(KEY) || "[]")); }
catch (_) { collapsed = new Set(); }
function save() {
try { localStorage.setItem(KEY, JSON.stringify(Array.prototype.slice.call(collapsed))); } catch (_) {}
}
document.querySelectorAll(".pj-mytree-group").forEach(function (group) {
const gid = group.dataset.groupId;
const btn = group.querySelector(".pj-mytree-toggle");
const icon = btn ? btn.querySelector(".material-symbols-outlined") : null;
function apply() {
const isCollapsed = collapsed.has(gid);
group.classList.toggle("is-collapsed", isCollapsed);
if (icon) icon.textContent = isCollapsed ? "folder" : "folder_open";
}
apply();
if (btn) btn.addEventListener("click", function () {
if (collapsed.has(gid)) collapsed.delete(gid); else collapsed.add(gid);
save();
apply();
});
});
}
document.addEventListener("DOMContentLoaded", function () {
initHome();
initProject();
initBell();
initInbox();
initLiveRefresh();
wireMyTreeToggles();
});
})();