diff --git a/src/components/index.ts b/src/components/index.ts index c50fa8d0..8ff530fd 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -143,6 +143,7 @@ import ApplyRole from './recruit/role/ApplyRole'; import OpenChatModal from './recruit/applicants/modal/OpenChatModal'; import Toast from './recruit/applicants/toast/Toast'; import NeedLogin from './recruit/recruitDetail/modal/needLogin/NeedLogin'; +import TabMenu from './tabMenu/TabMenu'; export { Header, @@ -281,4 +282,5 @@ export { OpenChatModal, Toast, NeedLogin, + TabMenu, }; diff --git a/src/components/pagination/Pagination.styled.ts b/src/components/pagination/Pagination.styled.ts index 28784c08..60a9a17a 100644 --- a/src/components/pagination/Pagination.styled.ts +++ b/src/components/pagination/Pagination.styled.ts @@ -1,7 +1,7 @@ import styled from 'styled-components'; const Pagination = styled.article` - margin-top: 12rem; + margin: 12rem auto; margin-bottom: 22rem; button { background-color: transparent; diff --git a/src/components/tabMenu/TabMenu.styled.ts b/src/components/tabMenu/TabMenu.styled.ts new file mode 100644 index 00000000..d8918ac6 --- /dev/null +++ b/src/components/tabMenu/TabMenu.styled.ts @@ -0,0 +1,46 @@ +import styled from 'styled-components'; + +const TabMenuLayout = styled.div` + display: flex; + flex-direction: row; + align-items: flex-end; +`; + +const TabMenuList = styled.ul` + display: flex; + flex-direction: row; + color: var(--State-unactive, var(--, #8e8e8e)); + + /* Body/body1/medium */ + font-size: 1.6rem; + font-weight: 500; + line-height: 1.9rem; + letter-spacing: 0.0032rem; +`; + +const TabMenuItem = styled.li<{ $clicked?: boolean }>` + display: flex; + flex: 1; + justify-content: center; + align-items: center; + width: 11.6rem; // width: clamp(45%, 11.6rem, 75%); + height: 3.6rem; + border-radius: 0.4rem 0.4rem 0rem 0rem; + border: ${props => (props.$clicked ? '0.1rem solid #373f41' : '0.1rem solid #8E8E8E')}; + border-bottom: ${props => (props.$clicked ? '0' : '0.1rem solid #373f41')}; + background: ${props => !props.$clicked && '#F6F6F6'}; + color: ${props => !props.$clicked && '#8E8E8E'}; + cursor: pointer; +`; + +const TabMenuLine = styled.hr` + all: unset; + display: flex; + height: 0.1rem; + background: #373f41; + width: 100%; +`; + +const S = { TabMenuLayout, TabMenuList, TabMenuItem, TabMenuLine }; + +export default S; diff --git a/src/components/tabMenu/TabMenu.tsx b/src/components/tabMenu/TabMenu.tsx new file mode 100644 index 00000000..b52c87ba --- /dev/null +++ b/src/components/tabMenu/TabMenu.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; +import S from './TabMenu.styled'; + +interface TabMenu { + readonly tabList: string[]; +} + +const TabMenu = ({ tabList }: TabMenu) => { + const [tab, setTab] = useState(tabList[0]); + + const clickedHandler: React.MouseEventHandler = e => { + const tab = e.target as HTMLButtonElement; + if (!tab.textContent) { + throw new Error('No Content'); + } + setTab(tab.textContent); + }; + + return ( + + + {tabList.map((currentTab, index) => ( + + {currentTab} + + ))} + + + + ); +}; + +export default TabMenu; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 573f02e4..d2180e40 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -12,10 +12,11 @@ import useDebounce from './useDebounce'; import useValid from './useValid'; import { useReadSkillList, useReadRoleList } from './useSearch'; import { - useReadPortfolioList, useReadPortfolio, - useCreatePortfolio, + useCreatePortfolio, useUpdatePortfolio, + useReadInfinitePortfolioList, + usePaginationPortfolioList, } from './usePortfolio'; import { useReadImagePresignedUrl, @@ -42,12 +43,13 @@ export { useReadSkillList, useReadRoleList, useReadPortfolio, + useReadInfinitePortfolioList, + usePaginationPortfolioList, useCreatePortfolio, useUpdatePortfolio, useReadImagePresignedUrl, useReadImageListPresignedUrl, useUploadImageFile, - useReadPortfolioList, useIntersection, useBookmark, useLogin, diff --git a/src/hooks/usePortfolio.ts b/src/hooks/usePortfolio.ts index 82b375bb..9b392edd 100644 --- a/src/hooks/usePortfolio.ts +++ b/src/hooks/usePortfolio.ts @@ -1,9 +1,10 @@ -import { useQuery, useMutation, useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; -import { readPortfolioList, createPortfolio, readPortfolio, updatePortfolio } from '../service'; +import { useQuery, useMutation, useInfiniteQuery, useQueryClient, keepPreviousData } from '@tanstack/react-query'; +import { createPortfolio, readPortfolio, updatePortfolio, readInfinitePortfolioList, readPaginationPortfolioList } from '../service'; const portfolioKeys = { readPortfolio: (portfolioId: string) => ['readPortfolio', portfolioId], - readPortfolioList: (size: number) => ['readProfile', size], + readInfinitePortfolioList: (size: number) => ['readInfinitePortfolioList', size], + readPaginationPortfolioList: (size: number) => ['readPaginationPortfolioList', size], }; /** @@ -55,11 +56,10 @@ export const useUpdatePortfolio = ({ /** * @description 포트폴리오 목록 무한스크롤 조회 API를 호출하는 hook입니다. */ - -export const useReadPortfolioList = (size: number) => { +export const useReadInfinitePortfolioList = (size: number) => { return useInfiniteQuery({ - queryKey: portfolioKeys.readPortfolioList(size), - queryFn: ({ pageParam }) => readPortfolioList({ size, pageParam }), + queryKey: portfolioKeys.readInfinitePortfolioList(size), + queryFn: ({ pageParam }) => readInfinitePortfolioList({ size, pageParam }), initialPageParam: 1, getNextPageParam: lastPage => { if (lastPage?.pageInfo.hasNextPage) { @@ -68,3 +68,14 @@ export const useReadPortfolioList = (size: number) => { }, }); }; + +/** + * @description 포트폴리오 목록 페이지네이션 조회 API를 호출하는 hook입니다. + */ +export const usePaginationPortfolioList = (size: number, pageParam: number) => { + return useQuery({ + queryKey: portfolioKeys.readPaginationPortfolioList(size), + queryFn: () => readPaginationPortfolioList({ size, pageParam }), + placeholderData: keepPreviousData, + }); +}; diff --git a/src/main.tsx b/src/main.tsx index f5e3183d..ba85a043 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -21,6 +21,7 @@ import { RecruitPostingApply, RecruitMyPostings, CompleteSignUpPage, + PortfolioManagementPage, } from './pages/index.ts'; import './globalStyle.css'; @@ -103,6 +104,10 @@ const router = createBrowserRouter([ path: 'portfolio/edit/:portfolioId?', element: , // 생성 및 편집 }, + { + path: 'portfolio/management', + element: , + } ], }, ]); diff --git a/src/pages/index.ts b/src/pages/index.ts index a534b4a1..17483260 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -26,6 +26,7 @@ import RecruitPostingBookmark from './recruit/recruitManagePage/RecruitPostingBo import RecruitPostingApply from './recruit/recruitManagePage/RecruitPostingApply'; import RecruitMyPostings from './recruit/recruitManagePage/RecruitMyPostings'; import CompleteSignUpPage from './account/complete/CompleteSignUpPage'; +import PortfolioManagementPage from './portfolio/management/PortfolioManagmentPage'; export { MainPage, @@ -57,4 +58,5 @@ export { RecruitPostingApply, RecruitMyPostings, CompleteSignUpPage, + PortfolioManagementPage, }; diff --git a/src/pages/portfolio/management/PortfolioManagementPage.styled.ts b/src/pages/portfolio/management/PortfolioManagementPage.styled.ts new file mode 100644 index 00000000..068a4029 --- /dev/null +++ b/src/pages/portfolio/management/PortfolioManagementPage.styled.ts @@ -0,0 +1,72 @@ +import styled from 'styled-components'; + +const PortfolioManagementLayout = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + margin: 0 auto; + // margin-bottom: 15rem; + width: clamp(45%, 96rem, 75%); // width: 96rem; + color: var(--Light-Black, #373f41); + + h2 { + color: var(--Text-textColor1, #151515); + /* Headline/h2 */ + font-size: 2.4rem; + font-weight: 700; + line-height: 2.9rem; /* 120.833% */ + letter-spacing: 0.0048rem; + } +`; + +const PortfolioManagementHeader = styled.header` + display: flex; + flex-direction: row; + margin-top: 8rem; + margin-bottom: 2rem; +`; + +const PortfolioManagementGrid = styled.div` + display: grid; + // margin: 8rem 0; + margin-top: 8rem; + grid-template-columns: repeat(auto-fill, minmax(22.5rem, 1fr)); + grid-auto-rows: minmax(12.7rem, auto); + row-gap: 4rem; + column-gap: 2rem; + + /* 스크롤바 숨기기 */ + overflow-y: auto; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + &::-webkit-scrollbar { + display: none; /* Chrome, Safari, Opera*/ +`; + +const PortfolioManagementColumn = styled.div` + position: fixed; + display: flex; + align-items: center; + justify-content: center; + left: 0; + top: 0; + width: 100%; + height: 100%; + + color: var(--State-unactive, #8e8e8e); + + /* Headline/h3 */ + font-size: 2rem; + font-weight: 600; + line-height: 2.4rem; /* 120% */ + letter-spacing: 0.004rem; +`; + +const S = { + PortfolioManagementLayout, + PortfolioManagementHeader, + PortfolioManagementGrid, + PortfolioManagementColumn, +}; + +export default S; diff --git a/src/pages/portfolio/management/PortfolioManagmentPage.tsx b/src/pages/portfolio/management/PortfolioManagmentPage.tsx new file mode 100644 index 00000000..27d2fa32 --- /dev/null +++ b/src/pages/portfolio/management/PortfolioManagmentPage.tsx @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; +import S from './PortfolioManagementPage.styled'; +import { Pagination, PortfolioCard, PrimaryBtn, TabMenu } from '../../../components'; +import { useNavigate } from 'react-router-dom'; +import { usePaginationPortfolioList } from '../../../hooks'; + +const tabList = ['전체']; + +const PortfolioManagementPage = () => { + const navigate = useNavigate(); + + const [pageNumber, setPageNumber] = useState(1); + const { data: portfolioList, isSuccess } = usePaginationPortfolioList(16, pageNumber); + + return ( + isSuccess && ( + + +

포트폴리오

+ navigate('/portfolio/edit')} + /> +
+ + {!portfolioList?.portfolios.length ? ( + + {portfolioList && + portfolioList.portfolios?.map(portfolio => ( + + ))} + + ) : ( + 아직 작성한 포트폴리오가 없어요 + )} + +
+ ) + ); +}; + +export default PortfolioManagementPage; diff --git a/src/pages/profile/edit/ProfileEditPage.tsx b/src/pages/profile/edit/ProfileEditPage.tsx index df777eea..cc09e305 100644 --- a/src/pages/profile/edit/ProfileEditPage.tsx +++ b/src/pages/profile/edit/ProfileEditPage.tsx @@ -33,7 +33,7 @@ import { useReadImagePresignedUrl, } from '../../../hooks'; import { useNavigate } from 'react-router-dom'; -import { useReadPortfolioList } from '../../../hooks/usePortfolio'; +import { useReadInfinitePortfolioList } from '../../../hooks/usePortfolio'; interface FormValues { nickname?: string; @@ -260,7 +260,7 @@ const ProfileEditPage = () => { hasNextPage, isFetching, isSuccess: isPortfolioSuccess, - } = useReadPortfolioList(12); + } = useReadInfinitePortfolioList(12); const portfolioList = useMemo( () => (data ? data.pages.flatMap(response => response?.portfolios) : []), [data] diff --git a/src/service/endPoint.ts b/src/service/endPoint.ts index b5579e23..2c2028e9 100644 --- a/src/service/endPoint.ts +++ b/src/service/endPoint.ts @@ -29,6 +29,7 @@ export const EndPoint = { /* portfolio */ PORTFOLIO: { read: (portfolioId: string) => `/portfolio/${portfolioId}`, + readPortfolioList: '/portfolio', create: '/portfolio', update: (portfolioId: string) => `/portfolio/${portfolioId}`, }, diff --git a/src/service/index.ts b/src/service/index.ts index 6a63a537..dbea2f58 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -19,10 +19,11 @@ import { import { readProfile, updateProfile } from './user/Profile'; import { readSkillList, readRoleList } from './search/search'; import { - readPortfolioList, readPortfolio, - createPortfolio, + createPortfolio, updatePortfolio, + readInfinitePortfolioList, + readPaginationPortfolioList, } from './portfolio/portfolio'; import { readImagePresignedUrl, readImageListPresignedUrl, uploadImageFile } from './image/image'; import { @@ -55,10 +56,11 @@ export { readPortfolio, createPortfolio, updatePortfolio, + readInfinitePortfolioList, + readPaginationPortfolioList, readImagePresignedUrl, readImageListPresignedUrl, uploadImageFile, - readPortfolioList, getPostingData, getApplyData, applyRole, diff --git a/src/service/portfolio/portfolio.ts b/src/service/portfolio/portfolio.ts index 1d35a96e..deed8e54 100644 --- a/src/service/portfolio/portfolio.ts +++ b/src/service/portfolio/portfolio.ts @@ -45,7 +45,7 @@ export const updatePortfolio = async ({ } }; -export const readPortfolioList = async ({ +export const readInfinitePortfolioList = async ({ size, pageParam, }: { @@ -69,3 +69,28 @@ export const readPortfolioList = async ({ return null; } }; + +export const readPaginationPortfolioList = async ({ + size, + pageParam, +}: { + size: number; + pageParam: number; +}) => { + try { + const response = await axiosAuthInstance.get( + EndPoint.PORTFOLIO.readPortfolioList, + { + params: { + size: size, + page: pageParam, + }, + } + ); + + return response; + } catch (error) { + console.error(error); + return null; + } +}; diff --git a/src/types/response.ts b/src/types/response.ts index 12dda23b..b35b286d 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -36,4 +36,6 @@ export interface PageInfo { page: number; size: number; hasNextPage?: boolean; + totalContents?: number; + totalPages?: number; }