[FE] SISC1-210 [FIX] : 게시글 작성 모달, 보드액션 부분 오류 수정#116
Conversation
|
Warning Rate limit exceeded@cmmoon03 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 48 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (3)
WalkthroughBoard UI와 라우팅에 팀 컨텍스트가 도입되고, Modal에 드래그·다중 파일 첨부 및 파일 리스트/삭제 UI가 추가되었습니다. Modal/CSS가 반응형 토큰 기반으로 재작성되었고, PostItem과 PostDetail은 팀 기반 경로 및 편집·첨부 흐름을 지원하도록 변경되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User as 사용자
participant Modal as Modal 컴포넌트
participant Board as Board 페이지 상태
Note over User,Modal: 파일 첨부 흐름 (드래그/선택/삭제)
User->>Modal: 파일 드래그/드롭 또는 파일 선택
Modal->>Modal: handleDragOver / handleDrop 처리
Modal->>Board: onFileChange(files)
Board->>Board: setSelectedFiles(files)
Board-->>Modal: selectedFiles prop 전달
User->>Modal: 파일 삭제 클릭
Modal->>Board: onRemoveFile(index)
Board->>Board: selectedFiles에서 항목 제거
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/src/components/Board/BoardActions.module.css(2 hunks)frontend/src/components/Board/Modal.jsx(2 hunks)frontend/src/components/Board/Modal.module.css(2 hunks)
🔇 Additional comments (3)
frontend/src/components/Board/BoardActions.module.css (1)
24-24: 레이아웃 중앙 정렬 방식이 적절합니다.고정 너비(908px)와
margin-left: auto,margin-right: auto를 사용하여 보드 액션 컨테이너를 수평으로 중앙 정렬하는 방식은 올바르게 구현되었습니다.Also applies to: 31-32
frontend/src/components/Board/Modal.jsx (1)
37-37: UI 텍스트 개선이 적절합니다."파일 추가"에 공백을 추가하고 버튼 레이블을 "저장"에서 "게시글 작성"으로 변경한 것은 한국어 표기 규칙에 맞고 사용자에게 더 명확한 의미를 전달합니다.
Also applies to: 69-69
frontend/src/components/Board/Modal.module.css (1)
64-64: 폼 간격 및 버튼 스타일링이 잘 개선되었습니다.폼에
margin-bottom: 27px를 추가하여 적절한 수직 간격을 확보했고, 저장 버튼의 배경색을 기본 테마 색상인 파란색(rgba(29, 128, 244, 1))으로 변경한 것은 일관된 디자인 시스템을 따릅니다.align-self: flex-end와flex-shrink: 0를 사용한 정렬 방식도 flexbox 레이아웃에 적합합니다.Also applies to: 245-245, 247-248
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
frontend/src/components/Board/Modal.module.css (2)
206-249:.select option요소에 토큰 기반 폰트 크기를 적용하세요.
line 248의.select option규칙에서font-size: 16px;이 하드코딩되어 있습니다. 파일 전체에서 토큰 기반 접근을 취하고 있으므로 일관성을 위해var(--font-base)로 수정하는 것을 권장합니다..select option { font-weight: 400; - font-size: 16px; + font-size: var(--font-base); color: rgba(23, 23, 23, 1); background: rgba(255, 255, 255, 1); }
28-29:max-width: 90vw중복 검토를 요청합니다.
line 28의width: clamp(320px, 90vw, 906px)는 이미 뷰포트 너비의 90%를 상한으로 제한합니다.line 29의max-width: 90vw는 중복될 수 있습니다. 반응형 동작을 검증한 후 필요 없다면 제거할 수 있습니다.모바일(320px), 태블릿(768px), 데스크톱(1200px+) 환경에서 모달 크기가 예상대로 작동하는지 확인해주세요. 불필요하다면 다음과 같이 제거할 수 있습니다:
width: clamp(320px, 90vw, 906px); - max-width: 90vw; max-height: 90vh;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
frontend/src/components/Board/Modal.module.css(6 hunks)
🔇 Additional comments (2)
frontend/src/components/Board/Modal.module.css (2)
15-38: 반응형 CSS 토큰 구현이 잘 적용되었습니다.CSS 변수를 활용한 토큰 기반 설계가 일관되게 구현되어 있습니다.
clamp()를 통한 유동적인 스케일링은 모바일부터 데스크톱까지 안정적인 반응형 동작을 보장합니다. 특히 패딩, 마진, 글꼴 크기가 모두 토큰으로 관리되어 유지보수성이 향상되었습니다.
253-274: 버튼 개선사항이 이전 리뷰 피드백을 잘 반영합니다.호버 색상이
rgba(24, 102, 195, 1)로 수정되어 자연스러운 시각적 피드백을 제공합니다. 추가로align-self: flex-end와flex-shrink: 0을 통해 버튼이 의도한 위치에 고정되며,:active상태 추가로 완전한 상호작용 경험을 제공합니다. PR 목표인 "돌출된 버튼" 문제 해결이 잘 구현되었습니다.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
frontend/src/components/Board/PostItem.jsx (1)
14-22: 프로덕션 코드에 디버깅용 console.log 문 제거 필요
handleCardClick함수 내부에 디버깅용console.log문이 여러 개 남아있습니다(16-19줄). 이는 브라우저 콘솔을 어지럽히고 불필요한 성능 오버헤드를 발생시킬 수 있습니다.다음 diff를 적용하여 디버깅 코드를 제거하세요:
const handleCardClick = () => { const team = currentTeam || 'all'; - console.log('=== 게시글 클릭 ==='); - console.log('post.id:', post.id); - console.log('team:', team); - console.log('이동할 URL:', `/board/${team}/${post.id}`); - navigate(`/board/${team}/${post.id}`, { state: { post } }); };frontend/src/pages/PostDetail.jsx (2)
21-24: 프로덕션 코드에 디버깅용 console.log 문 제거 필요컴포넌트 렌더링 시 실행되는 디버깅 로그가 남아있습니다. 브라우저 콘솔을 어지럽히므로 제거해야 합니다.
다음 diff를 적용하세요:
const PostDetail = () => { const { postId, team } = useParams(); const navigate = useNavigate(); const location = useLocation(); const [post, setPost] = useState(null); const [loading, setLoading] = useState(true); - - console.log('=== PostDetail 렌더링 ==='); - console.log('postId:', postId); - console.log('team:', team); - console.log('location.state:', location.state); -
72-98: useEffect 내부의 디버깅 로그 제거 필요게시글 로딩 로직은 올바르게 구현되었으나, 과도한 디버깅 로그(73, 77, 81-87, 95줄)가 남아있습니다.
다음 diff를 적용하여 디버깅 로그를 제거하세요:
useEffect(() => { - console.log('=== useEffect 실행 ==='); setLoading(true); let currentPost = location.state?.post; - - console.log('currentPost from location.state:', currentPost); - if (!currentPost) { const allPosts = getPostsFromStorage(); - console.log('모든 게시글:', allPosts); - console.log('찾는 postId:', postId, 'type:', typeof postId); - currentPost = allPosts.find((p) => { - console.log('비교:', p.id, '===', parseInt(postId, 10)); - return p.id === parseInt(postId, 10); - }); - console.log('찾은 게시글:', currentPost); + currentPost = allPosts.find((p) => p.id === parseInt(postId, 10)); } setPost(currentPost); setLoading(false); if (currentPost) { setComments(getCommentsFromStorage(currentPost.id)); } else { - console.log('❌ 게시글을 찾을 수 없습니다!'); setComments([]); } }, [postId, location.state]);frontend/src/pages/Board.jsx (2)
10-12: 프로덕션 코드에 디버깅용 console.log 문 제거 필요컴포넌트 렌더링 시마다 실행되는 디버깅 로그가 남아있습니다.
다음 diff를 적용하세요:
const Board = () => { const { team } = useParams(); - console.log('=== Board 렌더링 ==='); - console.log('현재 team:', team); -
180-191: PostItem 렌더링 시 디버깅 로그 제거 필요PostItem 렌더링 시마다 console.log가 실행되어 성능에 영향을 줄 수 있습니다.
다음 diff를 적용하세요:
{filteredPosts.length > 0 ? ( - filteredPosts.map((post) => { - console.log('PostItem 렌더링, post.id:', post.id, 'team:', team); - return ( - <PostItem - key={post.id} - post={post} - currentTeam={team} - onLike={handleLike} - onBookmark={handleBookmark} - /> - ); - }) + filteredPosts.map((post) => ( + <PostItem + key={post.id} + post={post} + currentTeam={team} + onLike={handleLike} + onBookmark={handleBookmark} + /> + ))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
frontend/src/components/Board/Modal.jsx(4 hunks)frontend/src/components/Board/Modal.module.css(6 hunks)frontend/src/components/Board/PostItem.jsx(1 hunks)frontend/src/pages/Board.jsx(6 hunks)frontend/src/pages/PostDetail.jsx(3 hunks)frontend/src/pages/PostDetail.module.css(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/Board/Modal.jsx
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/components/Board/PostItem.jsx (1)
frontend/src/pages/PostDetail.jsx (2)
navigate(16-16)post(18-18)
frontend/src/pages/Board.jsx (2)
frontend/src/pages/PostDetail.jsx (4)
useParams(15-15)post(18-18)handleLike(101-117)handleBookmark(120-132)frontend/src/components/Board/PostItem.jsx (1)
PostItem(11-78)
🔇 Additional comments (4)
frontend/src/pages/Board.jsx (1)
47-89: 파일 처리 로직이 잘 구현되었습니다파일 이름과 크기를 기준으로 중복을 감지하고 교체하는 로직이 명확하게 구현되었습니다. 사용자에게 교체 알림을 제공하는 것도 좋은 UX입니다.
참고: 현재는 파일 메타데이터만 저장하고 실제 업로드는 구현되지 않았습니다. 향후 서버 연동 시 실제 파일 업로드 로직 추가가 필요합니다.
frontend/src/components/Board/Modal.module.css (3)
14-38: 반응형 CSS 토큰 시스템이 잘 설계되었습니다CSS 커스텀 속성을 활용한 반응형 토큰 시스템(spacing, font, input-height, button-padding)이 일관되게 적용되었습니다.
clamp()함수를 사용하여 다양한 화면 크기에서 유연하게 대응할 수 있도록 구현되었습니다.모달의 오버플로 처리도
overflow: hidden으로 변경되어 모서리가 잘 유지됩니다(37줄).
69-97: 스크롤 기능 및 스타일링이 적절하게 추가되었습니다폼 컨테이너에 세로 스크롤(
overflow-y: auto)을 추가하고, 커스텀 스크롤바 스타일을 적용하여 사용자 경험을 개선했습니다. 스크롤바 공간을 위한padding-right: 8px처리도 적절합니다.
171-206: 파일 첨부 UI가 적절하게 구현되었습니다새로 추가된 파일 목록 UI 클래스들(
.fileList,.fileItem,.fileName,.removeFileButton)이 Board.jsx 및 Modal.jsx의 파일 첨부 기능과 잘 연동됩니다.특히 다음 사항들이 잘 구현되었습니다:
.fileName의word-break: break-all로 긴 파일명 처리(190줄).removeFileButton의flex-shrink: 0으로 버튼 크기 고정(201줄)- 호버 시 색상 변경으로 인터랙션 피드백 제공(204-206줄)
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/pages/Board.jsx (1)
41-45: 모달 닫을 때 selectedFiles 초기화 누락
handleCloseModal에서title과content는 초기화하지만selectedFiles는 초기화하지 않습니다. 사용자가 파일을 첨부한 후 저장하지 않고 모달을 닫으면, 다음 모달 오픈 시 이전 파일이 남아있게 됩니다.const handleCloseModal = () => { setShowModal(false); setTitle(''); setContent(''); + setSelectedFiles([]); };
🧹 Nitpick comments (3)
frontend/src/pages/Board.jsx (3)
10-12: 프로덕션 코드에서 console.log 제거 필요디버그용
console.log문은 프로덕션 배포 전에 제거해야 합니다. 불필요한 콘솔 출력은 성능에 영향을 주고 민감한 정보가 노출될 수 있습니다.const { team } = useParams(); - console.log('=== Board 렌더링 ==='); - console.log('현재 team:', team); -
77-89: alert() 대신 비차단 알림 방식 고려
alert()는 UI를 차단하여 사용자 경험을 저해합니다. 토스트 메시지나 인라인 알림으로 대체하는 것이 좋습니다. 또한 디버그용console.log도 제거가 필요합니다.// 교체된 파일이 있으면 알림 if (replacedFileNames.length > 0) { - alert(`교체됨: ${replacedFileNames.join(', ')}`); + // TODO: 토스트 메시지로 대체 권장 + console.info(`파일 교체됨: ${replacedFileNames.join(', ')}`); } return updatedFiles; }); // input 초기화 e.target.value = ''; - - console.log('파일 처리 완료'); };
180-191: 렌더링 루프 내 console.log 제거 필요
map콜백 내console.log는 게시글 수에 비례하여 반복 실행되므로 성능에 영향을 줄 수 있습니다. 디버깅 완료 후 제거해 주세요.filteredPosts.map((post) => { - console.log('PostItem 렌더링, post.id:', post.id, 'team:', team); - return ( - <PostItem - key={post.id} - post={post} - currentTeam={team} - onLike={handleLike} - onBookmark={handleBookmark} - /> - ); - }) + <PostItem + key={post.id} + post={post} + currentTeam={team} + onLike={handleLike} + onBookmark={handleBookmark} + /> + ))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
frontend/src/pages/Board.jsx(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/Board.jsx (2)
frontend/src/pages/PostDetail.jsx (4)
useParams(15-15)post(18-18)handleLike(101-117)handleBookmark(120-132)frontend/src/components/Board/PostItem.jsx (1)
PostItem(11-78)
🔇 Additional comments (2)
frontend/src/pages/Board.jsx (2)
107-111: 파일 데이터 손실 가능성 확인 필요현재 파일의 메타데이터(name, size, type)만 저장하고 있어, 실제 파일 내용은
localStorage에 저장되지 않습니다. 페이지 새로고침 후 첨부파일 다운로드나 표시가 불가능합니다.의도된 동작인지, 아니면 향후 백엔드 연동 시 파일 업로드 로직 추가가 필요한지 확인해 주세요.
197-208: Modal 파일 첨부 통합 LGTM파일 관련 props(
selectedFiles,onFileChange,onRemoveFile)가 올바르게 전달되고 있으며, 이전 리뷰에서 지적된handleSaved→handleSave오류도 수정되었습니다.
| ); | ||
| }; | ||
|
|
||
| const handleSave = () => { |
There was a problem hiding this comment.
아래의 onSave 속성에 적용시키려고 만드신 것 같은데 정작 아래에는 handleSaved 라고 적으셨네요. 항상 개발하고 테스트 해보는 습관을 들여보세요.
| const { team } = useParams(); | ||
| console.log('=== Board 렌더링 ==='); | ||
| console.log('현재 team:', team); | ||
|
|
There was a problem hiding this comment.
개발하면서 확인용으로 두신 거면, PR이 merge되기 전에 이러한 콘솔 로그는 모두 삭제해주시면 감사하겠습니다.
There was a problem hiding this comment.
제가 정신이 없어서 제대로 확인을 못했습니다. 콘솔로그 전체 삭제했습니다.
| const [selectedFiles, setSelectedFiles] = useState([]); | ||
|
|
||
| useEffect(() => { | ||
| localStorage.setItem('boardPosts', JSON.stringify(posts)); |
There was a problem hiding this comment.
api 연결하면서 아마 로컬스토리지는 사용하지 않으셔도 될 거에요.
There was a problem hiding this comment.
api 연결은 다른 브랜치에서 작업하고 있어서 기능 구현부터 하고서 연결하려고 했습니다.
frontend/src/pages/Board.jsx
Outdated
| // input 초기화 | ||
| e.target.value = ''; | ||
|
|
||
| console.log('파일 처리 완료'); |
| e.stopPropagation(); | ||
|
|
||
| const droppedFiles = Array.from(e.dataTransfer.files); | ||
| onFileChange({ target: { files: droppedFiles } }); |
There was a problem hiding this comment.
onFileChange 라는 메서드가 정의되어 있지 않아요.. 확인 해보세요..
There was a problem hiding this comment.
onFileChange는 Modal의 props로 부모 컴포넌트(Board.jsx)로부터 전달받는 함수입니다. Board.jsx의 46번째 줄에서 handleFileChange로 정의되어 있고, 204번째 줄에서 Modal에 전달됩니다.
| console.log('=== 게시글 클릭 ==='); | ||
| console.log('post.id:', post.id); | ||
| console.log('team:', team); | ||
| console.log('이동할 URL:', `/board/${team}/${post.id}`); |
There was a problem hiding this comment.
스타일 클래스를 만들어놓고 왜 style={{ cursor: 'pointer' }} 코드를 추가하신 거에요?
There was a problem hiding this comment.
Files changed에서 해당 리뷰가 안 보여서 직접 작성합니다. 알림 여러번 가더라도 양해부탁드립니다. 디버깅할때 편하려고 추가했습니다.
frontend/src/pages/PostDetail.jsx
Outdated
| console.log('=== PostDetail 렌더링 ==='); | ||
| console.log('postId:', postId); | ||
| console.log('team:', team); | ||
| console.log('location.state:', location.state); |
There was a problem hiding this comment.
참 많네요... 머지 되기 전에 지워주세요. 아래에 더 많은데 따로 코멘트 안 남길테니 알아서 지워주세요.
DongEun02
left a comment
There was a problem hiding this comment.
vsc 자체에서 알려주는 오류는 해결하고 PR 올려주세요. 그리고 AI 쓰는 건 상관없는데 AI 코드 그대로 올리지말고 자체적으로 수정해주세요. 이러면 나중에 더 힘들어집니다.
| content, | ||
| setContent, | ||
| selectedFiles, | ||
| onFileChange, |
There was a problem hiding this comment.
props로 onFileChange를 받았는데 사용은 Modal 컴포넌트 바깥에서 하니까 정의되어 있지 않다고 나옵니다. 위치 옮겨주세요.
| resize: none; | ||
| flex: 1; | ||
| font-family: | ||
| 'Pretendard', |
There was a problem hiding this comment.
저번에 제가 index.css에서 font-family 지정해놔서 필요 없다고 리뷰 달았는데 또 해놓으셨네요. 지워주세요.
There was a problem hiding this comment.
이전에 삭제했다가 form 관련 요소 내부에는 폰트가 적용 안돼서 최근 추가했습니다.
- 인라인 편집 모드 구현 - 첨부파일 추가/삭제 기능 - 북마크 카운트 기능 - 코드 정리
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/pages/Board.jsx (1)
39-43: 모달 닫을 때selectedFiles초기화 누락
handleCloseModal에서title과content는 초기화하지만selectedFiles는 초기화하지 않습니다. 모달을 닫았다가 다시 열면 이전에 선택한 파일이 그대로 남아있게 됩니다.const handleCloseModal = () => { setShowModal(false); setTitle(''); setContent(''); + setSelectedFiles([]); };
♻️ Duplicate comments (1)
frontend/src/components/Board/PostItem.jsx (1)
25-27: 인라인 스타일을 CSS 모듈로 이동 권장
style={{ cursor: 'pointer' }}가 인라인으로 적용되어 있습니다. 이전 리뷰에서도 지적된 부분으로, CSS 모듈에 정의하는 것이 일관성 있습니다.
🧹 Nitpick comments (3)
frontend/src/pages/PostDetail.jsx (3)
82-89:setLoading(false)중복 호출Line 82에서 이미
setLoading(false)를 호출했는데, Line 89에서 다시 호출하고 있습니다. 중복 호출을 제거하세요.setPost(currentPost); setLoading(false); if (currentPost) { setEditTitle(currentPost.title); setEditContent(currentPost.content); } - setLoading(false); - if (currentPost) {
117-118: 중복 주석 제거 필요
// 북마크 토글주석이 두 번 작성되어 있습니다. 과거 리뷰에서도 머지 전 정리 요청이 있었습니다.- // 북마크 토글 // 북마크 토글 const handleBookmark = () => {
360-382: 파일 목록 렌더링에서 index를 key로 사용
key={existing-${index}}및key={new-${index}}형태로 index를 key로 사용하고 있습니다. 파일 삭제 시 인덱스가 변경되어 리렌더링 이슈가 발생할 수 있습니다. 파일명 + 크기 조합 또는 고유 ID 사용을 권장합니다.- <div - key={`existing-${index}`} + <div + key={`existing-${file.name}-${file.size}`}Also applies to: 386-411
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
frontend/src/components/Board/Modal.jsx(4 hunks)frontend/src/components/Board/PostItem.jsx(1 hunks)frontend/src/pages/Board.jsx(7 hunks)frontend/src/pages/PostDetail.jsx(6 hunks)frontend/src/pages/PostDetail.module.css(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/pages/PostDetail.module.css
🧰 Additional context used
🧬 Code graph analysis (3)
frontend/src/pages/PostDetail.jsx (2)
frontend/src/pages/Board.jsx (1)
useParams(10-10)frontend/src/utils/TimeUtils.js (2)
getTimeAgo(1-26)getTimeAgo(1-26)
frontend/src/components/Board/Modal.jsx (1)
frontend/src/pages/Board.jsx (1)
selectedFiles(29-29)
frontend/src/pages/Board.jsx (1)
frontend/src/pages/PostDetail.jsx (2)
useParams(16-16)post(19-19)
🔇 Additional comments (11)
frontend/src/pages/PostDetail.jsx (2)
139-145: 수정 모드 진입 시 파일 상태 초기화 로직 확인
handleEdit에서post.files가undefined일 경우 빈 배열로 처리하는 방어 코드가 있어 좋습니다.
172-206: 수정 저장 로직 구현 확인제목/내용 유효성 검사와 기존 파일 + 새 파일 병합 로직이 잘 구현되어 있습니다. 저장 후
newFiles초기화도 적절합니다.frontend/src/components/Board/PostItem.jsx (2)
11-18: 팀 컨텍스트를 활용한 네비게이션 경로 구성 확인
currentTeamprop을 추가하고 기본값으로'all'을 사용하는 것은 적절합니다.Board.jsx에서team파라미터를 전달받아 일관된 라우팅이 가능합니다.
76-76:displayName설정 확인
React.memo로 감싼 컴포넌트에displayName을 설정하여 React DevTools에서 디버깅이 용이합니다.frontend/src/components/Board/Modal.jsx (3)
55-71: 파일 섹션에 드래그 앤 드롭 지원 추가 확인
onDragOver와onDrop이벤트 핸들러가 적절히 연결되어 있고,multiple속성으로 다중 파일 선택도 지원합니다.
82-102: 첨부 파일 목록 UI 구현 확인선택된 파일 목록을 표시하고 개별 삭제 기능을 제공합니다.
selectedFiles && selectedFiles.length > 0조건으로 null/undefined 케이스를 적절히 처리하고 있습니다.
107-108: 접근 권한 선택 기능 미완성
<select>요소가defaultValue만 설정되어 있고onChange핸들러가 없습니다. 현재는 UI만 존재하고 실제 기능이 구현되지 않은 상태입니다. 추후 구현 예정이라면 TODO 주석을 추가하는 것을 권장합니다.frontend/src/pages/Board.jsx (4)
45-85: 파일 선택 핸들러 중복 처리 로직 확인동일한 파일명에 대해 크기가 같으면 무시하고, 크기가 다르면 교체하는 로직이 잘 구현되어 있습니다. 사용자에게 교체된 파일을 알림으로 알려주는 UX도 적절합니다.
94-112: 새 게시글 생성 시 파일 데이터 포함 확인
selectedFiles에서 필요한 속성(name, size, type)만 추출하여 저장하는 것은 좋은 접근입니다. File 객체 전체를 localStorage에 저장하면 직렬화 문제가 발생할 수 있기 때문입니다.
128-142: 북마크 카운트 초기값 처리
post.bookmarkCount || 1과|| 0패턴으로undefined케이스를 처리하고 있습니다. 기존 데이터와의 호환성을 위한 방어 코드로 적절합니다.
183-190: PostItem에 currentTeam prop 전달 확인
team파라미터를currentTeam으로 전달하여 각 게시글 클릭 시 올바른 경로로 네비게이션됩니다.
| const handleDrop = (e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
|
|
||
| const droppedFiles = Array.from(e.dataTransfer.files); | ||
| onFileChange({ target: { files: droppedFiles } }); | ||
| }; |
There was a problem hiding this comment.
드래그 앤 드롭 핸들러에서 가짜 이벤트 객체 생성
onFileChange에 { target: { files: droppedFiles } } 형태의 객체를 전달하고 있습니다. 이 방식은 동작하지만, Board.jsx의 handleFileChange에서 e.target.value = ''를 호출할 때 에러가 발생할 수 있습니다. 드롭된 파일의 경우 target.value가 존재하지 않기 때문입니다.
Board.jsx의 handleFileChange에서 방어 코드 추가를 권장합니다:
// Board.jsx - handleFileChange 끝부분
if (e.target.value !== undefined) {
e.target.value = '';
}🤖 Prompt for AI Agents
In frontend/src/components/Board/Modal.jsx around lines 22–28, the synthetic
event passed to onFileChange lacks a target.value which causes an error when
Board.jsx's handleFileChange attempts to reset e.target.value; fix by either
including a value property on the fake event (so target has files and value) OR,
preferably, update Board.jsx's handleFileChange to defensively check that
e.target.value is defined before setting it (only clear it when present).
frontend/src/pages/Board.jsx
Outdated
| // input 초기화 | ||
| e.target.value = ''; | ||
| }; |
There was a problem hiding this comment.
드래그 앤 드롭 시 e.target.value 접근 오류 가능성
Modal.jsx의 handleDrop에서 가짜 이벤트 객체를 생성하여 onFileChange를 호출합니다. 이때 e.target.value가 undefined이므로 Line 84의 e.target.value = '' 할당이 문제를 일으킬 수 있습니다.
- // input 초기화
- e.target.value = '';
+ // input 초기화 (드래그 앤 드롭 시에는 target.value가 없을 수 있음)
+ if (e.target && e.target.value !== undefined) {
+ e.target.value = '';
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // input 초기화 | |
| e.target.value = ''; | |
| }; | |
| // input 초기화 (드래그 앤 드롭 시에는 target.value가 없을 수 있음) | |
| if (e.target && e.target.value !== undefined) { | |
| e.target.value = ''; | |
| } | |
| }; |
🤖 Prompt for AI Agents
In frontend/src/pages/Board.jsx around lines 83 to 85, the code unconditionally
sets e.target.value = '' which will throw when handleDrop calls onFileChange
with a fake event lacking target; change the code to first check that e &&
e.target && typeof e.target.value !== 'undefined' before assigning, or prefer
clearing the file input via a ref (if used) instead of relying on the passed
event; update the assignment to only run when the target exists (or replace with
ref.current.value = ''), ensuring no error when a synthetic/no-target event is
passed.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/pages/Board.jsx (1)
29-43: 모달 닫기 시selectedFiles가 초기화되지 않는 문제지금은:
handleCloseModal에서showModal,title,content만 초기화하고,handleSave에서도handleCloseModal만 호출하기 때문에,이전에 선택해 둔
selectedFiles가 모달을 닫거나 저장한 뒤에도 그대로 남습니다.그 결과:
- 글 작성을 취소했다가 다시 열었을 때 이전에 선택한 파일이 그대로 보이고,
- 사용자가 파일을 손보지 않고 저장하면, 원래 취소하려던 파일들이 다음 글에 그대로 첨부될 수 있습니다.
모달을 닫을 때 파일 선택도 함께 리셋되도록 하는 편이 자연스러워 보입니다.
아래처럼
handleCloseModal에서selectedFiles도 같이 초기화하는 것을 제안드립니다.- const handleCloseModal = () => { - setShowModal(false); - setTitle(''); - setContent(''); - }; + const handleCloseModal = () => { + setShowModal(false); + setTitle(''); + setContent(''); + setSelectedFiles([]); + };이렇게 하면 저장/취소/닫기 모두 동일하게 상태를 정리할 수 있습니다.
Also applies to: 95-113
🧹 Nitpick comments (4)
frontend/src/components/Board/PostItem.module.css (1)
10-12: 카드 클릭 가능 UI 스타일 추가 적절게시글 카드를 클릭형 인터랙션으로 쓰기 위한
cursor: pointer추가가 명확해서 좋습니다. 필요하다면 hover 시 살짝 배경색/그림자 효과를 주면 사용자가 더 잘 인지할 수 있을 것 같습니다.frontend/src/pages/Board.jsx (2)
29-93: 파일 선택/교체 로직 전반적으로 잘 구성됨
selectedFiles를 기준으로
- 동일 이름 & 동일 크기 파일은 무시,
- 동일 이름 & 다른 크기 파일은 교체 후
alert로 안내,- 나머지는 신규 추가,
하는 흐름이 직관적이고,
setSelectedFiles를 함수형 업데이트로 사용한 점도 좋습니다.
e.target.value가 존재할 때만 초기화하도록 한 조건 분기 덕분에 드래그 앤 드롭 등에서 가짜 이벤트 객체를 넘기는 경우에도 에러 없이 동작할 여지가 커졌습니다. 다만 파일이 많아질 수 있는 서비스라면, 나중에는alert대신 모달/토스트 등으로 교체를 안내하는 UX 개선을 고려해 보셔도 좋겠습니다.
198-207: Modal에 파일 관련 props 전달 구조 확인 권장
Modal에selectedFiles,onFileChange,onRemoveFile을 넘겨서 작성 모달 내부에서 파일 리스트/삭제/드래그 앤 드롭을 처리하도록 분리한 구조는 좋습니다.다만
Modal컴포넌트가 다른 페이지에서도 재사용되고 있다면, 새로 추가된 props들이 필수가 아니라 선택(props optional)으로 안전하게 처리되는지 한 번만 확인해 주세요. 그렇지 않다면 기존 사용처에서 빠뜨린 props 때문에 런타임 에러가 날 수 있습니다.frontend/src/pages/PostDetail.jsx (1)
136-203: 게시글 수정 + 첨부파일 편집 플로우 전체적으로 좋고, 약간의 상태 업데이트 개선 여지
isEdit플래그로 보기/수정 모드를 명확히 분리하고,- 수정 모드에서만
- 기존 파일(
editFiles),- 새 파일(
newFiles)
를 따로 관리한 뒤,handleSaveEdit에서 두 리스트를 합쳐files로 저장하는 구조가 명확하고 유지보수하기 좋아 보입니다.- 취소 시에는
post의 원본 값을 다시editTitle,editContent에 넣어서 내용 편집이 잘 되돌려집니다.몇 가지 작은 개선 제안입니다:
- 파일 관련 상태는 함수형 업데이트 사용 권장
handleAddNewFile,handleRemoveExistingFile,handleRemoveNewFile에서 기존 state를 바로 캡처해 쓰고 있는데, 이벤트가 연달아 발생하는 경우를 고려하면 함수형 업데이트가 더 안전합니다.const handleRemoveExistingFile = (index) => { - setEditFiles(editFiles.filter((_, i) => i !== index)); + setEditFiles((prev) => prev.filter((_, i) => i !== index)); }; const handleAddNewFile = (e) => { const files = Array.from(e.target.files); - setNewFiles([...newFiles, ...files]); + setNewFiles((prev) => [...prev, ...files]); e.target.value = ''; }; const handleRemoveNewFile = (index) => { - setNewFiles(newFiles.filter((_, i) => i !== index)); + setNewFiles((prev) => prev.filter((_, i) => i !== index)); };
- DOM 접근은 가능하면 ref로 감싸기
document.getElementById('editFileUpload').click()대신useRef로 input을 참조하면, 컴포넌트 리팩터링/ID 변경 시에도 더 안전합니다. (필수는 아니지만 장기적으로는 ref 기반이 관리에 유리합니다.)전반적으로 기능은 잘 동작할 것으로 보이고, 위 개선은 추후 리팩터링 시 고려해 보시면 좋겠습니다.
Also applies to: 343-449
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/src/components/Board/PostItem.module.css(1 hunks)frontend/src/pages/Board.jsx(7 hunks)frontend/src/pages/PostDetail.jsx(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/Board.jsx (1)
frontend/src/pages/PostDetail.jsx (3)
useParams(16-16)newFiles(26-26)post(19-19)
🔇 Additional comments (6)
frontend/src/pages/Board.jsx (2)
7-11: URL 기반 team 정보를 PostItem으로 전달하는 구조 적절
useParams로 가져온team을 그대로PostItem의currentTeam으로 넘겨서 카드 클릭 시 팀별 상세 경로로 이동하도록 한 흐름이 자연스럽습니다. 라우터 설정에 따라/board/:team외에/board단독 경로에서도 이 컴포넌트를 쓰고 있다면, 그 경우에도PostItem내부의 기본값 처리('all'등)와 일관되게 동작하는지만 실제 화면에서 한 번만 확인해 보시면 좋겠습니다.Also applies to: 185-189
103-110: 북마크 카운트 처리 로직 일관성 좋음북마크 토글 시
isBookmarked를 반전시키고,bookmarkCount는||연산자를 사용해undefined인 예전 데이터도 안전하게 처리하는 방식으로 구현하신 부분이 좋아 보입니다. 특히
(post.bookmarkCount || 1) - 1/(post.bookmarkCount || 0) + 1패턴 덕분에 과거에bookmarkCount필드가 없던 게시글도 자연스럽게 0↔1로 왕복되겠습니다.Also applies to: 129-139
frontend/src/pages/PostDetail.jsx (4)
16-27: 라우트 기반 로딩 + 편집 초기화 플로우가 자연스러움
useParams에서postId,team을 받고,
location.state?.post가 있으면 그대로 사용,- 없으면
localStorage에서postId로 찾아오는 fallback,- 찾은 후에는 곧바로
editTitle,editContent를 초기화,하는 흐름이 상세 페이지 진입 시 UX와 데이터 동기화 면에서 잘 설계된 것 같습니다.
currentPost가 없을 때는 댓글도 빈 배열로 초기화해 주어 에러 케이스도 무난히 처리되고 있습니다.Also applies to: 73-95
115-134: 상세 페이지에서도 북마크/좋아요 카운트 일관성 유지상세 페이지에서:
handleBookmark가isBookmarked토글 +bookmarkCount증감(초기undefined안전 처리 포함),handleLike가isLiked토글 +likeCount증감,을 처리하고, 하단 댓글 섹션의 액션 버튼에서 동일한 값을 렌더링하는 구조가 Board 목록과 잘 맞습니다.
bookmarkCount > 0일 때만 숫자를 보여주는 조건도 자연스럽고, 로직과 UI 간의 일관성이 좋아 보입니다.Also applies to: 464-495
205-213: 게시글 삭제 후 이동 경로 수정이 팀 컨텍스트와 잘 맞음삭제 시
navigate(/board/${team || 'all'})로 이동하도록 바꾸신 부분이:
team이 있는 경우: 해당 팀 게시판으로,- 없는 경우:
/board/all로,일관되게 보내 주어, 앞서 Board/PostItem 쪽에서 사용 중인 기본값 전략과도 잘 맞는 것 같습니다.
278-451: 편집 모드와 댓글 섹션의 모드 분리가 명확함
- 제목/내용/첨부파일은
isEdit에 따라 입력 컴포넌트 ↔ 정적 텍스트로 잘 토글되고,- 첨부파일 영역도
- 수정 모드: 기존/새 파일 리스트 + 삭제 버튼 + 파일 추가 버튼,
- 일반 모드: 파일이 있을 때만 목록 표시,
로 분리되어 있어 사용자가 현재 상태를 이해하기 쉽습니다.- 수정 모드일 때 댓글 작성/목록 UI 전체를 숨기고, 수정 완료/취소 버튼만 보여주는 점도 UX 상 자연스럽습니다.
렌더링 조건과 상태 플래그가 깔끔하게 맞물려 있어서 유지보수하기에도 괜찮은 구조로 보입니다.
Also applies to: 464-571
| {isEdit ? ( | ||
| <input | ||
| className={styles.editTitleInput} | ||
| type="text" | ||
| value={editTitle} | ||
| onChange={(e) => setEditTitle(e.target.value)} | ||
| placeholder="제목을 입력하세요" | ||
| /> | ||
| ) : ( | ||
| <h1 className={styles.title}>{post.title}</h1> | ||
| )} | ||
|
|
||
| {!isEdit && ( | ||
| <div className={styles.menuContainer}> | ||
| <button | ||
| className={styles.menuButton} | ||
| onClick={() => setShowMenu(!showMenu)} | ||
| > | ||
| ⋮ | ||
| </button> | ||
| {showMenu && ( | ||
| <div className={styles.menuDropdown}> | ||
| <button onClick={handleEdit}> | ||
| <img | ||
| src={EditIcon} | ||
| className={styles.EditIcon} | ||
| alt="수정" | ||
| /> | ||
| 수정하기 | ||
| </button> | ||
| <button onClick={handleDelete}> | ||
| <img | ||
| src={DeleteIcon} | ||
| className={styles.DeleteIcon} | ||
| alt="삭제" | ||
| /> | ||
| 삭제하기 | ||
| </button> | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} | ||
| </div> |
There was a problem hiding this comment.
상단 ⋮ 메뉴 버튼의 접근성 속성 보완 제안
상단 제목 우측의 ⋮ 메뉴 버튼은 시각적으로는 명확하지만, 하단 댓글 메뉴 버튼과 달리 aria-label이 없습니다. 스크린리더 사용자를 위해 어떤 메뉴인지 설명이 필요해 보입니다.
예를 들어:
- <button
- className={styles.menuButton}
- onClick={() => setShowMenu(!showMenu)}
- >
+ <button
+ className={styles.menuButton}
+ onClick={() => setShowMenu(!showMenu)}
+ aria-label="게시글 메뉴 열기"
+ >
⋮
</button>와 같이 보완해 두면 좋겠습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {isEdit ? ( | |
| <input | |
| className={styles.editTitleInput} | |
| type="text" | |
| value={editTitle} | |
| onChange={(e) => setEditTitle(e.target.value)} | |
| placeholder="제목을 입력하세요" | |
| /> | |
| ) : ( | |
| <h1 className={styles.title}>{post.title}</h1> | |
| )} | |
| {!isEdit && ( | |
| <div className={styles.menuContainer}> | |
| <button | |
| className={styles.menuButton} | |
| onClick={() => setShowMenu(!showMenu)} | |
| > | |
| ⋮ | |
| </button> | |
| {showMenu && ( | |
| <div className={styles.menuDropdown}> | |
| <button onClick={handleEdit}> | |
| <img | |
| src={EditIcon} | |
| className={styles.EditIcon} | |
| alt="수정" | |
| /> | |
| 수정하기 | |
| </button> | |
| <button onClick={handleDelete}> | |
| <img | |
| src={DeleteIcon} | |
| className={styles.DeleteIcon} | |
| alt="삭제" | |
| /> | |
| 삭제하기 | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| {isEdit ? ( | |
| <input | |
| className={styles.editTitleInput} | |
| type="text" | |
| value={editTitle} | |
| onChange={(e) => setEditTitle(e.target.value)} | |
| placeholder="제목을 입력하세요" | |
| /> | |
| ) : ( | |
| <h1 className={styles.title}>{post.title}</h1> | |
| )} | |
| {!isEdit && ( | |
| <div className={styles.menuContainer}> | |
| <button | |
| className={styles.menuButton} | |
| onClick={() => setShowMenu(!showMenu)} | |
| aria-label="게시글 메뉴 열기" | |
| > | |
| ⋮ | |
| </button> | |
| {showMenu && ( | |
| <div className={styles.menuDropdown}> | |
| <button onClick={handleEdit}> | |
| <img | |
| src={EditIcon} | |
| className={styles.EditIcon} | |
| alt="수정" | |
| /> | |
| 수정하기 | |
| </button> | |
| <button onClick={handleDelete}> | |
| <img | |
| src={DeleteIcon} | |
| className={styles.DeleteIcon} | |
| alt="삭제" | |
| /> | |
| 삭제하기 | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> |
🤖 Prompt for AI Agents
In frontend/src/pages/PostDetail.jsx around lines 278 to 320, the top-right "⋮"
menu button lacks accessibility attributes for screen readers; add an
appropriate aria-label (e.g., "게시물 옵션 메뉴" or "More options") to the button, and
also include aria-haspopup="true" and aria-expanded={showMenu} to convey that it
controls a popup menu and whether it is open; ensure the label is localized if
your app supports multiple languages.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
frontend/src/components/Board/PostItem.jsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/components/Board/PostItem.jsx (1)
frontend/src/pages/PostDetail.jsx (2)
navigate(17-17)post(19-19)
🔇 Additional comments (2)
frontend/src/components/Board/PostItem.jsx (2)
11-11: 구현이 올바르게 완료되었습니다 - 추가 조치 불필요검증 결과,
currentTeamprop이 Board.jsx에서 올바르게 전달되고 있으며, 라우팅도/board/:team/:postId패턴에 맞게 정상 구성되어 있습니다. PostDetail.jsx도 새로운 라우트 파라미터를 올바르게 처리하고 있습니다.
- ✅ Board.jsx에서
currentTeam={team}올바르게 전달- ✅ PostItem의 네비게이션 경로 정확함:
/board/${team}/${post.id}- ✅ App.jsx 라우트 설정 완료:
/board/:team,/board/:team/:postId- ✅ PostDetail에서 team, postId 파라미터 정확히 추출
PropTypes 추가는 선택적 개선사항으로 진행하셔도 되고 생략하셔도 무방합니다.
14-18: 라우트 설정 검증 완료 - 구현이 올바릅니다라우터 설정 검증 결과, 변경사항이 완벽하게 구현되었습니다:
- ✓ App.jsx line 31에
/board/:team/:postId라우트 정의됨- ✓ PostDetail.jsx line 16에서
useParams()로team과postId모두 추출- ✓ PostItem.jsx의 네비게이션 경로가 라우트 정의와 일치
- ✓
currentTeam || 'all'폴백 로직 적절함breaking change는 완전히 구현되었으며 추가 작업이 필요하지 않습니다.

1) 작업한 이슈번호
SISC1-210
2) 변경 요약 (What & Why)
무엇을 변경했는지:
BoardActions.module.css, Modal.jsx, Modal.module.css
왜 변경했는지(문제/목표):
3) 스크린샷/동영상 (UI 변경 시)
4) 상세 변경사항 (전부 다)
Summary by CodeRabbit
신규 기능
스타일
문구
네비게이션
✏️ Tip: You can customize this high-level summary in your review settings.