feat(project): 세션 헤더 색상 채우기, 보드 댓글팝업, 채팅 첨부파일/이미지

- 세션 색상: 점 대신 세션 헤더 윗부분 배경을 통째로 칠함(글자/카운트 흰색으로)
- 보드 업무 카드의 댓글 말풍선도 더블클릭하면 채팅 팝업 열리게(.pj-cmt-badge 공용)
- 댓글 채팅 팝업에 파일/이미지 첨부 추가: 클립 버튼 업로드 + 클립보드 이미지
  붙여넣기, 이미지는 썸네일, 파일은 확장자 아이콘+파일명으로 표시, 다운로드
  가능, 업로드한 사람만 삭제(즉시 반영). task_attachments.comment_id(마이그
  레이션 005)로 태깅해 업무 "첨부파일" 패널에도 그대로 함께 나온다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 12:27:44 +09:00
parent 6922679f51
commit 1d7fe71b75
8 changed files with 258 additions and 27 deletions
+10 -2
View File
@@ -1164,6 +1164,7 @@ async def api_add_comment(
author_email=user["email"],
author_name=user.get("name") or user["email"].split("@")[0],
body=payload.get("body", ""),
allow_empty=bool(payload.get("has_attachment")),
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@@ -1237,14 +1238,21 @@ async def api_list_attachments(request: Request, task_id: int) -> JSONResponse:
@router.post("/api/tasks/{task_id}/attachments")
async def api_upload_attachment(
request: Request, task_id: int, file: UploadFile = File(...)
request: Request, task_id: int, file: UploadFile = File(...),
comment_id: int | None = Form(None),
) -> JSONResponse:
"""comment_id 를 주면 댓글 채팅 팝업의 첨부(이미지/파일)로도 등록 —
같은 표에 저장되므로 업무 "첨부파일" 패널에도 그대로 함께 나온다."""
user = _require_user(request)
st = _db_or_503(request)
task = st.get_task(task_id=task_id)
if task is None:
raise HTTPException(status_code=404, detail="업무를 찾을 수 없습니다.")
_require_manage(request, st, user, task["project_id"])
if comment_id is not None:
comment = st.get_comment(comment_id=comment_id)
if comment is None or comment["task_id"] != task_id:
raise HTTPException(status_code=400, detail="잘못된 댓글입니다.")
data = await file.read()
if len(data) > _MAX_ATTACH_BYTES:
@@ -1261,7 +1269,7 @@ async def api_upload_attachment(
att = st.add_attachment(
task_id=task_id, filename=orig, stored_name=stored,
content_type=file.content_type or "application/octet-stream",
size_bytes=len(data), uploaded_by=user["email"],
size_bytes=len(data), uploaded_by=user["email"], comment_id=comment_id,
)
return JSONResponse({"attachment": att}, status_code=201)