react 공지사항 게시판 상세페이지 조회
https://joygotohome.tistory.com/125
이어지는 글입니다.
1. detail 훅 만들기
\features\notice\model\use-notice-detail.ts
import { useNavigate, useParams } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { getNoticeDetailRequest } from '../api/notice-api';
export const useNoticeDetail = () => {
const navigate = useNavigate();
const { id } = useParams();
const numericId = Number(id);
/*
* queryKey 규칙
['도메인', '액션', '식별자']
['notice', 'list'] → 공지사항 목록
['notice', 'detail', 1] → 1번 공지사항 상세
['notice', 'detail', 2] → 2번 공지사항 상세
*/
const { data, isLoading } = useQuery({
queryKey: ['notice', 'detail', id],
queryFn: () => getNoticeDetailRequest(numericId),
enabled: !!numericId,
});
const handleGoList = () => {
navigate(`/notice`);
};
const handleGoUpdate = () => {
navigate(`/notice/update/${numericId}`);
};
return {
isLoading,
data: data ?? null, // 공지사항 상세 데이터
handleGoList, // 목록으로 이동
handleGoUpdate, // 수정 페이지로 이동
};
};
2. API 연결추가
\features\notice\api\notice-api.ts
import apiClient from '@/shared/api/api-client';
import { getCompanyId } from '@/shared/hooks/use-company-info';
import type { NoticeCreateRequest, NoticeDetailRequest, NoticeListItem, PageResponse } from '../model/types';
const BASE_URL = '/notices';
// 목록 조회 - getData 사용 (data만 바로 반환)
export const getNoticeList = async (page = 0, size = 20): Promise<PageResponse<NoticeListItem>> => {
const companyId = getCompanyId();
return await apiClient.getData(`${BASE_URL}/company/${companyId}`, { params: { page, size } });
};
// 상세 조회
export const getNoticeDetailRequest = async (id: number): Promise<NoticeDetailRequest> => {
return await apiClient.getData(`${BASE_URL}/${id}`);
};
// 작성
export const createNotice = async (data: NoticeCreateRequest): Promise<{ id: number }> => {
return await apiClient.postData(`${BASE_URL}`, data);
};
3. type 추가
\features\notice\model\types.ts
export interface NoticeDetailRequest {
id: number;
companyId: number | null;
title: string;
content: string;
targetType: string | null;
displayType: string | null;
startDate: string | null;
endDate: string | null;
useYn: string;
registrationDateTime: string;
modificationDateTime: string;
}
4. Form그리기
\features\notice\ui\notice-detail.ts
import { useNoticeDetail } from '../model/use-notice-detail';
import {
FlexContainer,
FlexLoading,
NoticeSpinner,
ButtonFlex,
CancelButton,
SubmitButton,
NoticeDescriptions,
} from './notice-detail.styles';
export const NoticeDetail = (props: React.ComponentProps<typeof NoticeDescriptions>) => {
const { isLoading, data, handleGoList, handleGoUpdate } = useNoticeDetail();
if (isLoading) return (
<FlexLoading>
<NoticeSpinner />
</FlexLoading>
);
return (
<FlexContainer>
<ButtonFlex>
<CancelButton onClick={handleGoList}>목록</CancelButton>
<SubmitButton type="primary" onClick={handleGoUpdate}>수정</SubmitButton>
</ButtonFlex>
<NoticeDescriptions column={1} bordered {...props} >
<NoticeDescriptions.Item label="공지 대상">{data?.targetType}</NoticeDescriptions.Item>
<NoticeDescriptions.Item label="노출 방식">{data?.displayType}</NoticeDescriptions.Item>
<NoticeDescriptions.Item label="시작일">{data?.startDate}</NoticeDescriptions.Item>
<NoticeDescriptions.Item label="종료일">{data?.endDate}</NoticeDescriptions.Item>
<NoticeDescriptions.Item label="등록일">{data?.registrationDateTime}</NoticeDescriptions.Item>
<NoticeDescriptions.Item label="수정일">{data?.modificationDateTime}</NoticeDescriptions.Item>
<NoticeDescriptions.Item label="제목">{data?.title}</NoticeDescriptions.Item>
<NoticeDescriptions.Item label="내용">{data?.content}</NoticeDescriptions.Item>
</NoticeDescriptions>
</FlexContainer>
);
};
\features\notice\ui\notice-detail.styles.ts
import styled from 'styled-components';
import { Spin, Button, Flex, Descriptions } from 'antd';
// 전체 페이지를 감싸는 최상위 컨테이너
// 세로 방향으로 Header, Table 순서로 배치
export const FlexContainer = styled(Flex)`
height: 100%;
flex-direction: column;
gap: 0;
`;
export const FlexLoading = styled(Flex)`
min-height: 100vh;
align-items: center;
justify-content: center;
`;
export const NoticeSpinner = styled(Spin)`
font-size: 1.5rem;
`;
export const ButtonFlex = styled(Flex)`
justify-content: flex-end;
gap: 0.5rem;
margin-bottom: 1.5rem;
`;
// 버튼은 상단 오른쪽에 배치
// create 와 달리 border-top 대신 margin-bottom 으로 아래 내용과 간격
export const CancelButton = styled(Button)`
min-width: 5rem;
`;
export const SubmitButton = styled(Button)`
min-width: 5rem;
`;
export const NoticeDescriptions = styled(Descriptions)`
.ant-descriptions-item-label {
width: 8rem;
font-weight: 500;
background-color: #fafafa;
}
.ant-descriptions-item-content {
font-size: 0.9375rem;
}
`;
스타일은 나중에 바꾸도록 합시다.
5. page 연결하기
\pages\notice\ui\notice-detail-page.tsx
import { NoticeDetail } from "@/features/notice"
export const NoticeDetailPage = () => {
return (
<NoticeDetail />
)
}
\pages\notice\index.ts
export { NoticeListPage } from './ui/notice-list-page';
export { NoticeCreatePage } from './ui/notice-create-page';
export { NoticeDetailPage } from './ui/notice-detail-page';
\features\notice\index.ts
export type { NoticeCreateRequest, NoticeDetailRequest, NoticeListItem, PageResponse } from './model/types';
export { NoticeList } from './ui/notice-list';
export { NoticeCreate } from './ui/notice-create';
export { NoticeDetail } from './ui/notice-detail';

'React' 카테고리의 다른 글
| [React] 공지사항 게시판 만들기(4): update 공지사항 수정 (0) | 2026.03.24 |
|---|---|
| [React] 공지사항 게시판 만들기(2): create 공지사항 작성 (0) | 2026.03.23 |
| [React] 공지사항 게시판 만들기(1): list 공지사항 목록 조회 (0) | 2026.03.23 |
| [React] 프로젝트 생성방법 vite, CRA (0) | 2026.01.07 |
| [React] NextJS 새로운 페이지 생성 및 연결 방법 (0) | 2026.01.07 |