diff --git a/app/modules/project/router.py b/app/modules/project/router.py index 8a1b589..091c1c2 100644 --- a/app/modules/project/router.py +++ b/app/modules/project/router.py @@ -441,6 +441,14 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse: board_stage_names.append(s["name"]) board_stage_names.sort(key=lambda n: (_order.get(n, 999), n)) + # 아바타 맵: 이메일(소문자) → 구글 프로필 이미지 URL (담당자/댓글 작성자 표시용) + from app.main import user_store # noqa: WPS433 + avatars = { + store.norm_str(u["email"], lower=True): u.get("picture") or "" + for u in user_store.list_all() + if u.get("picture") + } + # 달력(휴가식 월간 그리드) — ?y=&m= 로 월 이동, 기본 이번 달 today = today_kst() try: @@ -472,6 +480,7 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse: "projects_meta": projects_meta, "stages_by_project": stages_by_project, "board_stage_names": board_stage_names, + "avatars": avatars, "priority_labels": store.PRIORITY_LABELS, "color_palette": list(store.COLOR_PALETTE), "today": today.isoformat(), diff --git a/app/modules/project/templates/project/index.html b/app/modules/project/templates/project/index.html index b564646..b46e6ad 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 0a6f638..e517a00 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 %} - + @@ -285,10 +285,11 @@ "priorityLabels": {{ priority_labels | tojson }}, "projects": {{ projects_meta | tojson }}, "stagesByProject": {{ stages_by_project | tojson }}, - "boardStages": {{ board_stage_names | tojson }} + "boardStages": {{ board_stage_names | tojson }}, + "avatars": {{ avatars | tojson }} } - + {% endblock %} diff --git a/app/static/project.css b/app/static/project.css index 9c16078..25a4940 100644 --- a/app/static/project.css +++ b/app/static/project.css @@ -108,7 +108,10 @@ .material-symbols-outlined { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; line-height: 1; vertical-align: middle; } .pj-gicon { font-size: 24px; color: #5f6368; flex: 0 0 auto; } .pj-gicon-sm { font-size: 20px; } -.pj-assignee { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; color: #5f6368; } +.pj-assignee { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #5f6368; } +/* 구글 프로필 이미지 — 원형 */ +.pj-avatar { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; vertical-align: middle; background: #e7e9ec; } +.pj-avatar.pj-gicon-sm { width: 18px; height: 18px; } /* ── 툴바 + 탭 ── */ .pj-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; } diff --git a/app/static/project.js b/app/static/project.js index f9594bf..e5ed5d1 100644 --- a/app/static/project.js +++ b/app/static/project.js @@ -193,6 +193,14 @@ const projectsMeta = data.projects || []; const stagesByProject = data.stagesByProject || {}; const boardStages = data.boardStages || []; + const avatars = data.avatars || {}; + // 담당자/작성자 아이콘 = 구글 프로필 이미지(원형). 없으면 기본 아이콘. + function avatarHtml(email, cls) { + const url = email ? avatars[String(email).toLowerCase()] : ""; + const c = cls || ""; + if (url) return ''; + return 'account_circle'; + } let timeline = null; let currentView = "calendar"; @@ -620,7 +628,7 @@ ? '' + (priorityLabels[t.priority] || t.priority) + "" : "") + (t.assignee_email - ? 'account_circle' + + ? '' + avatarHtml(t.assignee_email, "pj-gicon-sm") + escapeHtml(idOf(t.assignee_email)) + "" : "") + (t.due_date ? '' + t.due_date + "" : "") + @@ -738,6 +746,9 @@ tr.innerHTML = LIST_COLS.map(function (c) { let inner = escapeHtml(String(c.text(t))); if (c.key === "title") inner += cmtBadge(t); + if (c.key === "assignee" && t.assignee_email) { + inner = '' + avatarHtml(t.assignee_email, "pj-gicon-sm") + escapeHtml(idOf(t.assignee_email)) + ""; + } return "" + inner + ""; }).join(""); if (canManage) { tr.style.cursor = "pointer"; tr.addEventListener("click", function () { openTaskModal(t); }); } @@ -856,7 +867,7 @@ const li = document.createElement("li"); li.className = "pj-comment-item"; li.innerHTML = - 'account_circle' + + avatarHtml(c.author_email, "pj-gicon-sm") + '
' + escapeHtml(idOf(c.author_email)) + ' ' + (c.created_at || "").slice(0, 16).replace("T", " ") + "
" +