feat(project): 댓글·첨부·단계편집·알림센터 + 라이브러리 self-host

아사나식 기능 4종 추가:
- 댓글: 업무 모달에서 등록/삭제, 관련자 인앱 알림(task_comments)
- 첨부: 업로드(20MB)/다운로드/삭제, 파일은 DATA_DIR/project/<task_id>/,
  DB엔 메타만(task_attachments)
- 단계 편집: 보드 칸반에서 이름변경/완료토글/순서이동/삭제/추가
- 알림센터(인앱): 배정·완료·댓글 시 수신자별 알림, 벨 미읽음 배지,
  /project/inbox 페이지, 읽음/모두읽음(project_notifications)

라이브러리 self-host: vis-timeline + Material Symbols 를 static/vendor/ 로
내려받아 CDN 의존 제거.

DB: scripts/sql/project_db_002_attachments_notifications.sql (서버서 적용 필요).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 12:32:00 +09:00
parent ef7f76821d
commit 776f655db4
13 changed files with 1002 additions and 28 deletions
+50
View File
@@ -159,3 +159,53 @@
.pj-color-palette, #pj-f-colors { display: flex; gap: 8px; margin-top: 6px; }
.pj-color-swatch { width: 24px; height: 24px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; }
.pj-color-swatch.is-active { border-color: #1e1f21; box-shadow: 0 0 0 2px #fff inset; }
/* ── 알림 벨 ── */
.pj-toolbar-right { display: flex; align-items: center; gap: 12px; }
.pj-bell { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; border-radius: 10px; color: #525860; text-decoration: none; }
.pj-bell:hover { background: #eceef0; }
.pj-bell-badge { position: absolute; top: 3px; right: 3px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: #e8384f; color: #fff; font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; }
/* ── 업무 모달 댓글/첨부 ── */
.pj-task-extra { border-top: 1px solid #eef0f2; margin-top: 14px; padding-top: 12px; }
.pj-extra-head { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; color: #525860; margin-bottom: 8px; }
.pj-upload-btn { margin-left: auto; font-size: 12px; font-weight: 600; color: #4573d2; cursor: pointer; padding: 3px 8px; border: 1px solid #cdd9f0; border-radius: 7px; }
.pj-upload-btn:hover { background: #eef3fc; }
.pj-attach-list, .pj-comment-list { list-style: none; margin: 0 0 6px; padding: 0; max-height: 160px; overflow-y: auto; }
.pj-attach-item { display: flex; align-items: center; gap: 8px; padding: 5px 4px; font-size: 13px; }
.pj-attach-item a { color: #2b4a85; text-decoration: none; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pj-attach-item a:hover { text-decoration: underline; }
.pj-attach-size { font-size: 11px; color: #9aa1a9; }
.pj-comment-item { display: flex; align-items: flex-start; gap: 8px; padding: 7px 2px; }
.pj-comment-main { flex: 1; }
.pj-comment-head { font-size: 12.5px; font-weight: 700; color: #1e1f21; }
.pj-comment-time { font-weight: 400; color: #9aa1a9; font-size: 11px; margin-left: 6px; }
.pj-comment-body { font-size: 13px; color: #333; margin-top: 1px; white-space: pre-wrap; }
.pj-comment-form { display: flex; gap: 6px; }
.pj-comment-form input { flex: 1; padding: 7px 10px; border: 1px solid #d8dce2; border-radius: 8px; font-size: 13px; font-family: inherit; }
/* ── 보드 단계 컨트롤 ── */
.pj-col-head { position: relative; }
.pj-col-name { font-weight: 700; }
.pj-col-ctrls { display: none; gap: 1px; margin-left: 6px; }
.pj-col:hover .pj-col-ctrls { display: inline-flex; }
.pj-col-ctrls .pj-icon-btn { width: 20px; height: 20px; font-size: 13px; }
.pj-icon-btn[disabled] { opacity: .3; cursor: default; }
.pj-col-add { background: transparent; flex: 0 0 180px; }
.pj-add-stage { width: 100%; padding: 10px; border: 1px dashed #c9cdd3; border-radius: 12px; background: #fff; color: #6b7280; font-size: 13px; font-weight: 600; cursor: pointer; }
.pj-add-stage:hover { border-color: #1e1f21; color: #1e1f21; }
/* ── 알림센터 ── */
.pj-inbox { list-style: none; margin: 0; padding: 0; }
.pj-inbox-item { display: flex; align-items: flex-start; gap: 12px; padding: 14px 12px; border: 1px solid #eef0f2; border-radius: 12px; margin-bottom: 8px; cursor: pointer; background: #fff; }
.pj-inbox-item:hover { border-color: #d8dce2; }
.pj-inbox-item.is-unread { background: #f4f8ff; border-color: #d6e2fb; }
.pj-inbox-icon { color: #4573d2; font-size: 22px; flex: 0 0 auto; }
.pj-inbox-body { flex: 1; }
.pj-inbox-title { font-size: 14px; font-weight: 600; }
.pj-inbox-item.is-unread .pj-inbox-title::before { content: "●"; color: #4573d2; font-size: 9px; vertical-align: middle; margin-right: 6px; }
.pj-inbox-sub { font-size: 12.5px; color: #525860; margin-top: 2px; }
.pj-inbox-meta { font-size: 11px; color: #9aa1a9; margin-top: 4px; }
.pj-inbox-go { align-self: center; padding: 4px 12px; font-size: 12px; }