feat(project): 세션 색상/편집, 프로젝트 트리 드래그, 멤버업무 정렬, 댓글팝업 버그수정

- 좌측 프로젝트 리스트 드래그 재정렬(관리자, 홈 카드와 동일 API)
- 세션 추가/편집 팝업: 이름 + 아사나 10색 색상표(project_stages.color, 마이그레이션 004)
- 보드 세션 hover 아이콘 확대, 세션 제목 강조(업무 카드보다 크고 굵게)
- 완료 세션 반투명 + 보드 끝으로(정렬 기준만 변경 — 해제 시 원래 순서 자동 복귀)
- 전체 멤버 업무 팝업에 표 헤더(가운데정렬/굵게) + 정렬 아이콘 + 더블클릭 편집
- 댓글 채팅 팝업을 initCommentsChat() 공용화 — project.html에 모달이 없어 "내 업무"
  패널 댓글 더블클릭이 전혀 동작 안 했던 버그 수정(동적 배지 재바인딩 누락도 같이 수정)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 11:55:49 +09:00
parent 2f611767ca
commit 003d29d349
10 changed files with 438 additions and 174 deletions
+12 -5
View File
@@ -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)