diff --git a/app/modules/project/db.py b/app/modules/project/db.py index 571e6c8..da3d058 100644 --- a/app/modules/project/db.py +++ b/app/modules/project/db.py @@ -297,7 +297,8 @@ class ProjectStore: with self._pool.connection() as conn: rows = conn.execute( f"SELECT t.*, p.name AS project_name, p.color AS project_color, " - f" s.name AS stage_name, s.is_done_stage " + f" s.name AS stage_name, s.is_done_stage, " + f" (SELECT COUNT(*) FROM task_comments c WHERE c.task_id = t.id) AS comment_count " f"FROM tasks t " f"JOIN projects p ON p.id = t.project_id " f"LEFT JOIN project_stages s ON s.id = t.stage_id " @@ -311,7 +312,8 @@ class ProjectStore: with self._pool.connection() as conn: row = conn.execute( "SELECT t.*, p.name AS project_name, p.color AS project_color, " - " s.name AS stage_name, s.is_done_stage " + " s.name AS stage_name, s.is_done_stage, " + " (SELECT COUNT(*) FROM task_comments c WHERE c.task_id = t.id) AS comment_count " "FROM tasks t JOIN projects p ON p.id = t.project_id " "LEFT JOIN project_stages s ON s.id = t.stage_id " "WHERE t.id = %s", diff --git a/app/modules/project/templates/project/index.html b/app/modules/project/templates/project/index.html index 6e76848..c636196 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 %} @@ -167,5 +167,5 @@ - + {% endblock %} diff --git a/app/modules/project/templates/project/project.html b/app/modules/project/templates/project/project.html index a167a31..84c3be8 100644 --- a/app/modules/project/templates/project/project.html +++ b/app/modules/project/templates/project/project.html @@ -1,7 +1,7 @@ {% extends "erp_base.html" %} {% block head_extra %} - + @@ -290,5 +290,5 @@ - + {% endblock %} diff --git a/app/static/project.css b/app/static/project.css index 7206575..9c16078 100644 --- a/app/static/project.css +++ b/app/static/project.css @@ -286,6 +286,12 @@ /* 보드 카드 — 전체 뷰에서 어느 프로젝트인지 배지 표시 */ .pj-card-proj { font-size: 11px; font-weight: 700; margin-bottom: 3px; letter-spacing: -.2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +/* 댓글 말풍선 표시 — 색은 currentColor 상속 */ +.material-symbols-outlined.pj-cmt-ic { font-size: 14px; vertical-align: middle; margin-left: 4px; line-height: 1; } +.material-symbols-outlined.pj-cmt-ic.pj-cmt-ic-tl { font-size: 12px; margin-left: 3px; } +.pj-cmt-n { font-size: 11px; font-weight: 700; vertical-align: middle; margin-left: 1px; } +.pj-card-cmt { display: inline-flex; align-items: center; color: #6b7280; } + /* ── 보드 단계 컨트롤 ── */ .pj-col-head { position: relative; } .pj-col-name { font-weight: 700; } diff --git a/app/static/project.js b/app/static/project.js index 8d3ff83..01b90ca 100644 --- a/app/static/project.js +++ b/app/static/project.js @@ -175,6 +175,13 @@ return !!cb && cb === me; } const DELETE_DENIED = "삭제는 만든 사람만 할 수 있습니다."; + // 댓글 있으면 말풍선 아이콘(+개수). 색은 currentColor 상속(막대=흰, 카드/리스트=진회색). + function cmtBadge(t, cls) { + const n = t.comment_count || 0; + if (!n) return ""; + return 'chat_bubble' + n + ""; + } const dataEl = el("pj-data"); const data = JSON.parse(dataEl.textContent); @@ -326,7 +333,7 @@ div.style.background = b.t.completed_at ? "#9aa5b1" : (b.t.project_color || "#4573d2"); div.title = "[" + (b.t.project_name || "") + "] " + (b.t.title || "") + (b.t.assignee_email ? " · " + idOf(b.t.assignee_email) : ""); - div.innerHTML = '"; + div.innerHTML = '" + cmtBadge(b.t); cont.appendChild(div); }); for (var col = 0; col < 7; col++) { @@ -484,7 +491,7 @@ const endISO = e + (t.due_time ? "T" + t.due_time : ""); const isRange = (e !== s) || (!!t.start_time && !!t.due_time && t.start_time !== t.due_time); items.push({ - id: t.id, group: grp, content: t.title, + id: t.id, group: grp, content: escapeHtml(t.title || "") + cmtBadge(t, "pj-cmt-ic-tl"), start: startISO, end: isRange ? endISO : undefined, type: isRange ? "range" : "point", style: "background-color:" + (t.completed_at ? "#9aa5b1" : projColor) + ";color:#fff;border:none;", @@ -613,6 +620,7 @@ escapeHtml(idOf(t.assignee_email)) + "" : "") + (t.due_date ? '' + t.due_date + "" : "") + + (t.comment_count ? '' + cmtBadge(t) + "" : "") + ""; card.addEventListener("click", function () { if (canManage) openTaskModal(t); }); if (canManage) { @@ -724,7 +732,9 @@ const pc = t.project_color || "#4573d2"; tr.style.background = pc + "14"; tr.innerHTML = LIST_COLS.map(function (c) { - return "