diff --git a/app/modules/project/router.py b/app/modules/project/router.py
index a1bfea7..6c988bc 100644
--- a/app/modules/project/router.py
+++ b/app/modules/project/router.py
@@ -805,12 +805,20 @@ async def api_all_users(request: Request) -> JSONResponse:
@router.get("/api/users/tasks")
-async def api_user_tasks(request: Request, email: str) -> JSONResponse:
- """'전체 멤버' 아이콘 클릭 팝업 — 지정한 사용자가 담당자인 업무 목록
- (프로젝트/세션/마감일). /api/my-tasks 와 같은 조회를 이메일만 바꿔 재사용."""
+async def api_user_tasks(
+ request: Request, email: str, project_id: int | None = None
+) -> JSONResponse:
+ """'전체 멤버'/사이드바 '프로젝트 멤버' 아이콘 클릭 팝업 — 지정한 사용자가
+ 담당자인 업무 목록(프로젝트/세션/마감일). project_id 를 주면 그 프로젝트
+ 안에서 담당하는 업무만(멀티호밍으로 연결된 업무 포함, list_tasks 의
+ project_id 단일 조회 경로 재사용). 안 주면 전체 프로젝트 대상."""
_require_user(request)
st = _db_or_503(request)
- return JSONResponse({"tasks": st.list_tasks(assignee_email=email, limit=500)})
+ if project_id is not None:
+ tasks = st.list_tasks(project_id=project_id, assignee_email=email, limit=500)
+ else:
+ tasks = st.list_tasks(assignee_email=email, limit=500)
+ return JSONResponse({"tasks": tasks})
@router.get("/api/projects/{project_id}/members")
diff --git a/app/modules/project/templates/project/inbox.html b/app/modules/project/templates/project/inbox.html
index 344466c..1a5fcf1 100644
--- a/app/modules/project/templates/project/inbox.html
+++ b/app/modules/project/templates/project/inbox.html
@@ -1,7 +1,7 @@
{% extends "erp_base.html" %}
{% block head_extra %}
-
+
{% endblock %}
@@ -45,5 +45,5 @@
{% endif %}
-
+
{% endblock %}
diff --git a/app/modules/project/templates/project/index.html b/app/modules/project/templates/project/index.html
index 21a09c6..c6b7117 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 %}
@@ -398,5 +398,5 @@
-
+
{% endblock %}
diff --git a/app/modules/project/templates/project/project.html b/app/modules/project/templates/project/project.html
index b89b273..4782aea 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 %}
-
+
@@ -511,7 +511,7 @@
- 닫기
+ 닫기
@@ -538,5 +538,5 @@
-
+
{% endblock %}
diff --git a/app/static/project.css b/app/static/project.css
index 1b78753..edf2e95 100644
--- a/app/static/project.css
+++ b/app/static/project.css
@@ -203,11 +203,15 @@
}
.pj-side-block { margin-bottom: 22px; }
-.pj-side-head { display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 700; color: #6b7280; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 8px; }
+.pj-side-head { display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 800; color: #6b7280; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 8px; }
.pj-member-list { list-style: none; margin: 0; padding: 0; }
.pj-member-list li { display: flex; align-items: center; gap: 8px; padding: 5px 4px; border-radius: 7px; }
.pj-member-list li:hover { background: #f6f7f8; }
.pj-member-name { font-size: 13px; flex: 1; }
+/* 프로젝트 멤버 아이콘 — 기존 대비 2배(사진 18→36px, 기본 아이콘 24→48px).
+ 클릭하면 이 프로젝트 안에서 그 사람이 담당하는 업무 팝업. */
+.pj-avatar.pj-member-avatar-lg { width: 36px; height: 36px; }
+.pj-gicon.pj-member-avatar-lg { font-size: 48px; }
/* ── 아사나식 프로젝트 트리(사이드바) — 프로젝트 이름만 나열, 업무 목록 없음 ── */
.pj-tree { list-style: none; margin: 0; padding: 0; }
@@ -625,6 +629,8 @@
/* ── 전체 멤버 클릭 팝업 — 담당 업무 목록(프로젝트/세션/제목/마감일) ── */
.pj-mt-card { width: min(600px, 94vw); }
+.pj-mt-card h3 { display: flex; align-items: center; gap: 6px; }
+.pj-mt-title-avatar { width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto; vertical-align: middle; }
.pj-mt-head {
display: grid; grid-template-columns: 100px 90px 1fr 80px; gap: 8px;
padding: 6px; border-bottom: 2px solid #eef0f2; margin-bottom: 2px;
diff --git a/app/static/project.js b/app/static/project.js
index b4ab762..19c96f2 100644
--- a/app/static/project.js
+++ b/app/static/project.js
@@ -3398,8 +3398,8 @@
const url = avatars[String(m.user_email).toLowerCase()];
return "" +
(url
- ? ' '
- : 'account_circle ') +
+ ? ' '
+ : 'account_circle ') +
'' + escapeHtml(idOf(m.user_email)) + " " +
(m.role === "manager" ? '관리 ' : "") +
(isAdmin ? ' '
+ : 'account_circle ';
+ }
+ // opts.projectId 를 주면 "이 프로젝트 안에서" 담당하는 업무만, 제목도
+ // "[프로젝트 이름]의 [사용자]님의 업무" 형식으로. 안 주면(전체 멤버)
+ // 전체 프로젝트 대상 + "[사용자]님의 업무".
+ async function openMemberTasksModal(email, name, opts) {
if (!memberTasksModal) return;
- el("pj-mt-title").textContent = (name || email) + "님의 업무";
+ opts = opts || {};
+ mtCurrentCtx = { email: email, name: name, opts: opts };
+ const prefix = opts.projectName ? escapeHtml(opts.projectName) + "의 " : "";
+ el("pj-mt-title").innerHTML = prefix + mtTitleAvatarHtml(email) + escapeHtml(name || email) + "님의 업무";
mtItems = [];
mtSort = { key: null, dir: 1 };
updateMtSortIcons();
@@ -3511,13 +3544,26 @@
list.innerHTML = " 불러오는 중… ";
openModal(memberTasksModal);
try {
- const res = await api("GET", "/project/api/users/tasks?email=" + encodeURIComponent(email));
+ const url = "/project/api/users/tasks?email=" + encodeURIComponent(email) +
+ (opts.projectId ? "&project_id=" + opts.projectId : "");
+ const res = await api("GET", url);
mtItems = res.tasks || [];
renderMtList();
} catch (e) {
list.innerHTML = "불러오기 실패: " + escapeHtml(e.message) + " ";
}
}
+ // 업무 편집 팝업이 닫히면(hidden 이 다시 true) 대기 중인 컨텍스트가
+ // 있을 때만 멤버 업무 팝업을 그 자리에 다시 연다.
+ if (taskModal) {
+ new MutationObserver(function () {
+ if (taskModal.hidden && mtReopenCtx) {
+ const ctx = mtReopenCtx;
+ mtReopenCtx = null;
+ openMemberTasksModal(ctx.email, ctx.name, ctx.opts);
+ }
+ }).observe(taskModal, { attributes: true, attributeFilter: ["hidden"] });
+ }
const allMembersGrid = el("pj-allmembers-grid");
if (allMembersGrid) {
api("GET", "/project/api/all-users").then(function (res) {