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: ,
+ },
],
},
{