Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -21,6 +23,7 @@ function App() {
<GlobalStyle />
<QueryClientProvider client={queryClient}>
<AppRoutes />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</SearchFilteringProvider>
</ThemeProvider>
Expand Down
30 changes: 29 additions & 1 deletion src/api/customerService.api.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand All @@ -11,3 +17,25 @@ export const getFAQ = async (params: SearchKeyword) => {
throw e;
}
};

export const getNotice = async (params: NoticeSearch) => {
try {
const response = await httpClient.get<ApiNotice>(`/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<ApiNoticeDetail>(`/notice/${id}`);

return response.data.data;
} catch (e) {
console.error(e);
throw e;
}
};
7 changes: 7 additions & 0 deletions src/components/common/contentBorder/ContentBorder.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from 'styled-components';

export const ContentBorder = styled.div`
width: 100%;
height: 0.5px;
background: ${({ theme }) => theme.color.placeholder};
`;
5 changes: 5 additions & 0 deletions src/components/common/contentBorder/ContentBorder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as S from './ContentBorder.styled';

export default function ContentBorder() {
return <S.ContentBorder></S.ContentBorder>;
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import React, { useEffect, useState } from 'react';
import { useProjectCardListData } from '../../../../hooks/useProjectCardListData';
import { useSaveSearchFiltering } from '../../../../hooks/useSaveSearchFiltering';
import * as S from './Pagination.styled';
import {
ChevronLeftIcon,
ChevronRightIcon,
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;
Expand All @@ -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 (
<S.Container>
<S.Wrapper onClick={handleMovePaginationClick}>
{currentPage !== 1 && (
<>
<S.PaginationButton
onClick={() => handleUpdateFilters('page', currentPage - 1)}
onClick={() => onChangePagination(currentPage - 1)}
>
<ChevronLeftIcon />
</S.PaginationButton>
<S.PaginationDoubleButton
onClick={() => handleUpdateFilters('page', 1)}
>
<S.PaginationDoubleButton onClick={() => onChangePagination(1)}>
1
</S.PaginationDoubleButton>
<EllipsisHorizontalIcon />
Expand All @@ -74,12 +77,12 @@ export default function Pagination() {
<>
<EllipsisHorizontalIcon />
<S.PaginationDoubleButton
onClick={() => handleUpdateFilters('page', lastPage)}
onClick={() => onChangePagination(lastPage)}
>
{lastPage}
</S.PaginationDoubleButton>
<S.PaginationButton
onClick={() => handleUpdateFilters('page', currentPage + 1)}
onClick={() => onChangePagination(currentPage + 1)}
>
<ChevronRightIcon />
</S.PaginationButton>
Expand Down
4 changes: 2 additions & 2 deletions src/components/customerService/faq/FAQContent.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
`;
Expand Down
4 changes: 2 additions & 2 deletions src/components/customerService/inquiry/Inquiry.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
`}
Expand Down
27 changes: 27 additions & 0 deletions src/components/customerService/notice/NoticeList.styled.ts
Original file line number Diff line number Diff line change
@@ -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};
`;
18 changes: 18 additions & 0 deletions src/components/customerService/notice/NoticeList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<S.Container>
<S.Wrapper type='button' aria-label='공지사항 상세보기'>
<S.Title>{notice.title}</S.Title>
<S.NoticeDate>{formatDate(notice.createdAt)}</S.NoticeDate>
</S.Wrapper>
</S.Container>
);
}
Original file line number Diff line number Diff line change
@@ -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;
`;
63 changes: 63 additions & 0 deletions src/components/customerService/noticeDetail/NoticeDetailBundle.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<S.SpinnerWrapper>
<Spinner />
</S.SpinnerWrapper>
);
}

if (!noticeDetailData) {
return (
<S.Container>
<NoticeDetailContent
id={0}
title='공지사항이 없습니다.'
content='목록으로 돌아가세요.'
createdAt=''
viewCount={0}
/>
<ListButton />
</S.Container>
);
}

const {
id: detailId,
title,
content,
createdAt,
viewCount,
prev,
next,
} = noticeDetailData;

return (
<S.Container>
<NoticeDetailHeader />
<NoticeDetailContent
id={detailId}
title={title}
content={content}
createdAt={createdAt}
viewCount={viewCount}
/>
<NoticeDetailBottom prev={prev} next={next} />
</S.Container>
);
}
Original file line number Diff line number Diff line change
@@ -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;
`;
Original file line number Diff line number Diff line change
@@ -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 (
<S.Container>
<ContentBorder />
{prev !== null ? (
<OtherNoticeButton
navigation='이전'
id={prev.id}
title={prev.title}
createdAt={prev.createdAt}
/>
) : (
<S.NotOtherNotice>이전 공지사항이 없습니다.</S.NotOtherNotice>
)}
<ContentBorder />
{next !== null ? (
<OtherNoticeButton
navigation='다음'
id={next.id}
title={next.title}
createdAt={next.createdAt}
/>
) : (
<S.NotOtherNotice>다음 공지사항이 없습니다.</S.NotOtherNotice>
)}
<ListButton />
</S.Container>
);
}
Original file line number Diff line number Diff line change
@@ -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``;
Original file line number Diff line number Diff line change
@@ -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 (
<>
<ContentBorder />
<S.ListWrapper>
<S.ListLink to={`${ROUTES.customerService}/${ROUTES.notice}`}>
<S.ListTitle>목록</S.ListTitle>
</S.ListLink>
</S.ListWrapper>
</>
);
}
Loading