Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
29 changes: 28 additions & 1 deletion src/api/customerService.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { ApiFAQ, SearchKeyword } from '../models/customerService';
import type {
ApiFAQ,
ApiNotice,
ApiNoticeDetail,
SearchKeyword,
} from '../models/customerService';
import { httpClient } from './http.api';

export const getFAQ = async (params: SearchKeyword) => {
Expand All @@ -11,3 +16,25 @@ export const getFAQ = async (params: SearchKeyword) => {
throw e;
}
};

export const getNotice = async (params: SearchKeyword) => {
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;
}
};
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
26 changes: 26 additions & 0 deletions src/components/customerService/notice/NoticeList.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import styled from 'styled-components';

export const Container = styled.nav`
width: 100%;
`;

export const Wrapper = styled.button`
width: 100%;
padding: 1rem;
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 Date = styled.span`
font-size: 1.1rem;
`;
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 { Notice } from '../../../models/customerService';
import { formatDate } from '../../../util/format';
import * as S from './NoticeList.styled';

interface NoticeProps {
list: Notice;
}

export default function NoticeList({ list }: NoticeProps) {
return (
<S.Container>
<S.Wrapper>
<S.Title>{list.title}</S.Title>
<S.Date>{formatDate(list.createdAt)}</S.Date>
</S.Wrapper>
</S.Container>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

컴포넌트 구현은 좋으나 몇 가지 개선 사항이 있습니다.

컴포넌트가 잘 구현되었지만, 다음과 같은 개선 사항을 고려해 보세요:

  1. props 이름을 list에서 notice로 변경
  2. createdAt 값이 없는 경우를 대비한 오류 처리 추가
-export default function NoticeList({ list }: NoticeProps) {
+export default function NoticeList({ notice }: NoticeProps) {
  return (
    <S.Container>
      <S.Wrapper>
-        <S.Title>{list.title}</S.Title>
-        <S.Date>{formatDate(list.createdAt)}</S.Date>
+        <S.Title>{notice.title}</S.Title>
+        <S.Date>{notice.createdAt ? formatDate(notice.createdAt) : '-'}</S.Date>
      </S.Wrapper>
    </S.Container>
  );
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import styled from 'styled-components';

export const SpinnerWrapper = styled.div`
height: 60vh;
`;

export const Container = styled.div`
width: 75%;
margin: 0 auto;
`;
38 changes: 38 additions & 0 deletions src/components/customerService/noticeDetail/NoticeDetailBundle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useLocation } 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';

export default function NoticeDetailBundle() {
const location = useLocation();
const { id } = location.state;

const { noticeDetailData, isLoading } = useGetNoticeDetail(String(id));

if (!noticeDetailData) return;

if (isLoading) {
return (
<S.SpinnerWrapper>
<Spinner />
</S.SpinnerWrapper>
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

오류 상태 처리가 누락되었습니다.

데이터가 없거나 로딩 중인 상태는 처리되어 있으나, API 요청 중 오류가 발생한 경우에 대한 처리가 없습니다.

다음과 같이 오류 상태를 처리하는 코드를 추가하세요:

-const { noticeDetailData, isLoading } = useGetNoticeDetail(String(id));
+const { noticeDetailData, isLoading, isError, error } = useGetNoticeDetail(String(id));

 if (!noticeDetailData) return;

+if (isError) {
+  return (
+    <S.ErrorWrapper>
+      <p>공지사항을 불러오는 중 오류가 발생했습니다.</p>
+      <p>{error instanceof Error ? error.message : '다시 시도해주세요.'}</p>
+    </S.ErrorWrapper>
+  );
+}
+
 if (isLoading) {
   return (
     <S.SpinnerWrapper>

그리고 관련 스타일 컴포넌트를 NoticeDetailBundle.styled.ts에 추가해야 합니다.


const { title, content, createdAt, prev, next } = noticeDetailData;

return (
<S.Container>
<NoticeDetailHeader />
<NoticeDetailContent
title={title}
content={content}
createdAt={createdAt}
/>
<NoticeDetailBottom prev={prev} next={next} />
</S.Container>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Link } from 'react-router-dom';
import styled from 'styled-components';

export const Container = styled.nav``;

export const NotOtherNotice = styled.div`
padding: 0.5rem 1rem;
font-size: 0.9rem;
`;

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``;

export const ContentBorder = styled.div`
width: 100%;
height: 0.5px;
background: ${({ theme }) => theme.color.placeholder};
position: relative;
z-index: 1;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ROUTES } from '../../../../constants/routes';
import type { OtherNotice } from '../../../../models/customerService';
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>
<S.ContentBorder></S.ContentBorder>
{prev !== null ? (
<OtherNoticeButton
navigation='이전'
id={prev.id}
title={prev.title}
createdAt={prev.createdAt}
/>
) : (
<S.NotOtherNotice>이전 공지사항이 없습니다.</S.NotOtherNotice>
)}
<S.ContentBorder></S.ContentBorder>
{next !== null ? (
<OtherNoticeButton
navigation='다음'
id={next.id}
title={next.title}
createdAt={next.createdAt}
/>
) : (
<S.NotOtherNotice>다음 공지사항이 없습니다.</S.NotOtherNotice>
)}
<S.ContentBorder></S.ContentBorder>
<S.ListWrapper>
<S.ListLink to={`${ROUTES.customerService}/${ROUTES.notice}`}>
<S.ListTitle>목록</S.ListTitle>
</S.ListLink>
</S.ListWrapper>
</S.Container>
);
}
Original file line number Diff line number Diff line change
@@ -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};
`;
Original file line number Diff line number Diff line change
@@ -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 (
<S.OtherNoticeLink
to={`${ROUTES.customerService}/${ROUTES.noticeDetail}/${id}`}
state={{ id }}
>
<S.OtherNoticeWrapper>
<S.OtherNotice>{navigation}</S.OtherNotice>
<S.OtherNoticeTitle>{title}</S.OtherNoticeTitle>
</S.OtherNoticeWrapper>
<S.OtherNoticeDate>{formatDate(createdAt)}</S.OtherNoticeDate>
</S.OtherNoticeLink>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import styled from 'styled-components';

export const Container = styled.section`
width: 100%;
margin: 2rem 0;
`;

export const TitleWrapper = styled.div`
padding: 1rem;
`;

export const Title = styled.h2``;

export const AdminWrapper = styled.div`
padding: 0.5rem 0;
`;

export const AdminImg = styled.img`
width: 2rem;
height: 2rem;
`;

export const Admin = styled.span`
vertical-align: middle;
font-size: 1.1rem;
`;

export const InfoWrapper = styled.div`
display: flex;
align-items: center;
gap: 1rem;
`;

export const Date = styled.span`
font-size: 0.8rem;
`;

export const ViewWrapper = styled.div``;

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`
padding: 1.5rem 1rem;
`;

export const Content = styled.p``;

export const ContentBorder = styled.div`
width: 100%;
height: 0.5px;
background: ${({ theme }) => theme.color.placeholder};
position: relative;
z-index: 1;
`;
Loading