[React] 공지사항 게시판 만들기(3): detail 공지사항 상세페이지 조회

 

 

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