From 003d29d3492acca1cf1f3d3d67e28f6402ccb247 Mon Sep 17 00:00:00 2001 From: king Date: Thu, 17 Sep 2026 11:55:49 +0900 Subject: [PATCH] =?UTF-8?q?feat(project):=20=EC=84=B8=EC=85=98=20=EC=83=89?= =?UTF-8?q?=EC=83=81/=ED=8E=B8=EC=A7=91,=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=ED=8A=B8=EB=A6=AC=20=EB=93=9C=EB=9E=98=EA=B7=B8,?= =?UTF-8?q?=20=EB=A9=A4=EB=B2=84=EC=97=85=EB=AC=B4=20=EC=A0=95=EB=A0=AC,?= =?UTF-8?q?=20=EB=8C=93=EA=B8=80=ED=8C=9D=EC=97=85=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 좌측 프로젝트 리스트 드래그 재정렬(관리자, 홈 카드와 동일 API) - 세션 추가/편집 팝업: 이름 + 아사나 10색 색상표(project_stages.color, 마이그레이션 004) - 보드 세션 hover 아이콘 확대, 세션 제목 강조(업무 카드보다 크고 굵게) - 완료 세션 반투명 + 보드 끝으로(정렬 기준만 변경 — 해제 시 원래 순서 자동 복귀) - 전체 멤버 업무 팝업에 표 헤더(가운데정렬/굵게) + 정렬 아이콘 + 더블클릭 편집 - 댓글 채팅 팝업을 initCommentsChat() 공용화 — project.html에 모달이 없어 "내 업무" 패널 댓글 더블클릭이 전혀 동작 안 했던 버그 수정(동적 배지 재바인딩 누락도 같이 수정) Co-Authored-By: Claude Sonnet 5 --- app/modules/project/db.py | 17 +- app/modules/project/router.py | 3 + app/modules/project/store.py | 21 + .../project/templates/project/inbox.html | 4 +- .../project/templates/project/index.html | 4 +- .../project/templates/project/project.html | 48 +- app/static/project.css | 26 +- app/static/project.js | 464 ++++++++++++------ docs/PROJECT_MODULE.md | 9 +- scripts/sql/project_db_004_stage_color.sql | 16 + 10 files changed, 438 insertions(+), 174 deletions(-) create mode 100644 scripts/sql/project_db_004_stage_color.sql diff --git a/app/modules/project/db.py b/app/modules/project/db.py index 86a5e6f..89ceebf 100644 --- a/app/modules/project/db.py +++ b/app/modules/project/db.py @@ -238,11 +238,13 @@ class ProjectStore: return [self._serialize(r) for r in rows] def add_stage( - self, *, project_id: int, name: str, is_done_stage: bool = False, created_by: str = "" + self, *, project_id: int, name: str, is_done_stage: bool = False, + color: str | None = None, created_by: str = "", ) -> dict[str, Any]: nm = store.norm_str(name) if not nm: raise ValueError("세션 이름은 필수입니다.") + col = store.validate_stage_color(color) with self._pool.connection() as conn: nxt = conn.execute( "SELECT COALESCE(MAX(sort_order), -1) + 1 AS n " @@ -250,9 +252,9 @@ class ProjectStore: (project_id,), ).fetchone() row = conn.execute( - "INSERT INTO project_stages (project_id, name, sort_order, is_done_stage, created_by) " - "VALUES (%s,%s,%s,%s,%s) RETURNING *", - (project_id, nm, int(nxt["n"]), bool(is_done_stage), + "INSERT INTO project_stages (project_id, name, sort_order, is_done_stage, color, created_by) " + "VALUES (%s,%s,%s,%s,%s,%s) RETURNING *", + (project_id, nm, int(nxt["n"]), bool(is_done_stage), col, store.norm_str(created_by, lower=True)), ).fetchone() return self._serialize(row) @@ -776,8 +778,10 @@ class ProjectStore: # ════════════════════════════════════════════════════════════ def update_stage( self, *, stage_id: int, name: str | None = None, - is_done_stage: bool | None = None, + is_done_stage: bool | None = None, color: Any = ..., ) -> dict[str, Any]: + """color 는 지정 안 함(기본 `...`)과 명시적으로 지우기(`None`)를 + 구분해야 해서 다른 필드처럼 `is None` 이 아니라 `is not ...` 로 검사한다.""" sets: list[str] = [] params: list[Any] = [] if name is not None: @@ -789,6 +793,9 @@ class ProjectStore: if is_done_stage is not None: sets.append("is_done_stage = %s") params.append(bool(is_done_stage)) + if color is not ...: + sets.append("color = %s") + params.append(store.validate_stage_color(color)) if not sets: raise ValueError("변경할 내용이 없습니다.") params.append(stage_id) diff --git a/app/modules/project/router.py b/app/modules/project/router.py index a61e379..6aa6667 100644 --- a/app/modules/project/router.py +++ b/app/modules/project/router.py @@ -618,6 +618,7 @@ async def project_page(request: Request, project_id: int) -> HTMLResponse: "avatars": avatars, "priority_labels": store.PRIORITY_LABELS, "color_palette": list(store.COLOR_PALETTE), + "stage_color_palette": list(store.STAGE_COLOR_PALETTE), "today": today.isoformat(), "year": year, "month": month, @@ -865,6 +866,7 @@ async def api_add_stage( project_id=project_id, name=payload.get("name", ""), is_done_stage=bool(payload.get("is_done_stage", False)), + color=payload.get("color"), created_by=user["email"], ) except ValueError as exc: @@ -1362,6 +1364,7 @@ async def api_update_stage( stage_id=stage_id, name=payload.get("name"), is_done_stage=payload.get("is_done_stage"), + color=payload.get("color", ...), ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) diff --git a/app/modules/project/store.py b/app/modules/project/store.py index e46ddc8..b9570fc 100644 --- a/app/modules/project/store.py +++ b/app/modules/project/store.py @@ -49,6 +49,21 @@ COLOR_PALETTE: tuple[str, ...] = ( "#5a6772", # slate ) +# 세션(칸반 컬럼) 색상 팔레트 — 아사나 태그/커스텀필드에서 쓰는 10색. +# 세션 생성/편집 팝업의 색상표가 이 순서 그대로 노출된다. +STAGE_COLOR_PALETTE: tuple[str, ...] = ( + "#4573d2", # blue + "#37a3a3", # teal + "#62a420", # green + "#e8a33d", # amber + "#e8384f", # red + "#aa62e3", # purple + "#f06a6a", # coral + "#5a6772", # slate + "#e8398a", # pink + "#a15c43", # brown +) + # ════════════════════════════════════════════════════════════ # 대한민국 국경일·공휴일(2026~2030) — 프로젝트 달력 표시 전용. # @@ -181,6 +196,12 @@ def validate_color(color: Any) -> str: return COLOR_PALETTE[0] +def validate_stage_color(color: Any) -> str | None: + """세션 색상 — 팔레트 밖 값/빈값은 저장 안 함(NULL, 기본 회색 표시).""" + v = norm_str(color).lower() + return v if v in STAGE_COLOR_PALETTE else None + + def validate_date(d: Any) -> str | None: """'YYYY-MM-DD' 형식만 통과. 빈값은 None.""" from datetime import date as _date # noqa: WPS433 diff --git a/app/modules/project/templates/project/inbox.html b/app/modules/project/templates/project/inbox.html index 5067a6a..f85bfe9 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 2ebe9d5..0e75246 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 %} @@ -393,5 +393,5 @@ - + {% endblock %} diff --git a/app/modules/project/templates/project/project.html b/app/modules/project/templates/project/project.html index cdcb5a6..5beed52 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,6 +290,23 @@ + + +