From 31e266e79ea0b743f7a7e2ea7166dede7351ba86 Mon Sep 17 00:00:00 2001 From: zenn Date: Tue, 7 Apr 2026 14:19:05 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8C=93=EA=B8=80=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/db.js | 16 +- docs/history.md | 4 + docs/map.md | 2 +- docs/spec.md | 2 + docs/todo.md | 2 + docs/update.md | 7 + frontend/src/views/CommentInboxView.vue | 282 ++++++++++++++---------- 7 files changed, 196 insertions(+), 119 deletions(-) diff --git a/backend/src/db.js b/backend/src/db.js index 343996f..56dc8ff 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -272,6 +272,15 @@ function mapCommentNotificationRow(row) { commentId: row.comment_id, parentCommentId: row.parent_comment_id || '', parentCommentContent: row.parent_comment_content || '', + parentCommentCreatedAt: Number(row.parent_comment_created_at || 0), + parentAuthorName: getUserDisplayName({ + nickname: row.parent_author_nickname, + email: row.parent_author_email, + }), + parentAuthorAccountName: getUserAccountName({ + email: row.parent_author_email, + }), + parentAuthorAvatarSrc: row.parent_author_avatar_src || '', notificationType: row.notification_type || 'tierlist_comment', isRead: !!row.is_read, readAt: Number(row.read_at || 0), @@ -2851,6 +2860,7 @@ async function listCommentNotifications(userId, { unreadOnly = false } = {}) { c.parent_comment_id, c.content AS comment_content, parent.content AS parent_comment_content, + parent.created_at AS parent_comment_created_at, t.topic_id, tp.slug AS topic_slug, tp.name AS topic_name, @@ -2858,10 +2868,14 @@ async function listCommentNotifications(userId, { unreadOnly = false } = {}) { t.thumbnail_src AS tierlist_thumbnail_src, actor.nickname AS actor_nickname, actor.email AS actor_email, - actor.avatar_src AS actor_avatar_src + actor.avatar_src AS actor_avatar_src, + parent_author.nickname AS parent_author_nickname, + parent_author.email AS parent_author_email, + parent_author.avatar_src AS parent_author_avatar_src FROM comment_notifications n INNER JOIN tierlist_comments c ON c.id = n.comment_id LEFT JOIN tierlist_comments parent ON parent.id = c.parent_comment_id + LEFT JOIN users parent_author ON parent_author.id = parent.author_id INNER JOIN tierlists t ON t.id = n.tierlist_id INNER JOIN topics tp ON tp.id = t.topic_id INNER JOIN users actor ON actor.id = n.actor_user_id diff --git a/docs/history.md b/docs/history.md index 300079a..16bc3c9 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-04-07 v1.1.9 +- 댓글 관리함은 단순 알림 문구보다 `어느 티어표에서 어떤 루트 댓글이 있었고 그 아래 어떤 새 댓글/답글이 달렸는지`를 카드 한 장 안에서 읽히게 하는 편이 더 중요하다고 정리했다. 그래서 좌측에는 대상 티어표 정보, 우측에는 댓글 흐름 자체를 배치하는 2열 구조를 기본으로 삼는다. +- `commentInboxCard__lead`처럼 제목을 다시 설명하는 보조 문구는 상태 전달에 비해 공간만 차지하므로 제거하고, 대신 실제 댓글 작성자/시간/본문 정보를 바로 보여주는 방향이 낫다고 판단했다. + ## 2026-04-07 v1.1.8 - 댓글은 처음부터 전부 렌더링하지 않고 일부만 보여준 뒤 `더 보기`로 확장하는 방향을 채택했다. 이 프로젝트는 본문이 긴 티어표 프리뷰와 함께 댓글을 보여주므로, 기본 노출 개수를 제한하는 편이 가독성과 레일 안정성에 모두 유리하다. - 댓글 관리 화면 컨트롤은 별도 체크박스 문법을 만들지 않고, 설정/에디터에서 이미 쓰는 토글 스위치와 저장 CTA 톤을 재사용하는 것이 일관성에 맞다고 판단했다. diff --git a/docs/map.md b/docs/map.md index 8290936..c76cf0f 100644 --- a/docs/map.md +++ b/docs/map.md @@ -22,7 +22,7 @@ ## `/comments` - 화면 파일: `frontend/src/views/CommentInboxView.vue` -- 역할: 내 티어표에 달린 댓글과 내 댓글에 달린 답글을 시간순 카드로 확인, 안 읽은 댓글만 보기 필터, 모두 읽음 처리, 카드별 red dot 표시, 티어표 썸네일과 원댓글/새 댓글 비교 블록 제공, 카드 클릭 시 해당 티어표의 특정 댓글 위치로 이동 +- 역할: 내 티어표에 달린 댓글과 내 댓글에 달린 답글을 시간순 카드로 확인, 안 읽은 댓글만 보기 필터, 모두 읽음 처리, 카드별 red dot 표시, 좌측 `썸네일/티어표 제목/템플릿 이름`과 우측 `루트 댓글/새 댓글 또는 답글` 비교 구조 제공, 카드 클릭 시 해당 티어표의 특정 댓글 위치로 이동 - 연동 API: `GET /api/comments/inbox`, `GET /api/comments/inbox/unread-count`, `POST /api/comments/inbox/read` ## `/login` diff --git a/docs/spec.md b/docs/spec.md index 40194a0..34dcfc9 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -52,6 +52,7 @@ - 댓글 알림 메뉴는 좌측 사이드 `댓글 관리`로 노출하며, 읽지 않은 댓글이 하나라도 있으면 빨간 dot을 표시한다. - 댓글 정렬은 루트 댓글 최신순, 각 루트 내부의 답글은 오래된순을 기본 규칙으로 유지한다. - 댓글 표시 밀도 제어를 위해 기본 노출 개수는 루트 댓글 10개, 각 루트의 답글 3개로 제한하고 `더 보기` 버튼으로 추가 노출한다. +- 댓글 관리 카드(`/comments`)는 좌측 `16:9 썸네일 + 티어표 제목 + 템플릿 이름`, 우측 `알림 제목 + 루트 댓글 정보 + 새 댓글/답글 정보`의 2열 구조를 사용한다. - 우측 패널 - 현재 화면 문맥에 맞는 설명, 빠른 액션, 계정 상태 같은 보조 정보를 배치한다. - 에디터/관리자 세부 옵션은 후속 단계에서 이 패널로 점진 이관한다. @@ -172,6 +173,7 @@ - `readAt`: number - `createdAt`: number - 기존 운영 DB에 예전 형태 테이블이 남아 있어도 서버 시작 시 스키마 보정으로 누락 컬럼을 자동 추가한다. + - 댓글 관리 카드 구성을 위해 조회 응답에는 `parentCommentContent`, `parentCommentCreatedAt`, `parentAuthorName`, `parentAuthorAccountName`, `parentAuthorAvatarSrc`를 함께 내려준다. - `templateRequests` - `id`: string - `type`: string diff --git a/docs/todo.md b/docs/todo.md index 2d40c2b..fa25901 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,6 +1,8 @@ # 할 일 및 이슈 ## 단기 확인 +- `v1.1.9` 이후 댓글 관리 카드에서 좌측 썸네일/티어표 정보와 우측 루트 댓글/새 댓글 정보가 실제로 한눈에 읽히는지, 특히 답글 알림에서 부모 댓글 작성자 정보가 자연스럽게 보이는지 확인한다. +- `v1.1.9` 이후 `commentInboxCard__lead` 제거로 정보가 부족해지지 않았는지, 제목과 댓글 블록만으로 상태를 이해할 수 있는지 데스크톱/모바일에서 다시 확인한다. - `v1.1.8` 이후 댓글 더 보기 규칙(루트 10개, 답글 3개)과 남은 개수 표기가 실제 데이터에서 자연스럽게 동작하는지 확인한다. - 댓글 관리 화면의 `안 읽은 댓글만 보기` 토글과 `모두 읽음 처리` 버튼이 설정/에디터의 공통 컨트롤 톤과 이질감이 없는지 확인한다. - `v1.1.7` 이후 댓글 관리 카드 썸네일이 실제로 모든 카드에서 16:9로 유지되는지 데스크톱/모바일에서 다시 확인한다. diff --git a/docs/update.md b/docs/update.md index ebbe29e..142d404 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,12 @@ # 업데이트 로그 +## 2026-04-07 v1.1.9 +- 댓글 관리 화면의 패널과 카드 톤을 댓글 카드(`commentsCard`) 계열과 더 가깝게 다시 정리했다. 바깥 패널은 같은 배경/보더 문법을 쓰고, 개별 알림 카드는 장식성 그림자 대신 단정한 카드 레이어로 맞췄다. +- 댓글 관리 카드의 정보 구조를 다시 설계했다. 왼쪽에는 `16:9 썸네일 / 티어표 제목 / 템플릿 이름`만 모으고, 오른쪽에는 `알림 제목 / 루트 댓글 / 새 댓글 또는 새 답글` 흐름으로 읽히게 정리했다. +- 중복 설명 역할이던 `commentInboxCard__lead`는 제거했다. 이제 카드 제목만 봐도 상태를 이해할 수 있고, 실제 내용 이해는 바로 아래 댓글 정보 블록이 담당한다. +- 댓글 관리 API는 답글 알림에서 부모 댓글 작성자 아바타/이름/작성시간도 함께 내려주도록 확장했다. 그래서 프런트는 `루트 댓글`과 `새 답글`을 각각 작성자 단위로 한눈에 비교해 보여줄 수 있다. +- 확인: `node --check backend/src/db.js`, `npm run build` + ## 2026-04-07 v1.1.8 - 댓글 관리 화면의 상단 컨트롤을 정리했다. `안 읽은 댓글만 보기`는 체크박스 대신 프로젝트 공통 토글 스위치 문법으로 바꾸고, `모두 읽음 처리` 버튼도 저장 CTA 계열과 같은 톤으로 맞췄다. - 댓글 영역에는 `더 보기` 흐름을 추가했다. 루트 댓글은 처음 10개, 답글은 처음 3개만 보여주고, 남은 개수를 표시하는 `댓글 n개 더 보기`, `답글 n개 더 보기` 버튼으로 단계적으로 펼친다. diff --git a/frontend/src/views/CommentInboxView.vue b/frontend/src/views/CommentInboxView.vue index fe46471..fbb6629 100644 --- a/frontend/src/views/CommentInboxView.vue +++ b/frontend/src/views/CommentInboxView.vue @@ -20,6 +20,10 @@ function avatarUrlOf(notification) { return notification.actorAvatarSrc ? toApiUrl(notification.actorAvatarSrc) : '' } +function parentAvatarUrlOf(notification) { + return notification.parentAuthorAvatarSrc ? toApiUrl(notification.parentAuthorAvatarSrc) : '' +} + function tierListThumbnailUrl(notification) { return notification.tierListThumbnailSrc ? toApiUrl(notification.tierListThumbnailSrc) : '' } @@ -28,6 +32,14 @@ function avatarFallbackOf(notification) { return displayInitialFrom(notification.actorName, notification.actorAccountName, '?') } +function parentAvatarFallbackOf(notification) { + return displayInitialFrom(notification.parentAuthorName, notification.parentAuthorAccountName, '?') +} + +function parentDisplayNameOf(notification) { + return notification.parentAuthorName || '알 수 없음' +} + function formatDate(ts) { return new Date(Number(ts || 0)).toLocaleString('ko-KR', { year: 'numeric', @@ -42,10 +54,6 @@ function notificationTitle(notification) { return notification.notificationType === 'comment_reply' ? '내 댓글에 답글이 달렸어요.' : '내 티어표에 새 댓글이 달렸어요.' } -function notificationLead(notification) { - return notification.notificationType === 'comment_reply' ? '원래 댓글과 새 답글을 함께 확인해보세요.' : '내 티어표에 새로 남겨진 댓글입니다.' -} - function emitUnreadCount(unread) { if (typeof window === 'undefined') return window.dispatchEvent(new CustomEvent('tier-maker:comment-inbox-updated', { detail: { unreadCount: unread } })) @@ -154,52 +162,70 @@ watch(unreadOnly, loadInbox) :class="{ 'commentInboxCard--unread': !notification.isRead }" >