diff --git a/frontend/src/components/RunnerPost/RunnerPostFilter/RunnerPostFilter.tsx b/frontend/src/components/RunnerPost/RunnerPostFilter/RunnerPostFilter.tsx index 5d951f8bd..704df6724 100644 --- a/frontend/src/components/RunnerPost/RunnerPostFilter/RunnerPostFilter.tsx +++ b/frontend/src/components/RunnerPost/RunnerPostFilter/RunnerPostFilter.tsx @@ -1,28 +1,47 @@ import React from 'react'; import { css, keyframes, styled } from 'styled-components'; -import { REVIEW_STATUS_FILTER_TEXT, REVIEW_STATUS_LABEL_TEXT } from '@/constants'; +import { REVIEW_STATUS_FILTER_TEXT } from '@/constants'; +import Text from '@/components/common/Text/Text'; +import Flex from '@/components/common/Flex/Flex'; +import { ReviewStatusFilter } from '@/types/runnerPost'; +import useTotalCount from '@/hooks/query/useTotalCount'; interface Props { - reviewStatus: string; + reviewStatus: ReviewStatusFilter; handleClickRadioButton: (e: React.ChangeEvent) => void; } const RunnerPostFilter = ({ reviewStatus, handleClickRadioButton }: Props) => { + const { totalCounts, isAllLoaded } = useTotalCount(); + return ( - {Object.entries(REVIEW_STATUS_FILTER_TEXT).map(([value, text]) => ( - - - {text} - - ))} + {Object.entries(REVIEW_STATUS_FILTER_TEXT).map(([value, text]) => { + const isSelected = reviewStatus === value; + + return ( + + + + + + {text} + + ({isAllLoaded ? totalCounts[value as ReviewStatusFilter] : 0}) + + + + + + ); + })} ); @@ -96,7 +115,6 @@ const S = { font-size: 18px; font-weight: 700; - color: ${({ $isSelected }) => ($isSelected ? 'var(--baton-red)' : 'var(--gray-600)')}; &::after { ${({ $isSelected }) => ($isSelected ? underLine : null)} diff --git a/frontend/src/components/RunnerPost/RunnerPostItem/RunnerPostItem.tsx b/frontend/src/components/RunnerPost/RunnerPostItem/RunnerPostItem.tsx index d1a32ee43..bf0aac9a4 100644 --- a/frontend/src/components/RunnerPost/RunnerPostItem/RunnerPostItem.tsx +++ b/frontend/src/components/RunnerPost/RunnerPostItem/RunnerPostItem.tsx @@ -7,15 +7,12 @@ import Label from '@/components/common/Label/Label'; import { REVIEW_STATUS_LABEL_TEXT } from '@/constants'; import eyeIcon from '@/assets/eye-icon.svg'; import applicantIcon from '@/assets/applicant-icon.svg'; -import useViewport from '@/hooks/useViewport'; const RunnerPostItem = ({ runnerPostData: { runnerPostId, title, deadline, tags, runnerProfile, watchedCount, applicantCount, reviewStatus }, }: { runnerPostData: RunnerPost; }) => { - const { isMobile } = useViewport(); - const { goToRunnerPostPage } = usePageRouter(); const handlePostClick = () => { @@ -29,9 +26,9 @@ const RunnerPostItem = ({ {deadline.replace('T', ' ')} 까지 @@ -45,11 +42,7 @@ const RunnerPostItem = ({ {runnerProfile ? ( - + {runnerProfile.name} ) : null} @@ -76,7 +69,7 @@ const S = { min-width: 340px; width: 100%; height: max-content; - padding: 35px 40px; + padding: 25px 30px; border: 0.5px solid var(--gray-500); border-radius: 12px; @@ -89,16 +82,12 @@ const S = { transform: scale(1.015); outline: 1.5px solid var(--baton-red); } - - @media (max-width: 768px) { - padding: 25px 30px; - } `, PostTitle: styled.p` margin-bottom: 15px; - font-size: 28px; + font-size: 20px; font-weight: 700; @media (max-width: 768px) { @@ -114,6 +103,7 @@ const S = { DeadLine: styled.p` margin-bottom: 60px; + font-size: 14px; color: var(--gray-600); diff --git a/frontend/src/components/RunnerPost/RunnerPostList/RunnerPostList.tsx b/frontend/src/components/RunnerPost/RunnerPostList/RunnerPostList.tsx index 08bb38ea8..618de848d 100644 --- a/frontend/src/components/RunnerPost/RunnerPostList/RunnerPostList.tsx +++ b/frontend/src/components/RunnerPost/RunnerPostList/RunnerPostList.tsx @@ -24,12 +24,8 @@ const S = { display: flex; flex-direction: column; align-items: center; - gap: 30px; + gap: 20px; width: 100%; - - @media (max-width: 768px) { - gap: 20px; - } `, }; diff --git a/frontend/src/components/TechLabel/TechLabel.tsx b/frontend/src/components/TechLabel/TechLabel.tsx index 5e7c9e52d..41454ee42 100644 --- a/frontend/src/components/TechLabel/TechLabel.tsx +++ b/frontend/src/components/TechLabel/TechLabel.tsx @@ -41,7 +41,7 @@ const S = { padding: ${({ $hideText }) => ($hideText ? 0 : '1px 8px')}; - font-size: ${({ $hideText }) => ($hideText ? '14px' : '12px')}; + font-size: ${({ $hideText }) => ($hideText ? '16px' : '12px')}; line-height: 18px; border-radius: 2em; diff --git a/frontend/src/components/common/SideWidget/RankerItem.tsx b/frontend/src/components/common/SideWidget/RankerItem.tsx index b99b4f593..a979c9aa8 100644 --- a/frontend/src/components/common/SideWidget/RankerItem.tsx +++ b/frontend/src/components/common/SideWidget/RankerItem.tsx @@ -15,14 +15,14 @@ const RankerItem = ({ supporter, onClick }: RankerItemProps) => { return ( - {supporter.rank} + {supporter.rank} {supporter.name} - @{supporter.company} + {supporter.company ? `@${supporter.company}` : ''} @@ -56,6 +56,7 @@ const fadeIn = keyframes` const ListWrapper = styled(Flex)` padding: 10px 15px; + border-radius: 12px; & { cursor: pointer; diff --git a/frontend/src/components/common/SideWidget/SideWidget.tsx b/frontend/src/components/common/SideWidget/SideWidget.tsx index d5746b98a..91cae9d0d 100644 --- a/frontend/src/components/common/SideWidget/SideWidget.tsx +++ b/frontend/src/components/common/SideWidget/SideWidget.tsx @@ -18,7 +18,7 @@ const SideWidget = ({ children, title }: SideWidgetProps) => { return ( - + {title} {children} @@ -72,7 +72,11 @@ const SideWidgetList = ({ data }: SideWidgetListProps) => { /> ) : ( data.map((supporter) => ( - handleClickRanker(supporter.supporterId)} /> + handleClickRanker(supporter.supporterId)} + /> )) )} diff --git a/frontend/src/constants/index.ts b/frontend/src/constants/index.ts index 6c4c9cf52..311c7c75a 100644 --- a/frontend/src/constants/index.ts +++ b/frontend/src/constants/index.ts @@ -16,6 +16,11 @@ export const REVIEW_STATUS_FILTER_TEXT: Record = { }; export const REVIEW_STATUS: ReviewStatus[] = Object.keys(REVIEW_STATUS_LABEL_TEXT) as ReviewStatus[]; + +export const REVIEW_STATUS_FILTER: ReviewStatusFilter[] = Object.keys( + REVIEW_STATUS_FILTER_TEXT, +) as ReviewStatusFilter[]; + export const RUNNER_POST_OPTIONS = ['대기중인 리뷰', '진행중인 리뷰', '완료된 리뷰']; export const SUPPORTER_POST_OPTIONS = ['신청한 리뷰', '진행중인 리뷰', '완료된 리뷰']; diff --git a/frontend/src/hooks/query/useTotalCount.ts b/frontend/src/hooks/query/useTotalCount.ts new file mode 100644 index 000000000..26119ac2d --- /dev/null +++ b/frontend/src/hooks/query/useTotalCount.ts @@ -0,0 +1,34 @@ +import { useQueries } from '@tanstack/react-query'; +import { getRunnerPost } from '@/apis/apis'; +import { ReviewStatusFilter } from '@/types/runnerPost'; +import { REVIEW_STATUS_FILTER } from '@/constants'; + +const useTotalCount = () => { + const reviewStatuses = REVIEW_STATUS_FILTER; + + const queryResults = useQueries({ + queries: reviewStatuses.map((status) => ({ + queryKey: ['runnerPostTotalCount', status], + + queryFn: () => getRunnerPost({ limit: 0, reviewStatus: status }).then((res) => res.pageInfo.totalCount), + })), + }); + + const isLoaded = queryResults.map((data) => { + return data.isLoading; + }); + + const isAllLoaded = isLoaded.every((don) => don === false); + + const totalCounts = queryResults.reduce((acc, result, index) => { + if (result.isSuccess) { + acc[reviewStatuses[index]] = result.data; + } + + return acc; + }, {} as { [key in ReviewStatusFilter]?: number }); + + return { totalCounts, isAllLoaded }; +}; + +export default useTotalCount; diff --git a/frontend/src/hooks/useViewport.ts b/frontend/src/hooks/useViewport.ts index 42b9b6444..4bae023d6 100644 --- a/frontend/src/hooks/useViewport.ts +++ b/frontend/src/hooks/useViewport.ts @@ -4,15 +4,20 @@ function useViewport() { const [isMobile, setIsMobile] = useState(false); const [isLoaded, setIsLoaded] = useState(false); - const handleResize = () => { - setIsMobile(window.innerWidth <= 768 || window.outerWidth <= 768); - }; - useLayoutEffect(() => { + const mediaQuery = window.matchMedia('(max-width: 768px)'); + + const handleResize = () => { + setIsMobile(mediaQuery.matches); + }; + handleResize(); setIsLoaded(true); - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); + + mediaQuery.addEventListener('change', handleResize); + return () => { + mediaQuery.removeEventListener('change', handleResize); + }; }, []); return { diff --git a/frontend/src/mocks/data/rank.json b/frontend/src/mocks/data/rank.json index bc472c26d..1ec55fb7d 100644 --- a/frontend/src/mocks/data/rank.json +++ b/frontend/src/mocks/data/rank.json @@ -7,8 +7,8 @@ "reviewedCount": 3, "imageUrl": "profile.jpg", "githubUrl": "https://github.com/cookienc", - "technicalTags": ["java", "spring"], - "company": "우아한테크코스" + "technicalTags": ["java", "spring", "javascript", "react", "typescript"], + "company": "" }, { "rank": 2, diff --git a/frontend/src/mocks/data/runnerPostList.json b/frontend/src/mocks/data/runnerPostList.json index 405c21836..89c55292c 100644 --- a/frontend/src/mocks/data/runnerPostList.json +++ b/frontend/src/mocks/data/runnerPostList.json @@ -103,6 +103,7 @@ ], "pageInfo": { "isLast": false, - "nextCursor": 1 + "nextCursor": 1, + "totalCount": 129 } } diff --git a/frontend/src/pages/MainPage.tsx b/frontend/src/pages/MainPage.tsx index ef66834ff..f761a45ff 100644 --- a/frontend/src/pages/MainPage.tsx +++ b/frontend/src/pages/MainPage.tsx @@ -7,7 +7,7 @@ import { usePageRouter } from '@/hooks/usePageRouter'; import { useRunnerPostList } from '@/hooks/query/useRunnerPostList'; import useViewport from '@/hooks/useViewport'; import { ReviewStatus } from '@/types/runnerPost'; -import { useContext, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { styled } from 'styled-components'; import { isLogin } from '@/apis/auth'; import SideWidget from '@/components/common/SideWidget/SideWidget'; @@ -77,10 +77,10 @@ const MainPage = () => {