Conversation
Walkthrough이 변경사항은 채용 공고 즐겨찾기(북마크) 기능을 전반적으로 도입하는 것으로, API 서비스 모듈과 글로벌 컨텍스트, 여러 컴포넌트 및 스타일 파일에 걸쳐 북마크 관련 로직과 UI, 상태 관리가 추가되었습니다. 채용 리스트, 상세, 아이템 컴포넌트 모두 북마크 기능과 연동되며, 페이지 단에서는 필터링, 데이터 중복 제거, 네비게이션 개선 등이 포함되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant RecruitmentList
participant RecruitmentItem
participant BookmarkContext
participant BookmarkAPI
User->>RecruitmentList: 북마크 필터 버튼 클릭
RecruitmentList->>BookmarkContext: 북마크 상태 확인
RecruitmentList->>RecruitmentItem: isBookmarked prop 전달
User->>RecruitmentItem: 북마크 버튼 클릭
RecruitmentItem->>BookmarkContext: toggleBookmark(jobId)
BookmarkContext->>BookmarkAPI: add/removeRecruitmentBookmark(jobId)
BookmarkAPI-->>BookmarkContext: 성공/실패 응답
BookmarkContext-->>RecruitmentItem: 상태 업데이트
RecruitmentItem-->>User: 북마크 아이콘/상태 반영
User->>RecruitmentDetail: 북마크 버튼 클릭
RecruitmentDetail->>BookmarkContext: toggleBookmark(jobId)
BookmarkContext->>BookmarkAPI: add/removeRecruitmentBookmark(jobId)
BookmarkAPI-->>BookmarkContext: 성공/실패 응답
BookmarkContext-->>RecruitmentDetail: 상태 업데이트
RecruitmentDetail-->>User: 북마크 아이콘/상태 반영
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40분 Suggested reviewers
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
src/contexts/BookmarkContext.jsx (1)
8-18: 북마크 컨텍스트 구조가 잘 설계되었습니다.Set 자료구조를 사용한 효율적인 ID 관리와 적절한 상태 초기화가 구현되어 있습니다. 다만 디버그용 console.log는 제거해야 합니다.
다음 수정사항을 적용해주세요:
useEffect(() => { loadBookmarks(); - console.log("bookmarkedJobs",bookmarkedJobs); }, []);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/api/recruitmentBookmarkService.js(1 hunks)src/components/recruitment/recruitment_map/RecruitmentDetail.jsx(3 hunks)src/components/recruitment/recruitment_map/RecruitmentDetail.module.scss(1 hunks)src/components/recruitment/recruitment_map/RecruitmentItem.jsx(3 hunks)src/components/recruitment/recruitment_map/RecruitmentItem.module.scss(2 hunks)src/components/recruitment/recruitment_map/RecruitmentList.jsx(2 hunks)src/components/recruitment/recruitment_map/RecruitmentList.module.scss(1 hunks)src/contexts/BookmarkContext.jsx(1 hunks)src/pages/recruitment/RecruitmentMap.jsx(11 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/contexts/BookmarkContext.jsx (2)
src/api/recruitmentBookmarkService.js (3)
getUserRecruitmentBookmarks(35-46)addRecruitmentBookmark(8-15)removeRecruitmentBookmark(21-28)src/components/recruitment/recruitment_map/RecruitmentList.jsx (1)
useBookmark(10-10)
src/components/recruitment/recruitment_map/RecruitmentList.jsx (3)
src/contexts/BookmarkContext.jsx (3)
useBookmark(100-106)checkIsBookmarked(36-39)isLoading(12-12)src/components/recruitment/recruitment_map/RecruitmentItem.jsx (1)
useBookmark(10-10)src/pages/recruitment/RecruitmentMap.jsx (3)
showOnlyBookmarked(46-46)selectedJob(31-31)isLastPage(50-50)
src/components/recruitment/recruitment_map/RecruitmentDetail.jsx (3)
src/contexts/BookmarkContext.jsx (3)
useBookmark(100-106)toggleBookmark(74-80)checkIsBookmarked(36-39)src/components/recruitment/recruitment_map/RecruitmentList.jsx (1)
useBookmark(10-10)src/components/recruitment/recruitment_map/RecruitmentItem.jsx (2)
useBookmark(10-10)handleToggleBookmark(13-19)
src/api/recruitmentBookmarkService.js (1)
src/api/axiosInstance.js (1)
api(4-7)
src/components/recruitment/recruitment_map/RecruitmentItem.jsx (3)
src/components/recruitment/recruitment_map/RecruitmentList.jsx (1)
useBookmark(10-10)src/components/recruitment/recruitment_map/RecruitmentDetail.jsx (1)
handleToggleBookmark(54-60)src/contexts/BookmarkContext.jsx (1)
toggleBookmark(74-80)
src/pages/recruitment/RecruitmentMap.jsx (5)
src/components/recruitment/recruitment_map/RecruitmentList.jsx (1)
showOnlyBookmarked(11-11)src/components/recruitment/recruitment_map/RecruitmentDetail.jsx (3)
loading(13-13)error(14-14)filterDataLoading(16-16)src/components/recruitment/recruitment_map/MapContainer.jsx (1)
loading(23-26)src/api/recruitmentService.js (6)
isLastPage(116-116)params(22-22)validateAndBuildParams(11-100)validateAndBuildParams(11-100)getRecruitments(102-129)getRecruitments(102-129)src/contexts/BookmarkContext.jsx (1)
BookmarkProvider(8-97)
🔇 Additional comments (17)
src/api/recruitmentBookmarkService.js (2)
8-15: 깔끔한 API 함수 구현입니다.POST 요청과 에러 핸들링이 잘 구현되어 있습니다.
21-28: 삭제 함수도 적절하게 구현되었습니다.DELETE 요청과 에러 핸들링 패턴이 일관적입니다.
src/components/recruitment/recruitment_map/RecruitmentList.module.scss (1)
8-41: 필터 버튼 스타일이 잘 구현되었습니다.flexbox 레이아웃, hover 효과, active 상태 스타일링이 적절하게 구현되어 있고, 기존 스타일과 일관된 색상 테마를 사용하고 있습니다.
src/components/recruitment/recruitment_map/RecruitmentDetail.module.scss (1)
14-53: 헤더 버튼 레이아웃과 북마크 버튼 스타일이 잘 구현되었습니다.flexbox를 이용한 버튼 배치, hover 효과, disabled 상태 처리가 적절하게 구현되어 있습니다. 북마크 아이콘의 노란색 색상 선택도 시각적으로 구분하기 좋습니다.
src/components/recruitment/recruitment_map/RecruitmentItem.module.scss (2)
21-58: 아이템 레이아웃 업데이트가 잘 구현되었습니다.제목과 북마크 버튼을 위한 새로운 레이아웃 구조가 적절하게 구현되었고, 북마크 버튼 스타일이 다른 컴포넌트와 일관성을 유지하고 있습니다.
67-78: 소스 로고 레이아웃 조정이 적절합니다.전체 너비 사용과 오른쪽 정렬, 로고 크기 축소가 새로운 레이아웃 구조에 잘 맞습니다.
src/contexts/BookmarkContext.jsx (5)
21-33: 북마크 로드 함수가 잘 구현되었습니다.API 호출과 에러 핸들링, Set 자료구조로의 변환이 적절하게 구현되어 있습니다.
35-39: 북마크 확인 함수가 효율적으로 구현되었습니다.null/undefined 체크와 Set의 O(1) 조회를 활용한 효율적인 구현입니다.
42-71: 북마크 추가/삭제 함수들이 잘 구현되었습니다.API 호출, 상태 업데이트, 에러 핸들링이 일관되게 구현되어 있고, 불변성을 지키며 새로운 Set을 생성하는 방식이 적절합니다.
74-80: 토글 함수가 간결하게 구현되었습니다.기존 상태를 확인하여 적절한 함수를 호출하는 로직이 명확합니다.
100-106: 커스텀 훅이 적절하게 구현되었습니다.컨텍스트 사용 검증과 에러 처리가 잘 구현되어 있습니다.
src/pages/recruitment/RecruitmentMap.jsx (2)
174-186: 효과적인 중복 제거 구현Map을 사용한 ID 기반 중복 제거 로직이 잘 구현되었습니다.
369-384: 좌표 검증 로직 우수한국 좌표 범위 검증 및 좌표 스왑 감지 로직이 잘 구현되었습니다. 잘못된 좌표 데이터를 효과적으로 처리합니다.
src/components/recruitment/recruitment_map/RecruitmentDetail.jsx (4)
5-7: 북마크 기능을 위한 import가 적절히 추가되었습니다.useBookmark 훅과 북마크 아이콘들이 올바르게 import되어 북마크 기능 구현에 필요한 의존성이 잘 설정되었습니다.
15-15: 북마크 컨텍스트 연동이 올바르게 구현되었습니다.useBookmark 훅에서 필요한 함수들과 상태를 적절히 destructuring하고 있으며, isLoading을 isBookmarkLoading으로 rename하여 기존 loading 상태와의 충돌을 방지한 것이 좋습니다.
53-60: 북마크 토글 핸들러가 잘 구현되었습니다.이벤트 버블링 방지(
e.stopPropagation())와 job ID 유효성 검사를 통해 안전한 북마크 토글 기능을 제공하고 있습니다. 다른 컴포넌트들과 일관성 있는 구현입니다.
190-202: 북마크 버튼 UI가 사용성과 접근성을 고려하여 잘 구현되었습니다.다음과 같은 좋은 구현 요소들이 포함되어 있습니다:
- 북마크 상태에 따른 아이콘 조건부 렌더링
- 로딩 중 버튼 비활성화 처리
- 접근성을 위한 aria-label 동적 설정
- 헤더 버튼들의 일관성 있는 그룹화
| export async function getUserRecruitmentBookmarks() { | ||
|
|
||
|
|
||
| try { | ||
| const response = await api.get("/api/v1/recruitments/bookmarks"); | ||
| console.log("response",response); | ||
| return response.data.data; | ||
| } catch (error) { | ||
| console.error("Error fetching recruitment filters:", error); | ||
| throw error; | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
코드 정리 및 일관성 개선이 필요합니다.
다음 사항들을 수정해주세요:
- 불필요한 빈 줄들 (36-37행)
- 디버그용 console.log 제거
- 에러 메시지가 다른 함수들과 일치하지 않음
다음과 같이 수정하는 것을 권장합니다:
export async function getUserRecruitmentBookmarks() {
-
-
try {
const response = await api.get("/api/v1/recruitments/bookmarks");
- console.log("response",response);
return response.data.data;
} catch (error) {
- console.error("Error fetching recruitment filters:", error);
+ console.error('사용자 채용공고 즐겨찾기 조회 실패:', error);
throw error;
}
}📝 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.
| export async function getUserRecruitmentBookmarks() { | |
| try { | |
| const response = await api.get("/api/v1/recruitments/bookmarks"); | |
| console.log("response",response); | |
| return response.data.data; | |
| } catch (error) { | |
| console.error("Error fetching recruitment filters:", error); | |
| throw error; | |
| } | |
| } | |
| export async function getUserRecruitmentBookmarks() { | |
| try { | |
| const response = await api.get("/api/v1/recruitments/bookmarks"); | |
| return response.data.data; | |
| } catch (error) { | |
| console.error('사용자 채용공고 즐겨찾기 조회 실패:', error); | |
| throw error; | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/api/recruitmentBookmarkService.js between lines 35 and 46, remove the
unnecessary blank lines at 36 and 37, delete the debug console.log statement
logging the response, and update the error message in the catch block to match
the format used in other functions for consistency.
| {job.recruitmentSourceId && ( | ||
| <div className={styles.sourceLogoContainer}> | ||
| {job.recruitmentSourceId.includes('wanted') && ( | ||
| <img src={wantedLogo} alt="Wanted" className={styles.sourceLogo} /> | ||
| )} | ||
| {job.recruitmentSourceId.includes('jumpit') && ( | ||
| <img src={jumpitLogo} alt="Jumpit" className={styles.sourceLogo} /> | ||
| )} | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
잠재적인 null 참조 오류 방지 필요
recruitmentSourceId가 문자열이 아닐 경우 includes 메서드 호출 시 오류가 발생할 수 있습니다.
타입 체크를 추가하여 안전하게 처리하세요:
- {job.recruitmentSourceId && (
+ {job.recruitmentSourceId && typeof job.recruitmentSourceId === 'string' && (
<div className={styles.sourceLogoContainer}>
{job.recruitmentSourceId.includes('wanted') && (
<img src={wantedLogo} alt="Wanted" className={styles.sourceLogo} />
)}
{job.recruitmentSourceId.includes('jumpit') && (
<img src={jumpitLogo} alt="Jumpit" className={styles.sourceLogo} />
)}
</div>
)}🤖 Prompt for AI Agents
In src/components/recruitment/recruitment_map/RecruitmentItem.jsx around lines
63 to 72, the code calls includes on job.recruitmentSourceId without confirming
it is a string, which may cause runtime errors if it is null or not a string.
Add a type check to ensure job.recruitmentSourceId is a string before calling
includes, for example by using typeof or a utility function, to safely handle
cases where recruitmentSourceId might be null or another type.
| const filteredJobs = showOnlyBookmarked | ||
| ? jobs.filter(job => checkIsBookmarked(job.id)) | ||
| : jobs; | ||
|
|
||
| // 북마크 필터 상태가 변경될 때만 상위 컴포넌트에 알림 | ||
| useEffect(() => { | ||
| if (onFilteredJobsChange) { | ||
| const filtered = showOnlyBookmarked | ||
| ? jobs.filter(job => checkIsBookmarked(job.id)) | ||
| : jobs; | ||
| onFilteredJobsChange(filtered, showOnlyBookmarked); | ||
| } | ||
| }, [showOnlyBookmarked, jobs, checkIsBookmarked, onFilteredJobsChange]); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
중복된 필터링 로직 제거 및 성능 최적화 필요
필터링 로직이 중복되어 있고, 매 렌더링마다 불필요한 재계산이 발생할 수 있습니다.
useMemo를 사용하여 필터링 로직을 최적화하고 중복을 제거하세요:
+import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
- // 필터링된 채용 공고 목록
- const filteredJobs = showOnlyBookmarked
- ? jobs.filter(job => checkIsBookmarked(job.id))
- : jobs;
+ // 필터링된 채용 공고 목록 (메모이제이션)
+ const filteredJobs = useMemo(() => {
+ return showOnlyBookmarked
+ ? jobs.filter(job => checkIsBookmarked(job.id))
+ : jobs;
+ }, [showOnlyBookmarked, jobs, checkIsBookmarked]);
// 북마크 필터 상태가 변경될 때만 상위 컴포넌트에 알림
useEffect(() => {
if (onFilteredJobsChange) {
- const filtered = showOnlyBookmarked
- ? jobs.filter(job => checkIsBookmarked(job.id))
- : jobs;
- onFilteredJobsChange(filtered, showOnlyBookmarked);
+ onFilteredJobsChange(filteredJobs, showOnlyBookmarked);
}
- }, [showOnlyBookmarked, jobs, checkIsBookmarked, onFilteredJobsChange]);
+ }, [filteredJobs, showOnlyBookmarked, onFilteredJobsChange]);🤖 Prompt for AI Agents
In src/components/recruitment/recruitment_map/RecruitmentList.jsx around lines
56 to 68, the job filtering logic is duplicated and recalculated on every
render, causing unnecessary performance overhead. Refactor by using useMemo to
memoize the filteredJobs array based on showOnlyBookmarked, jobs, and
checkIsBookmarked dependencies. Then, use this memoized filteredJobs both for
rendering and inside the useEffect callback to avoid redundant filtering and
improve performance.
| const handleSelectJob = useCallback((job) => { | ||
| setSelectedJob(job); | ||
| }; | ||
| setMapCenter({ lat: job.latitude, lng: job.longitude }); | ||
| navigate(`/recruitment/map/${job.id}`); | ||
| }, []); |
There was a problem hiding this comment.
useCallback 의존성 배열에 navigate 추가 필요
navigate 함수가 의존성 배열에 누락되어 있습니다.
const handleSelectJob = useCallback((job) => {
setSelectedJob(job);
setMapCenter({ lat: job.latitude, lng: job.longitude });
navigate(`/recruitment/map/${job.id}`);
- }, []);
+ }, [navigate]);📝 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.
| const handleSelectJob = useCallback((job) => { | |
| setSelectedJob(job); | |
| }; | |
| setMapCenter({ lat: job.latitude, lng: job.longitude }); | |
| navigate(`/recruitment/map/${job.id}`); | |
| }, []); | |
| const handleSelectJob = useCallback((job) => { | |
| setSelectedJob(job); | |
| setMapCenter({ lat: job.latitude, lng: job.longitude }); | |
| navigate(`/recruitment/map/${job.id}`); | |
| }, [navigate]); |
🤖 Prompt for AI Agents
In src/pages/recruitment/RecruitmentMap.jsx around lines 71 to 75, the
useCallback hook for handleSelectJob is missing the navigate function in its
dependency array. Add navigate to the dependency array to ensure the callback
updates correctly when navigate changes.
| if (content.length > 0) { | ||
| setRecruitments(prev => [...prev, ...content]); | ||
| setPage(nextPage); |
There was a problem hiding this comment.
추가 데이터 로드 시 중복 제거 로직 누락
fetchRecruitments와 달리 loadMoreRecruitments에서는 중복 제거 로직이 적용되지 않아 중복 데이터가 발생할 수 있습니다.
중복 제거 로직을 일관되게 적용하세요:
if (content.length > 0) {
- setRecruitments(prev => [...prev, ...content]);
+ setRecruitments(prev => {
+ const allItems = [...prev, ...content];
+ return Array.from(new Map(allItems.map(item => [item.id, item])).values());
+ });
setPage(nextPage);
}📝 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.
| if (content.length > 0) { | |
| setRecruitments(prev => [...prev, ...content]); | |
| setPage(nextPage); | |
| if (content.length > 0) { | |
| setRecruitments(prev => { | |
| const allItems = [...prev, ...content]; | |
| return Array.from(new Map(allItems.map(item => [item.id, item])).values()); | |
| }); | |
| setPage(nextPage); | |
| } |
🤖 Prompt for AI Agents
In src/pages/recruitment/RecruitmentMap.jsx around lines 234 to 236, the
loadMoreRecruitments function appends new recruitment data without removing
duplicates, unlike fetchRecruitments. To fix this, modify the setRecruitments
call to merge the existing and new content arrays while filtering out duplicate
entries based on a unique identifier, ensuring consistent deduplication when
loading more data.
🚀 주요 변경 사항
공고 북마크 추가
🚨 리뷰어에게
📸 스크린샷 (선택 사항)
Summary by CodeRabbit
신규 기능
스타일
버그 수정