fix(project): 타임라인 배경을 프로젝트 시작/마감일 기준으로 표시

배경(전체 일정)을 업무 최소~최대가 아니라 프로젝트 자체 start_date~
due_date 로 칠한다. 업무가 그 밖에 있으면 범위를 넓혀 가리지 않게 유지.
projects_meta 에 start_date/due_date 추가.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 16:59:57 +09:00
parent c0ca3cf64f
commit 523b9b3150
4 changed files with 21 additions and 8 deletions
+2
View File
@@ -399,6 +399,8 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse:
"id": p["id"], "id": p["id"],
"name": p["name"], "name": p["name"],
"color": p.get("color") or "#4573d2", "color": p.get("color") or "#4573d2",
"start_date": p.get("start_date"),
"due_date": p.get("due_date"),
}) })
nav_projects.append({ nav_projects.append({
"id": p["id"], "id": p["id"],
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260625t" /> <link rel="stylesheet" href="/static/project.css?v=20260625u" />
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" /> <link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
{% endblock %} {% endblock %}
@@ -164,5 +164,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=20260625t" defer></script> <script src="/static/project.js?v=20260625u" defer></script>
{% endblock %} {% endblock %}
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %} {% extends "erp_base.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260625t" /> <link rel="stylesheet" href="/static/project.css?v=20260625u" />
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host --> <!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" /> <link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
<!-- 타임라인 vis-timeline — self-host --> <!-- 타임라인 vis-timeline — self-host -->
@@ -289,5 +289,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=20260625t" defer></script> <script src="/static/project.js?v=20260625u" defer></script>
{% endblock %} {% endblock %}
+15 -4
View File
@@ -441,6 +441,10 @@
const dated = tasks.filter(function (t) { return t.start_date || t.due_date; }); const dated = tasks.filter(function (t) { return t.start_date || t.due_date; });
if (!dated.length) { node.innerHTML = '<div class="pj-empty">기간이 설정된 업무가 없습니다.</div>'; return; } if (!dated.length) { node.innerHTML = '<div class="pj-empty">기간이 설정된 업무가 없습니다.</div>'; return; }
// 프로젝트 메타(시작/마감/색) 조회용
const projById = {};
projectsMeta.forEach(function (p) { projById[p.id] = p; });
// 프로젝트별로 업무를 묶어 그룹(행) 생성 // 프로젝트별로 업무를 묶어 그룹(행) 생성
const byProj = {}; const byProj = {};
dated.forEach(function (t) { (byProj[t.project_id] = byProj[t.project_id] || []).push(t); }); dated.forEach(function (t) { (byProj[t.project_id] = byProj[t.project_id] || []).push(t); });
@@ -450,8 +454,9 @@
Object.keys(byProj).forEach(function (pid) { Object.keys(byProj).forEach(function (pid) {
const list = byProj[pid]; const list = byProj[pid];
const grp = "P" + pid; const grp = "P" + pid;
const projColor = list[0].project_color || "#4573d2"; const meta = projById[pid] || {};
groupRows.push({ id: grp, content: list[0].project_name || "프로젝트" }); const projColor = list[0].project_color || meta.color || "#4573d2";
groupRows.push({ id: grp, content: list[0].project_name || meta.name || "프로젝트" });
let minMs = Infinity, maxMs = -Infinity; let minMs = Infinity, maxMs = -Infinity;
list.forEach(function (t) { list.forEach(function (t) {
const s = t.start_date || t.due_date; const s = t.start_date || t.due_date;
@@ -470,10 +475,16 @@
if (sm < minMs) minMs = sm; if (sm < minMs) minMs = sm;
if (em > maxMs) maxMs = em; if (em > maxMs) maxMs = em;
}); });
maxMs += 86400000; // 마지막 날 하루를 덮도록 +1일 // 배경(프로젝트 전체 일정) = 프로젝트 자체 시작/마감일 우선, 없으면 업무 최소~최대.
let bgStart = meta.start_date ? new Date(meta.start_date + "T00:00:00").getTime() : minMs;
let bgEnd = meta.due_date ? new Date(meta.due_date + "T00:00:00").getTime() : maxMs;
// 업무가 프로젝트 기간 밖에 있어도 가려지지 않게 범위를 넓힌다.
bgStart = Math.min(bgStart, minMs);
bgEnd = Math.max(bgEnd, maxMs);
bgEnd += 86400000; // 마지막 날 하루를 덮도록 +1일
items.push({ items.push({
id: "bg" + pid, group: grp, type: "background", id: "bg" + pid, group: grp, type: "background",
start: new Date(minMs), end: new Date(maxMs), start: new Date(bgStart), end: new Date(bgEnd),
style: "background-color:" + projColor + "22;", style: "background-color:" + projColor + "22;",
}); });
}); });