diff --git a/src/App.tsx b/src/App.tsx index 49a55f70..f450a916 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,8 @@ import { GlobalStyle } from './style/global'; import { defaultTheme } from './style/theme'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { SearchFilteringProvider } from './context/SearchFilteringContext'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; + const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -21,6 +23,7 @@ function App() { + diff --git a/src/api/customerService.api.ts b/src/api/customerService.api.ts index 43136237..19483fb9 100644 --- a/src/api/customerService.api.ts +++ b/src/api/customerService.api.ts @@ -1,4 +1,10 @@ -import type { ApiFAQ, SearchKeyword } from '../models/customerService'; +import type { + ApiFAQ, + ApiNotice, + ApiNoticeDetail, + NoticeSearch, + SearchKeyword, +} from '../models/customerService'; import { httpClient } from './http.api'; export const getFAQ = async (params: SearchKeyword) => { @@ -11,3 +17,25 @@ export const getFAQ = async (params: SearchKeyword) => { throw e; } }; + +export const getNotice = async (params: NoticeSearch) => { + try { + const response = await httpClient.get(`/notice`, { params }); + + return response.data.data; + } catch (e) { + console.error(e); + throw e; + } +}; + +export const getNoticeDetail = async (id: string) => { + try { + const response = await httpClient.get(`/notice/${id}`); + + return response.data.data; + } catch (e) { + console.error(e); + throw e; + } +}; diff --git a/src/components/common/contentBorder/ContentBorder.styled.ts b/src/components/common/contentBorder/ContentBorder.styled.ts new file mode 100644 index 00000000..dba302e9 --- /dev/null +++ b/src/components/common/contentBorder/ContentBorder.styled.ts @@ -0,0 +1,7 @@ +import styled from 'styled-components'; + +export const ContentBorder = styled.div` + width: 100%; + height: 0.5px; + background: ${({ theme }) => theme.color.placeholder}; +`; diff --git a/src/components/common/contentBorder/ContentBorder.tsx b/src/components/common/contentBorder/ContentBorder.tsx new file mode 100644 index 00000000..be42e9cb --- /dev/null +++ b/src/components/common/contentBorder/ContentBorder.tsx @@ -0,0 +1,5 @@ +import * as S from './ContentBorder.styled'; + +export default function ContentBorder() { + return ; +} diff --git a/src/components/home/projectCardLists/pagination/Pagination.styled.ts b/src/components/common/pagination/Pagination.styled.ts similarity index 100% rename from src/components/home/projectCardLists/pagination/Pagination.styled.ts rename to src/components/common/pagination/Pagination.styled.ts diff --git a/src/components/home/projectCardLists/pagination/Pagination.tsx b/src/components/common/pagination/Pagination.tsx similarity index 71% rename from src/components/home/projectCardLists/pagination/Pagination.tsx rename to src/components/common/pagination/Pagination.tsx index 770494a2..65a36f6f 100644 --- a/src/components/home/projectCardLists/pagination/Pagination.tsx +++ b/src/components/common/pagination/Pagination.tsx @@ -1,6 +1,4 @@ import React, { useEffect, useState } from 'react'; -import { useProjectCardListData } from '../../../../hooks/useProjectCardListData'; -import { useSaveSearchFiltering } from '../../../../hooks/useSaveSearchFiltering'; import * as S from './Pagination.styled'; import { ChevronLeftIcon, @@ -8,11 +6,19 @@ import { EllipsisHorizontalIcon, } from '@heroicons/react/24/outline'; -export default function Pagination() { - const { projectListsData } = useProjectCardListData(); - const { searchFilters, handleUpdateFilters } = useSaveSearchFiltering(); - const lastPage = projectListsData?.lastPage; - const currentPage = searchFilters.page; +interface PaginationProps { + page: number; + getLastPage: number; + onChangePagination: (page: number) => void; +} + +export default function Pagination({ + page, + getLastPage, + onChangePagination, +}: PaginationProps) { + const lastPage = getLastPage; + const currentPage = page; const calculatePageRange = () => { if (!lastPage) return; @@ -39,22 +45,19 @@ export default function Pagination() { const target = e.target as HTMLElement; const dataId = target.dataset.id; if (!dataId) return; - handleUpdateFilters('page', Number(dataId)); + onChangePagination(Number(dataId)); }; - return ( {currentPage !== 1 && ( <> handleUpdateFilters('page', currentPage - 1)} + onClick={() => onChangePagination(currentPage - 1)} > - handleUpdateFilters('page', 1)} - > + onChangePagination(1)}> 1 @@ -74,12 +77,12 @@ export default function Pagination() { <> handleUpdateFilters('page', lastPage)} + onClick={() => onChangePagination(lastPage)} > {lastPage} handleUpdateFilters('page', currentPage + 1)} + onClick={() => onChangePagination(currentPage + 1)} > diff --git a/src/components/customerService/faq/FAQContent.styled.ts b/src/components/customerService/faq/FAQContent.styled.ts index c7c62df1..1ceb05c8 100644 --- a/src/components/customerService/faq/FAQContent.styled.ts +++ b/src/components/customerService/faq/FAQContent.styled.ts @@ -9,11 +9,11 @@ export const ListWrapper = styled.button` display: flex; justify-content: space-between; align-items: center; - padding: 1.5rem 0; + padding: 1.2rem 0; `; export const ListTitle = styled.div` - font-size: 1.3rem; + font-size: 1.2rem; padding-left: 1.5rem; font-weight: bold; `; diff --git a/src/components/customerService/inquiry/Inquiry.styled.ts b/src/components/customerService/inquiry/Inquiry.styled.ts index e8c7f06d..8c4bab09 100644 --- a/src/components/customerService/inquiry/Inquiry.styled.ts +++ b/src/components/customerService/inquiry/Inquiry.styled.ts @@ -50,8 +50,8 @@ export const CategorySelect = styled.button<{ $isCategoryOpen: boolean }>` height: 1.3rem; transition: transform 300ms ease-in-out; transform: rotate(0deg); - ${({ $isOpen }) => - $isOpen && + ${({ $isCategoryOpen }) => + $isCategoryOpen && css` transform: rotate(180deg); `} diff --git a/src/components/customerService/notice/NoticeList.styled.ts b/src/components/customerService/notice/NoticeList.styled.ts new file mode 100644 index 00000000..75e501a5 --- /dev/null +++ b/src/components/customerService/notice/NoticeList.styled.ts @@ -0,0 +1,27 @@ +import styled from 'styled-components'; + +export const Container = styled.nav` + width: 100%; +`; + +export const Wrapper = styled.button` + width: 100%; + padding: 1rem 1.5rem; + display: flex; + align-items: center; + justify-content: space-between; + + &:hover { + background: ${({ theme }) => theme.color.lightgrey}; + } +`; + +export const Title = styled.span` + font-size: 1.2rem; + font-weight: 700; +`; + +export const NoticeDate = styled.span` + font-size: 1.1rem; + color: ${({ theme }) => theme.color.placeholder}; +`; diff --git a/src/components/customerService/notice/NoticeList.tsx b/src/components/customerService/notice/NoticeList.tsx new file mode 100644 index 00000000..b61099da --- /dev/null +++ b/src/components/customerService/notice/NoticeList.tsx @@ -0,0 +1,18 @@ +import type { NoticeList as TNoticeList } from '../../../models/customerService'; +import { formatDate } from '../../../util/format'; +import * as S from './NoticeList.styled'; + +interface NoticeProps { + notice: TNoticeList; +} + +export default function NoticeList({ notice }: NoticeProps) { + return ( + + + {notice.title} + {formatDate(notice.createdAt)} + + + ); +} diff --git a/src/components/customerService/noticeDetail/NoticeDetailBundle.styled.ts b/src/components/customerService/noticeDetail/NoticeDetailBundle.styled.ts new file mode 100644 index 00000000..b8dfa27e --- /dev/null +++ b/src/components/customerService/noticeDetail/NoticeDetailBundle.styled.ts @@ -0,0 +1,11 @@ +import styled from 'styled-components'; + +export const SpinnerWrapper = styled.div` + height: 60vh; +`; + +export const Container = styled.section` + width: 75%; + margin: 0 auto; + margin-bottom: 2rem; +`; diff --git a/src/components/customerService/noticeDetail/NoticeDetailBundle.tsx b/src/components/customerService/noticeDetail/NoticeDetailBundle.tsx new file mode 100644 index 00000000..05a8f594 --- /dev/null +++ b/src/components/customerService/noticeDetail/NoticeDetailBundle.tsx @@ -0,0 +1,63 @@ +import { useLocation, useParams } from 'react-router-dom'; +import { useGetNoticeDetail } from '../../../hooks/useGetNoticeDetail'; +import * as S from './NoticeDetailBundle.styled'; +import NoticeDetailBottom from './bottom/NoticeDetailBottom'; +import NoticeDetailContent from './content/NoticeDetailContent'; +import NoticeDetailHeader from './header/NoticeDetailHeader'; +import Spinner from '../../mypage/Spinner'; +import ListButton from './bottom/button/ListButton'; + +export default function NoticeDetailBundle() { + const location = useLocation(); + const { noticeId } = useParams(); + const id = noticeId || String(location.state.id); + + const { noticeDetail: noticeDetailData, isLoading } = useGetNoticeDetail(id); + + if (isLoading) { + return ( + + + + ); + } + + if (!noticeDetailData) { + return ( + + + + + ); + } + + const { + id: detailId, + title, + content, + createdAt, + viewCount, + prev, + next, + } = noticeDetailData; + + return ( + + + + + + ); +} diff --git a/src/components/customerService/noticeDetail/bottom/NoticeDetailBottom.styled.ts b/src/components/customerService/noticeDetail/bottom/NoticeDetailBottom.styled.ts new file mode 100644 index 00000000..1db5b26c --- /dev/null +++ b/src/components/customerService/noticeDetail/bottom/NoticeDetailBottom.styled.ts @@ -0,0 +1,8 @@ +import styled from 'styled-components'; + +export const Container = styled.nav``; + +export const NotOtherNotice = styled.div` + padding: 0.5rem 1rem; + font-size: 0.9rem; +`; diff --git a/src/components/customerService/noticeDetail/bottom/NoticeDetailBottom.tsx b/src/components/customerService/noticeDetail/bottom/NoticeDetailBottom.tsx new file mode 100644 index 00000000..ff64ec7f --- /dev/null +++ b/src/components/customerService/noticeDetail/bottom/NoticeDetailBottom.tsx @@ -0,0 +1,43 @@ +import type { OtherNotice } from '../../../../models/customerService'; +import ContentBorder from '../../../common/contentBorder/ContentBorder'; +import ListButton from './button/ListButton'; +import OtherNoticeButton from './button/OtherNoticeButton'; +import * as S from './NoticeDetailBottom.styled'; + +interface NoticeDetailBottomProps { + prev: OtherNotice | null; + next: OtherNotice | null; +} + +export default function NoticeDetailBottom({ + prev, + next, +}: NoticeDetailBottomProps) { + return ( + + + {prev !== null ? ( + + ) : ( + 이전 공지사항이 없습니다. + )} + + {next !== null ? ( + + ) : ( + 다음 공지사항이 없습니다. + )} + + + ); +} diff --git a/src/components/customerService/noticeDetail/bottom/button/ListButton.styled.ts b/src/components/customerService/noticeDetail/bottom/button/ListButton.styled.ts new file mode 100644 index 00000000..3b8f1a24 --- /dev/null +++ b/src/components/customerService/noticeDetail/bottom/button/ListButton.styled.ts @@ -0,0 +1,30 @@ +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +export const ListWrapper = styled.div` + width: 100%; + display: flex; + justify-content: center; + margin-top: 1.5rem; +`; + +export const ListLink = styled(Link)` + display: flex; + justify-content: center; + align-items: center; + font-size: 0.9rem; + background: ${({ theme }) => theme.color.navy}; + border-radius: ${({ theme }) => theme.borderRadius.large}; + color: ${({ theme }) => theme.color.white}; + border: 1px solid ${({ theme }) => theme.color.navy}; + padding: 0.5rem 1rem; + + &:hover { + background: ${({ theme }) => theme.color.lightgrey}; + color: ${({ theme }) => theme.color.navy}; + border: 1px solid ${({ theme }) => theme.color.navy}; + transition: all 0.3s ease-in-out; + } +`; + +export const ListTitle = styled.span``; diff --git a/src/components/customerService/noticeDetail/bottom/button/ListButton.tsx b/src/components/customerService/noticeDetail/bottom/button/ListButton.tsx new file mode 100644 index 00000000..7fd83b7b --- /dev/null +++ b/src/components/customerService/noticeDetail/bottom/button/ListButton.tsx @@ -0,0 +1,16 @@ +import { ROUTES } from '../../../../../constants/routes'; +import ContentBorder from '../../../../common/contentBorder/ContentBorder'; +import * as S from './ListButton.styled'; + +export default function ListButton() { + return ( + <> + + + + 목록 + + + + ); +} diff --git a/src/components/customerService/noticeDetail/bottom/button/OtherNoticeButton.styled.ts b/src/components/customerService/noticeDetail/bottom/button/OtherNoticeButton.styled.ts new file mode 100644 index 00000000..7f020f21 --- /dev/null +++ b/src/components/customerService/noticeDetail/bottom/button/OtherNoticeButton.styled.ts @@ -0,0 +1,31 @@ +import { Link } from 'react-router-dom'; +import styled from 'styled-components'; + +export const OtherNoticeLink = styled(Link)` + width: 100%; + display: flex; + padding: 0.5rem 1rem; + justify-content: space-between; + align-items: center; + font-size: 0.9rem; + + &:hover { + background: ${({ theme }) => theme.color.lightgrey}; + } +`; + +export const OtherNoticeWrapper = styled.div` + display: flex; + gap: 1rem; +`; + +export const OtherNotice = styled.span` + font-weight: 600; +`; + +export const OtherNoticeTitle = styled.span``; + +export const OtherNoticeDate = styled.span` + font-size: 0.8rem; + color: ${({ theme }) => theme.color.placeholder}; +`; diff --git a/src/components/customerService/noticeDetail/bottom/button/OtherNoticeButton.tsx b/src/components/customerService/noticeDetail/bottom/button/OtherNoticeButton.tsx new file mode 100644 index 00000000..76ce3255 --- /dev/null +++ b/src/components/customerService/noticeDetail/bottom/button/OtherNoticeButton.tsx @@ -0,0 +1,28 @@ +import { ROUTES } from '../../../../../constants/routes'; +import { OtherNotice } from '../../../../../models/customerService'; +import { formatDate } from '../../../../../util/format'; +import * as S from './OtherNoticeButton.styled'; + +interface OtherNoticeButtonProps extends OtherNotice { + navigation: string; +} + +export default function OtherNoticeButton({ + navigation, + id, + title, + createdAt, +}: OtherNoticeButtonProps) { + return ( + + + {navigation} + {title} + + {formatDate(createdAt)} + + ); +} diff --git a/src/components/customerService/noticeDetail/content/NoticeDetailContent.styled.ts b/src/components/customerService/noticeDetail/content/NoticeDetailContent.styled.ts new file mode 100644 index 00000000..fc65086c --- /dev/null +++ b/src/components/customerService/noticeDetail/content/NoticeDetailContent.styled.ts @@ -0,0 +1,68 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + width: 100%; + margin-top: 2rem; +`; + +export const TitleWrapper = styled.div` + padding: 1rem; +`; + +export const Title = styled.h2``; + +export const AdminWrapper = styled.div` + padding: 0.5rem 0; + display: flex; + align-items: center; + gap: 0.2rem; +`; + +export const AdminImg = styled.img` + width: 2rem; + height: 2rem; +`; + +export const Admin = styled.span` + font-size: 1.1rem; +`; + +export const InfoWrapper = styled.div` + display: flex; + align-items: center; + gap: 1rem; +`; + +export const NoticeContentDate = styled.span` + font-size: 0.8rem; +`; + +export const ViewWrapper = styled.div` + display: flex; + align-items: center; + gap: 0.3rem; +`; + +export const ViewIcon = styled.div` + display: flex; + svg { + width: 1rem; + height: 1rem; + } +`; + +export const ViewCount = styled.span` + font-size: 0.8rem; +`; + +export const ContentWrapper = styled.div` + min-height: 30vh; + padding: 1.5rem 1rem 4rem 1rem; +`; + +export const Content = styled.div` + width: 100%; + height: 100%; + white-space: pre-wrap; + cursor: text; +`; diff --git a/src/components/customerService/noticeDetail/content/NoticeDetailContent.tsx b/src/components/customerService/noticeDetail/content/NoticeDetailContent.tsx new file mode 100644 index 00000000..5c2af2a5 --- /dev/null +++ b/src/components/customerService/noticeDetail/content/NoticeDetailContent.tsx @@ -0,0 +1,48 @@ +import { EyeIcon } from '@heroicons/react/24/outline'; +import { formatDate } from '../../../../util/format'; +import * as S from './NoticeDetailContent.styled'; +import logo from '../../../../assets/mainlogo.svg'; +import ContentBorder from '../../../common/contentBorder/ContentBorder'; + +interface NoticeDetailContentProps { + id: number; + title: string; + content: string; + createdAt: string; + viewCount: number; +} + +export default function NoticeDetailContent({ + id, + title, + content, + createdAt, + viewCount, +}: NoticeDetailContentProps) { + return ( + + + {title} + + + DevPals + + {Boolean(id) && ( + + {formatDate(createdAt)} + + + + + {viewCount} + + + )} + + + + {content} + + + ); +} diff --git a/src/components/customerService/noticeDetail/header/NoticeDetailHeader.styled.ts b/src/components/customerService/noticeDetail/header/NoticeDetailHeader.styled.ts new file mode 100644 index 00000000..eddea3b6 --- /dev/null +++ b/src/components/customerService/noticeDetail/header/NoticeDetailHeader.styled.ts @@ -0,0 +1,16 @@ +import styled from 'styled-components'; + +export const Container = styled.header` + width: 100%; + display: flex; + flex-direction: column; + margin-top: 1rem; + gap: 1rem; +`; + +export const WrapperTitle = styled.div` + display: flex; + justify-content: center; +`; + +export const Title = styled.h1``; diff --git a/src/components/customerService/noticeDetail/header/NoticeDetailHeader.tsx b/src/components/customerService/noticeDetail/header/NoticeDetailHeader.tsx new file mode 100644 index 00000000..aa8db537 --- /dev/null +++ b/src/components/customerService/noticeDetail/header/NoticeDetailHeader.tsx @@ -0,0 +1,11 @@ +import * as S from './NoticeDetailHeader.styled'; + +export default function NoticeDetailHeader() { + return ( + + + DevPals 공지사항 + + + ); +} diff --git a/src/components/home/projectCardLists/ProjectCardLists.tsx b/src/components/home/projectCardLists/ProjectCardLists.tsx index 696e4300..4a4208dc 100644 --- a/src/components/home/projectCardLists/ProjectCardLists.tsx +++ b/src/components/home/projectCardLists/ProjectCardLists.tsx @@ -1,17 +1,23 @@ import { useEffect, useState } from 'react'; import { useProjectCardListData } from '../../../hooks/useProjectCardListData'; import CardList from './cardList/CardList'; -import Pagination from './pagination/Pagination'; import * as S from './ProjectCardLists.styled'; import { Link } from 'react-router-dom'; import { ROUTES } from '../../../constants/routes'; import EmptyLoading from '../../common/emptyLoading/EmptyLoading'; import NoResult from '../../common/noResult/NoResult'; +import { useSaveSearchFiltering } from '../../../hooks/useSaveSearchFiltering'; +import Pagination from '../../common/pagination/Pagination'; export default function ProjectCardLists() { const { projectListsData, isLoading } = useProjectCardListData(); + const { searchFilters, handleUpdateFilters } = useSaveSearchFiltering(); const [isFlex, setIsFlex] = useState(false); + const handleChangePagination = (page: number) => { + handleUpdateFilters('page', page); + }; + useEffect(() => { if (projectListsData && Boolean(projectListsData.projects.length)) { setIsFlex(false); @@ -44,7 +50,11 @@ export default function ProjectCardLists() { )} - + ); } diff --git a/src/components/mypage/activityLog/inquiries/Inquiries.styled.ts b/src/components/mypage/activityLog/inquiries/Inquiries.styled.ts index b278bef1..080866c5 100644 --- a/src/components/mypage/activityLog/inquiries/Inquiries.styled.ts +++ b/src/components/mypage/activityLog/inquiries/Inquiries.styled.ts @@ -26,12 +26,6 @@ export const InquiriesTableHeadWrapper = styled.div` margin-bottom: 0.5rem; `; -export const ContentBorder = styled.div` - width: 100%; - height: 0.5px; - background: ${({ theme }) => theme.color.placeholder}; -`; - export const InquiriesTableHeaderNo = styled.div` text-align: center; `; diff --git a/src/components/mypage/activityLog/inquiries/Inquiries.tsx b/src/components/mypage/activityLog/inquiries/Inquiries.tsx index 57a3addf..14d3b0d7 100644 --- a/src/components/mypage/activityLog/inquiries/Inquiries.tsx +++ b/src/components/mypage/activityLog/inquiries/Inquiries.tsx @@ -1,4 +1,5 @@ import { useGetMyInquiries } from '../../../../hooks/useGetMyInquiries'; +import ContentBorder from '../../../common/contentBorder/ContentBorder'; import NoContent from '../../../common/noContent/NoContent'; import Spinner from '../../Spinner'; import * as S from './Inquiries.styled'; @@ -30,7 +31,7 @@ export default function Inquiries() { 제목 상태 - + {myInquiriesData.map((list, index) => ( diff --git a/src/constants/routes.ts b/src/constants/routes.ts index e0d21454..d21643b0 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -26,5 +26,6 @@ export const ROUTES = { customerService: '/customer-service', FAQ: 'faq', notice: 'notice', + noticeDetail: 'notice-detail', inquiry: '/inquiry', } as const; diff --git a/src/hooks/queries/keys.ts b/src/hooks/queries/keys.ts index 2ab0e945..7522fcd1 100644 --- a/src/hooks/queries/keys.ts +++ b/src/hooks/queries/keys.ts @@ -47,4 +47,5 @@ export const ActivityLog = { export const CustomerService = { faq: 'faq', notice: 'notice', + noticeDetail: 'noticeDetail', }; diff --git a/src/hooks/useGetNotice.ts b/src/hooks/useGetNotice.ts new file mode 100644 index 00000000..d42c8b28 --- /dev/null +++ b/src/hooks/useGetNotice.ts @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query'; +import type { NoticeSearch } from '../models/customerService'; +import { getNotice } from '../api/customerService.api'; +import { CustomerService } from './queries/keys'; + +export const useGetNotice = (searchProperty: NoticeSearch) => { + const { keyword, page } = searchProperty; + + const { data: noticeData, isLoading } = useQuery({ + queryKey: [CustomerService.notice, keyword, page], + queryFn: () => getNotice(searchProperty), + staleTime: Infinity, + gcTime: Infinity, + }); + + return { noticeData, isLoading }; +}; diff --git a/src/hooks/useGetNoticeDetail.ts b/src/hooks/useGetNoticeDetail.ts new file mode 100644 index 00000000..bcfa8561 --- /dev/null +++ b/src/hooks/useGetNoticeDetail.ts @@ -0,0 +1,14 @@ +import { useQuery } from '@tanstack/react-query'; +import { getNoticeDetail } from '../api/customerService.api'; +import { CustomerService } from './queries/keys'; + +export const useGetNoticeDetail = (id: string) => { + const { data: noticeDetailData, isLoading } = useQuery({ + queryKey: [CustomerService.noticeDetail, id], + queryFn: () => getNoticeDetail(id), + staleTime: Infinity, + gcTime: Infinity, + }); + + return { noticeDetail: noticeDetailData, isLoading }; +}; diff --git a/src/models/customerService.ts b/src/models/customerService.ts index 5845da97..7d058a0a 100644 --- a/src/models/customerService.ts +++ b/src/models/customerService.ts @@ -10,6 +10,39 @@ export interface ApiFAQ extends ApiCommonType { data: FAQ[]; } +export interface NoticeList extends OtherNotice { + content: string; +} + +export interface Notice { + notices: NoticeList[]; + totalPages: string; +} + +export interface ApiNotice { + data: Notice; +} + +export interface OtherNotice { + id: number; + title: string; + createdAt: string; +} + +export interface NoticeDetail extends NoticeList { + viewCount: number; + prev: OtherNotice | null; + next: OtherNotice | null; +} + +export interface ApiNoticeDetail { + data: NoticeDetail; +} + export interface SearchKeyword { keyword: string; } + +export interface NoticeSearch extends SearchKeyword { + page: number; +} diff --git a/src/pages/customerService/faq/FAQ.styled.ts b/src/pages/customerService/faq/FAQ.styled.ts index 1bc2056a..0571e9f8 100644 --- a/src/pages/customerService/faq/FAQ.styled.ts +++ b/src/pages/customerService/faq/FAQ.styled.ts @@ -19,9 +19,3 @@ export const Wrapper = styled.div` `; export const ToggleWrapper = styled.div``; - -export const ContentBorder = styled.div` - width: 100%; - height: 0.5px; - background: ${({ theme }) => theme.color.placeholder}; -`; diff --git a/src/pages/customerService/faq/FAQ.tsx b/src/pages/customerService/faq/FAQ.tsx index 78f125fa..3fdefe30 100644 --- a/src/pages/customerService/faq/FAQ.tsx +++ b/src/pages/customerService/faq/FAQ.tsx @@ -6,6 +6,7 @@ import Spinner from '../../../components/mypage/Spinner'; import CustomerServiceHeader from '../../../components/customerService/CustomerServiceHeader'; import FAQContent from '../../../components/customerService/faq/FAQContent'; import NoResult from '../../../components/common/noResult/NoResult'; +import ContentBorder from '../../../components/common/contentBorder/ContentBorder'; export default function FAQ() { const [keyword, setKeyword] = useState({ keyword: '' }); @@ -40,7 +41,7 @@ export default function FAQ() { faqData.map((list) => ( - + )) ) : ( diff --git a/src/pages/customerService/notice/Notice.styled.ts b/src/pages/customerService/notice/Notice.styled.ts index 926ed2ed..60578597 100644 --- a/src/pages/customerService/notice/Notice.styled.ts +++ b/src/pages/customerService/notice/Notice.styled.ts @@ -1,3 +1,23 @@ +import { Link } from 'react-router-dom'; import styled from 'styled-components'; -export const Container = styled.section``; +export const SpinnerWrapper = styled.div` + height: 60vh; +`; + +export const Container = styled.section` + margin-top: 2rem; + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +export const Wrapper = styled.div` + width: 75%; + display: flex; + flex-direction: column; +`; + +export const NoticeDetailLink = styled(Link)``; diff --git a/src/pages/customerService/notice/Notice.tsx b/src/pages/customerService/notice/Notice.tsx index f6cd848c..43acbbd1 100644 --- a/src/pages/customerService/notice/Notice.tsx +++ b/src/pages/customerService/notice/Notice.tsx @@ -1,9 +1,74 @@ +import { useState } from 'react'; +import CustomerServiceHeader from '../../../components/customerService/CustomerServiceHeader'; +import Spinner from '../../../components/mypage/Spinner'; +import { useGetNotice } from '../../../hooks/useGetNotice'; import * as S from './Notice.styled'; +import type { NoticeSearch } from '../../../models/customerService'; +import NoResult from '../../../components/common/noResult/NoResult'; +import NoticeList from '../../../components/customerService/notice/NoticeList'; +import { ROUTES } from '../../../constants/routes'; +import Pagination from '../../../components/common/pagination/Pagination'; +import ContentBorder from '../../../components/common/contentBorder/ContentBorder'; export default function Notice() { + const [keyword, setKeyword] = useState({ + keyword: '', + page: 1, + }); + const [value, setValue] = useState(''); + const { noticeData, isLoading } = useGetNotice(keyword); + + const handleGetKeyword = (keyword: string) => { + setKeyword((prev) => ({ ...prev, keyword })); + setValue(keyword); + }; + const handleChangePagination = (page: number) => { + setKeyword((prev) => ({ ...prev, page })); + }; + + if (isLoading) { + return ( + + + + ); + } + + if (!noticeData) return; + + const lastPage = Number(noticeData.totalPages); + return ( <> - + + + + {noticeData.notices.length > 0 && } + {noticeData.notices.length > 0 ? ( + noticeData.notices.map((list) => ( + + + + + )) + ) : ( + + )} + + + ); } diff --git a/src/pages/customerService/noticeDetail/NoticeDetail.styled.ts b/src/pages/customerService/noticeDetail/NoticeDetail.styled.ts new file mode 100644 index 00000000..6cfb8e0f --- /dev/null +++ b/src/pages/customerService/noticeDetail/NoticeDetail.styled.ts @@ -0,0 +1,3 @@ +import styled from 'styled-components'; + +export const Container = styled.header``; diff --git a/src/pages/customerService/noticeDetail/NoticeDetail.tsx b/src/pages/customerService/noticeDetail/NoticeDetail.tsx new file mode 100644 index 00000000..92bbeda1 --- /dev/null +++ b/src/pages/customerService/noticeDetail/NoticeDetail.tsx @@ -0,0 +1,5 @@ +import NoticeDetailBundle from '../../../components/customerService/noticeDetail/NoticeDetailBundle'; + +export default function NoticeDetail() { + return ; +} diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 03605a45..0b4169dc 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -3,7 +3,6 @@ import Banner from '../../components/home/banner/Banner'; import ProjectStats from '../../components/home/projectStats/ProjectStats'; import SearchFiltering from '../../components/home/searchFiltering/SearchFiltering'; import ProjectCardLists from '../../components/home/projectCardLists/ProjectCardLists'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; const Home = () => { return ( @@ -14,7 +13,6 @@ const Home = () => { - ); }; diff --git a/src/pages/mypage/MyPage.tsx b/src/pages/mypage/MyPage.tsx index 23736656..72579cf0 100644 --- a/src/pages/mypage/MyPage.tsx +++ b/src/pages/mypage/MyPage.tsx @@ -10,7 +10,6 @@ import { import { ROUTES } from '../../constants/routes'; import { useMyProfileInfo } from '../../hooks/useMyInfo'; import loadingImg from '../../assets/loadingImg.svg'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; const MyPage = () => { const menuItems = [ @@ -47,7 +46,6 @@ const MyPage = () => { - ); }; diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index e95f4d8d..83f33f45 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -23,6 +23,9 @@ const Layout = lazy(() => import('../components/common/layout/Layout')); const Home = lazy(() => import('../pages/home/Home')); const FAQ = lazy(() => import('../pages/customerService/faq/FAQ')); const Notice = lazy(() => import('../pages/customerService/notice/Notice')); +const NoticeDetail = lazy( + () => import('../pages/customerService/noticeDetail/NoticeDetail') +); const Inquiry = lazy( () => import('../components/customerService/inquiry/Inquiry') ); @@ -143,6 +146,10 @@ const AppRoutes = () => { path: ROUTES.notice, element: , }, + { + path: `${ROUTES.noticeDetail}/:noticeId`, + element: , + }, ], }, {