feat(project): 담당자·댓글 작성자 아이콘을 구글 프로필 이미지(원형)로 표시
- 라우터: 이메일→구글 picture URL 아바타 맵을 pj-data 로 전달 - 보드 카드/리스트 담당자, 댓글 작성자에 원형 프로필 이미지 표시 - 이미지 없으면 기존 기본 아이콘 폴백. referrerpolicy=no-referrer Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260625y" />
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260625z" />
|
||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||
{% endblock %}
|
||||
|
||||
@@ -167,5 +167,5 @@
|
||||
<script>
|
||||
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
|
||||
</script>
|
||||
<script src="/static/project.js?v=20260625y" defer></script>
|
||||
<script src="/static/project.js?v=20260625z" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260625y" />
|
||||
<link rel="stylesheet" href="/static/project.css?v=20260625z" />
|
||||
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
|
||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||
<!-- 타임라인 vis-timeline — self-host -->
|
||||
@@ -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 }}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
|
||||
<script src="/static/project.js?v=20260625y" defer></script>
|
||||
<script src="/static/project.js?v=20260625z" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
+13
-2
@@ -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 '<img class="pj-avatar ' + c + '" src="' + escapeHtml(url) + '" alt="" referrerpolicy="no-referrer">';
|
||||
return '<span class="material-symbols-outlined pj-gicon ' + c + '">account_circle</span>';
|
||||
}
|
||||
|
||||
let timeline = null;
|
||||
let currentView = "calendar";
|
||||
@@ -620,7 +628,7 @@
|
||||
? '<span class="pj-pri pj-pri-' + t.priority + '">' + (priorityLabels[t.priority] || t.priority) + "</span>"
|
||||
: "") +
|
||||
(t.assignee_email
|
||||
? '<span class="pj-assignee"><span class="material-symbols-outlined pj-gicon pj-gicon-sm">account_circle</span>' +
|
||||
? '<span class="pj-assignee">' + avatarHtml(t.assignee_email, "pj-gicon-sm") +
|
||||
escapeHtml(idOf(t.assignee_email)) + "</span>"
|
||||
: "") +
|
||||
(t.due_date ? '<span class="pj-card-due">' + t.due_date + "</span>" : "") +
|
||||
@@ -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 = '<span class="pj-assignee">' + avatarHtml(t.assignee_email, "pj-gicon-sm") + escapeHtml(idOf(t.assignee_email)) + "</span>";
|
||||
}
|
||||
return "<td>" + inner + "</td>";
|
||||
}).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 =
|
||||
'<span class="material-symbols-outlined pj-gicon pj-gicon-sm">account_circle</span>' +
|
||||
avatarHtml(c.author_email, "pj-gicon-sm") +
|
||||
'<div class="pj-comment-main"><div class="pj-comment-head">' +
|
||||
escapeHtml(idOf(c.author_email)) + ' <span class="pj-comment-time">' +
|
||||
(c.created_at || "").slice(0, 16).replace("T", " ") + "</span></div>" +
|
||||
|
||||
Reference in New Issue
Block a user