From 289dc32a8ba0a704588e254d3ee64db4a41b8250 Mon Sep 17 00:00:00 2001 From: ohoCoding Date: Tue, 24 Jun 2025 20:49:57 +0900 Subject: [PATCH 01/17] =?UTF-8?q?refactor:=20:recycle:=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80/=EA=B8=B0=EC=97=85=20=ED=99=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EmployerEmployeeCardList.tsx | 132 +++++++----- src/components/Home/HomeCareerPostCard.tsx | 64 ++++++ src/components/Home/HomeCareerPostingList.tsx | 58 ++++++ src/components/Home/HomeJobPostingSection.tsx | 182 ++++++++++++---- src/components/PostSearch/CareerCardList.tsx | 21 +- .../PostSearch/CareerSearchSection.tsx | 57 +++-- .../PostSearch/PostSearchFilterList.tsx | 15 +- src/hooks/api/useCareer.ts | 26 +++ src/pages/PostSearch/PostSearchPage.tsx | 41 +++- src/pages/Resume/ScrappedJobPostsPage.tsx | 10 +- src/types/api/career.ts | 1 + src/utils/formatSearchFilter.ts | 196 +++++++++++++----- 12 files changed, 614 insertions(+), 189 deletions(-) create mode 100644 src/components/Home/HomeCareerPostCard.tsx create mode 100644 src/components/Home/HomeCareerPostingList.tsx diff --git a/src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx b/src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx index 15e39b05..99d1f338 100644 --- a/src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx +++ b/src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx @@ -16,7 +16,6 @@ const EmployerEmployeeCard = ({ cardData: EmployeeResumeListItemType; }) => { const navigate = useNavigate(); - const { mutate } = usePutScrapResume(); const handleClickBookmark = (e: MouseEvent) => { @@ -24,79 +23,95 @@ const EmployerEmployeeCard = ({ mutate(cardData.id); }; - const goToResumeDetailPage = (id: string) => { - navigate(`/employer/search/${id}`); + const goToResumeDetailPage = () => { + navigate(`/employer/search/${cardData.id}`); }; return (
goToResumeDetailPage(cardData.id)} + className="w-[9.063rem] m-1 flex flex-col gap-2 rounded-lg" + onClick={goToResumeDetailPage} > -
-
+ {/* 이미지에만 border 적용 */} +
+ {cardData?.profile_img_url ? ( profile image -
-
-
-

- {cardData?.name}{' '} - - {cardData?.nationality} - -

-
+ +
+

+ {cardData?.name} +

+ +

+ {cardData?.title || '친절한 서비스를 고객을 맞게 만들어보아요!'} +

+ +
+
+ {cardData?.industry && ( + + )} + {cardData?.visa && ( + + )} +
+ +
+ + {cardData?.bookmark_count ?? 0}
-

- {cardData?.address} -

-

{cardData?.title}

-
- - -
); }; type EmployerEmployeeCardListProps = { + title?: string; resumeData: EmployeeResumeListItemType[]; isLoading: boolean; isInitialLoading: boolean; + onSeeMoreClick?: () => void; }; const EmployerEmployeeCardList = ({ + title, resumeData, isLoading, isInitialLoading, + onSeeMoreClick, }: EmployerEmployeeCardListProps) => { if (isInitialLoading) { return ( @@ -106,14 +121,14 @@ const EmployerEmployeeCardList = ({ ); } - if (resumeData?.length === 0) { + if (!resumeData || resumeData.length === 0) { return (

찾고 계신 인재가 없어요.

-

+

{postTranslation.emptySearchResultContent.ko}

@@ -121,14 +136,29 @@ const EmployerEmployeeCardList = ({ } return ( - <> -
- {resumeData.map((value: EmployeeResumeListItemType) => ( - +
+ {title && ( +
+

{title}

+ {onSeeMoreClick && ( + + )} +
+ )} + +
+ {resumeData.map((value) => ( + ))} -
+ + {isLoading && } - + ); }; diff --git a/src/components/Home/HomeCareerPostCard.tsx b/src/components/Home/HomeCareerPostCard.tsx new file mode 100644 index 00000000..8e7bdf58 --- /dev/null +++ b/src/components/Home/HomeCareerPostCard.tsx @@ -0,0 +1,64 @@ +import { useNavigate } from 'react-router-dom'; +import { CareerListItemType } from '@/types/api/career'; +import { CAREER_CATEGORY } from '@/constants/postSearch'; +import { useCurrentPostIdStore } from '@/store/url'; + +type HomeCareerPostCardProps = { + careerData: CareerListItemType; +}; + +const HomeCareerPostCard = ({ careerData }: HomeCareerPostCardProps) => { + const { updateCurrentPostId } = useCurrentPostIdStore(); + const navigate = useNavigate(); + + const goToCareerDetailPage = () => { + updateCurrentPostId(careerData.id); + navigate(`/career/${careerData.id}`); + }; + + return ( +
+ {/* 썸네일 영역 (CareerListItemType에는 img가 없는 상태라 빈 박스로 처리) */} +
+ +
+ {/* 제목 */} +

+ {careerData.title} +

+ + {/* 주관 - 주최 */} +
+

+ {careerData.organizer_name ?? '-'} +

+

+ {careerData.host_name ?? '-'} +

+
+ + {/* 하단 태그 */} +
+ {/* 커리어 카테고리 */} + {careerData.career_category && ( + + {CAREER_CATEGORY[careerData.career_category]} + + )} + + {/* 비자 유형 */} + {careerData.visa && careerData.visa.length > 0 && ( + + {careerData.visa.join(', ').replace(/_/g, '-')} + + )} +
+
+
+ ); +}; + +export default HomeCareerPostCard; diff --git a/src/components/Home/HomeCareerPostingList.tsx b/src/components/Home/HomeCareerPostingList.tsx new file mode 100644 index 00000000..7374f14f --- /dev/null +++ b/src/components/Home/HomeCareerPostingList.tsx @@ -0,0 +1,58 @@ +import { CareerListItemType } from '@/types/api/career'; +import HomeCareerPostCard from '@/components/Home/HomeCareerPostCard'; +import HomeEmptyJobList from '@/components/Home/HomeEmptyJobList'; +import LoadingPostItem from '@/components/Common/LoadingPostItem'; +import { postTranslation } from '@/constants/translation'; +import { isEmployerByAccountType } from '@/utils/signup'; +import { useUserStore } from '@/store/user'; + +const RenderCareerList = ({ + data, + isLoading, +}: { + data: CareerListItemType[]; + isLoading: boolean; +}) => { + if (isLoading) return ; + if (!data?.length) return ; + return ( + <> + {data.map((career) => ( + + ))} + + ); +}; + +const HomeCareerPostingList = ({ + title, + isLoading, + data, + onSeeMoreClick, +}: { + title: string; + isLoading: boolean; + data: CareerListItemType[]; + onSeeMoreClick: () => void; +}) => { + const { account_type } = useUserStore(); + + return ( +
+
+

{title}

+ +
+
+ +
+
+ ); +}; + +export default HomeCareerPostingList; diff --git a/src/components/Home/HomeJobPostingSection.tsx b/src/components/Home/HomeJobPostingSection.tsx index 40164745..1c391ec6 100644 --- a/src/components/Home/HomeJobPostingSection.tsx +++ b/src/components/Home/HomeJobPostingSection.tsx @@ -1,68 +1,158 @@ import { useNavigate } from 'react-router-dom'; -import { POST_SEARCH_MENU, POST_SORTING } from '@/constants/postSearch'; +import { + POST_SEARCH_MENU, + POST_SEARCH_PAGE_MENU, + POST_SORTING, +} from '@/constants/postSearch'; import { useUserStore } from '@/store/user'; import { UserType } from '@/constants/user'; -import { PostSortingType } from '@/types/PostSearchFilter/PostSearchFilterItem'; import { useHomeJobPosting } from '@/hooks/useHomeJobPosting'; -import HomeJobPostingList from '@/components/Home/HomeJobPostingList'; +import { useInfiniteGetEmployeeResumeList } from '@/hooks/api/useResume'; import { useGetPostList } from '@/hooks/api/usePost'; +import { useGetCareerList } from '@/hooks/api/useCareer'; +import HomeJobPostingList from '@/components/Home/HomeJobPostingList'; +import HomeCareerPostingList from '@/components/Home/HomeCareerPostingList'; +import EmployerEmployeeCardList from '../Employer/EmployeeSearch/EmployerEmployeeCardList'; -const HomeJobPostingSection = () => { +const HomePostSection = () => { const navigate = useNavigate(); const { account_type } = useUserStore(); + const isLogin = !!account_type; + const isOwner = account_type === UserType.OWNER; - const { data: trendData, isLoading: trendLoading } = useHomeJobPosting( - POST_SEARCH_MENU.TRENDING, - !!account_type, - ); - const { data: recentlyData, isLoading: recentlyLoading } = useHomeJobPosting( - POST_SEARCH_MENU.RECENTLY, - !!account_type, - ); + // 공통: 공고 트렌딩 + const { data: trendingJobData, isLoading: trendingJobLoading } = + useHomeJobPosting(POST_SEARCH_MENU.TRENDING, isLogin); + + // 공통: 공고 북마크 (유저만) + const bookmarkedJobRequest = { size: 5, type: POST_SEARCH_MENU.BOOKMARKED }; + const { data: bookmarkedJobData, isLoading: bookmarkedJobLoading } = + useGetPostList(bookmarkedJobRequest, isLogin); + + // 고용주: 인재 트렌딩 + const { data: trendingResumeData, isLoading: trendingResumeLoading } = + useInfiniteGetEmployeeResumeList( + { + size: 5, + sorting: POST_SORTING.POPULAR, + }, + isOwner, + ); + + // 유저: 커리어 트렌딩 + const trendingCareerRequest = { + size: 5, + sorting: POST_SORTING.POPULAR, + page: 1, + isBookMarked: false, + }; + const { data: trendingCareerData, isLoading: trendingCareerLoading } = + useGetCareerList(trendingCareerRequest, !isOwner && isLogin); - const bookmarkedDataRequest = { + // 유저: 커리어 북마크 + const bookmarkedCareerRequest = { size: 5, - type: POST_SEARCH_MENU.BOOKMARKED, + page: 1, + isBookMarked: true, }; - const { data: userBookmarkedData, isLoading: userBookmarkedLoading } = - useGetPostList(bookmarkedDataRequest, !!account_type); + const { data: bookmarkedCareerData, isLoading: bookmarkedCareerLoading } = + useGetCareerList(bookmarkedCareerRequest, !isOwner && isLogin); - const goToSearchPage = (type: PostSortingType) => { - navigate(`/search`, { state: { sortType: type } }); + // 공고 검색 이동 + const goToJobSearch = () => { + navigate('/search', { + state: { + initialMenu: POST_SEARCH_PAGE_MENU.POST, + postSortType: POST_SORTING.POPULAR, + }, + }); + }; + + // 커리어 검색 이동 + const goToCareerSearch = () => { + navigate('/search', { + state: { + initialMenu: POST_SEARCH_PAGE_MENU.CAREER, + careerSortType: POST_SORTING.POPULAR, + }, + }); + }; + + // 이력서 검색 이동 (고용주용) + const goToResumeSearch = () => { + navigate('/search', { + state: { + initialMenu: POST_SEARCH_PAGE_MENU.CAREER, + resumeSortType: POST_SORTING.POPULAR, + }, + }); }; return (
- goToSearchPage(POST_SORTING.POPULAR)} - /> - goToSearchPage(POST_SORTING.RECENT)} - /> - {account_type === UserType.USER && ( - navigate('/resume/scrapped')} - /> + {isOwner ? ( + <> + {/* 고용주: 인재 트렌딩 */} + page.data.resumes) ?? + [] + } + isLoading={trendingResumeLoading} + isInitialLoading={trendingResumeLoading} + onSeeMoreClick={goToResumeSearch} + /> + + {/* 고용주: 공고 트렌딩 */} + + + ) : ( + <> + {/* 일반 유저: 공고 트렌딩 */} + + + {/* 일반 유저: 커리어 트렌딩 */} + + + {/* 일반 유저: 공고 북마크 */} + + navigate('/resume/scrapped', { state: { filter: 'Job Posting' } }) + } + /> + + {/* 일반 유저: 커리어 북마크 */} + + navigate('/resume/scrapped', { state: { filter: 'Career' } }) + } + /> + )}
); }; -export default HomeJobPostingSection; +export default HomePostSection; diff --git a/src/components/PostSearch/CareerCardList.tsx b/src/components/PostSearch/CareerCardList.tsx index c482f4f1..0886a282 100644 --- a/src/components/PostSearch/CareerCardList.tsx +++ b/src/components/PostSearch/CareerCardList.tsx @@ -98,15 +98,19 @@ const CareerCard = ({ careerData }: { careerData: CareerListItemType }) => { }; type CareerCardListProps = { + title?: string; careerData: CareerListItemType[]; isLoading: boolean; isInitialLoading: boolean; + onSeeMoreClick?: () => void; }; const CareerCardList = ({ + title, careerData, isLoading, isInitialLoading, + onSeeMoreClick, }: CareerCardListProps) => { const { account_type } = useUserStore(); @@ -142,8 +146,23 @@ const CareerCardList = ({ return (
+ {/* 여기 title 영역만 추가 */} + {title && ( +
+

{title}

+ {onSeeMoreClick && ( + + )} +
+ )} +
- {careerData.map((value: CareerListItemType) => ( + {careerData?.map((value: CareerListItemType) => ( ))}
diff --git a/src/components/PostSearch/CareerSearchSection.tsx b/src/components/PostSearch/CareerSearchSection.tsx index aedbfe8f..a3eeb1ec 100644 --- a/src/components/PostSearch/CareerSearchSection.tsx +++ b/src/components/PostSearch/CareerSearchSection.tsx @@ -4,7 +4,10 @@ import { useInfiniteScroll } from '@/hooks/useInfiniteScroll'; import { PostSearchType } from '@/hooks/usePostSearch'; import { useUserStore } from '@/store/user'; import { PostSortingType } from '@/types/PostSearchFilter/PostSearchFilterItem'; -import { formatCareerSearchFilter } from '@/utils/formatSearchFilter'; +import { + formatCareerSearchFilterForUser, + formatCareerSearchFilterForGuest, +} from '@/utils/formatSearchFilter'; import { useState } from 'react'; import { postSearchTranslation } from '@/constants/translation'; import { isEmployerByAccountType } from '@/utils/signup'; @@ -34,40 +37,56 @@ const CareerSearchSection = ({ updateSearchOption, }: CareerSearchSectionProps) => { const { account_type } = useUserStore(); + const isLogin = account_type === UserType.USER; const [isLoading, setIsLoading] = useState(false); + // 게스트 요청 const { data: guestCareerData, fetchNextPage: guestFetchNextPage, - hasNextPage: guesthasNextPage, + hasNextPage: guestHasNextPage, isFetchingNextPage: guestIsFetchingNextPage, isLoading: guestIsInitialLoading, } = useInfiniteGetCareerGuestList( - formatCareerSearchFilter(searchOption), - !account_type ? true : false, + formatCareerSearchFilterForGuest({ + page: 1, + size: 5, + searchText: searchOption.searchText, + careerSortType: searchOption.careerSortType, + careerCategory: searchOption.careerCategory, + }), + !isLogin, ); + // 유저 요청 const { data: userCareerData, fetchNextPage: userFetchNextPage, - hasNextPage: userhasNextPage, + hasNextPage: userHasNextPage, isFetchingNextPage: userIsFetchingNextPage, - isLoading: careerIsInitialLoading, + isLoading: userIsInitialLoading, } = useInfiniteGetCareerList( - formatCareerSearchFilter(searchOption), - account_type === UserType.USER ? true : false, + formatCareerSearchFilterForUser({ + page: 1, + size: 5, + searchText: searchOption.searchText, + careerSortType: searchOption.careerSortType, + careerCategory: searchOption.careerCategory, + isBookMarked: false, + }), + isLogin, ); - const data = account_type ? userCareerData : guestCareerData; + const data = isLogin ? userCareerData : guestCareerData; const careerData = data?.pages?.flatMap((page) => page.data.career_list) || []; - const isInitialLoading = account_type - ? careerIsInitialLoading + const isInitialLoading = isLogin + ? userIsInitialLoading : guestIsInitialLoading; - const fetchNextPage = account_type ? userFetchNextPage : guestFetchNextPage; - const hasNextPage = account_type ? userhasNextPage : guesthasNextPage; - const isFetchingNextPage = account_type + const fetchNextPage = isLogin ? userFetchNextPage : guestFetchNextPage; + const hasNextPage = isLogin ? userHasNextPage : guestHasNextPage; + const isFetchingNextPage = isLogin ? userIsFetchingNextPage : guestIsFetchingNextPage; @@ -80,10 +99,8 @@ const CareerSearchSection = ({ const handleUpdateCareerCategory = (category: CareerCategoryKey) => { const categorySet = new Set(searchOption.careerCategory); - if (categorySet.has(category)) categorySet.delete(category); else categorySet.add(category); - updateSearchOption('careerCategory', [...categorySet]); }; @@ -93,10 +110,9 @@ const CareerSearchSection = ({ return ( <> -