본문 바로가기
개발/Spring

[SpringBoot] React + TypeScript + JPA 프로젝트 (9) - 게시판 만들기(상세보기)

by 코딩하는 흰둥이 2024. 11. 20.

이전글

https://greed-yb.tistory.com/321

 

[SpringBoot] React + TypeScript + JPA 프로젝트 (8) - useContext 전역변수 다루기

이전글https://greed-yb.tistory.com/310 [SpringBoot] React + TypeScript + JPA 프로젝트 (7) - 게시판 만들기(글 작성)이전글https://greed-yb.tistory.com/309 [SpringBoot] React + TypeScript + JPA 프로젝트 (6) - 게시판 만들기(페

greed-yb.tistory.com

 

 

 

 

게시판 목록과 작성 페이지를 만들었으니

이번엔 조회 페이지를 만들어보겠다

 

 

 

board.tsx - 게시판 목록

페이징 구현까지 한 게시글 목록에서

제목을 눌러서 해당 게시물로 들어가기 위해 <Link> 태그를 추가해준다

 

import React, {useEffect, useState} from "react";
import axios from "axios";
import {Link} from "react-router-dom";
import {Paging} from "../utils/pagination";
import {useUserContext} from "../utils/UserInfo";


// 게시판 변수 타입 설정
interface Board {
    no: number;
    title: string;
    writer: string;
    created: string;
}


function Board() {

    // 게시판 목록 변수
    const [BoardList, setBoardList] = useState<Board[]>([]);
    const [boardDataSet, setBoardDataSet] = useState<Board[]>([]);

    // 검색 변수
    const [searchOption, setSearchOption] = useState('title');
    const [search, setSearch] = useState('');

    // enter key 적용
    const enterKey = (event: React.KeyboardEvent<HTMLInputElement>) =>{
        if(event.key === "Enter"){
            axiosBoardList();
        }
    }

    useEffect(() => {
        axiosBoardList();
    }, []);

    // 게시물 목록 가져오는 axios
    const axiosBoardList = async () => {
        await axios.get("/api/boardList" , {
            params: {
                searchOption,
                search,
            },
        }).then((response) => {
            if(response.data.length > 0){
                setBoardList(response.data);
                setPage(1);
            }else{
                // 게시글 변수 초기화
                setBoardList([]);
                setBoardDataSet([]);
                setPage(1);
            }
        }).catch((error) => {
            console.log("error : " + error);
        });
    }

    // 현재 페이지 설정
    const [page, setPage] = useState(0);
    const handlePageChange = (page: number) => {
        setPage(page);
    };

    // 한 페이지에서 나오는 게시글 수
    const pageItemSize : number = 5;

    // pageItemSize 을 가지고 배열을 만들어준다
    let indexArray = Array.from({ length: pageItemSize }, (item, index) => {
        return index;
    });

    let pageIndex : number[] = [];
    // 현재 페이지 번호에 맞는 게시글 번호를 담는다
    // pageItemSize = 5 로 설정한 상태
    // 1page = [0,1,2,3,4] / 2page = [5,6,7,8,9] ....
    pageIndex = page === 1 ? indexArray : indexArray.map((item) => item + (page - 1) * pageItemSize);

    // 현재 페이지의 게시글 데이터
    const pagingData: Board[] = [];

    // 검색 및 페이징에 따른 데이터 변경
    const dataChange = () => {
        if(BoardList.length > 0){
            for (let i = 0; i < indexArray.length; i++) {
                if (BoardList && BoardList[pageIndex[i]] === undefined) {
                    break;
                } else {
                    pagingData.push(BoardList[pageIndex[i]]);
                }
            }
            setBoardDataSet(pagingData);
        }else{
            setBoardDataSet([]);
        }
    };

    // 게시물 변수 및 페이지 클릭 시 게시글 데이터 변경
    useEffect(() => {
        if (BoardList.length > 0) {
            dataChange();
        }
    }, [BoardList ,page]);


    return (
        <main id="main" className="main">
            <div className="pagetitle">
                <h1>게시판</h1>
                <nav>
                    <ol className="breadcrumb">
                        <li className="breadcrumb-item"><Link to="/">Home</Link></li>
                        <li className="breadcrumb-item">Tables</li>
                        <li className="breadcrumb-item active">Data</li>
                    </ol>
                </nav>
            </div>
            <section className="section">
                <div className="row">
                    <div className="col-lg-12">

                        <div className="card">
                            <div className="card-body">
                                <h5 className="card-title">게시판 목록</h5>
                                <Link to="/boardWrite" className="btn btn-dark" type="button" style={{float:"right"}}>글 작성</Link>

                                <table className="table datatable">
                                    <thead>
                                    <tr>
                                        <th>
                                            No
                                        </th>
                                        <th>Title</th>
                                        <th>Writer</th>
                                        <th data-type="date" data-format="YYYY/DD/MM">Created</th>
                                    </tr>
                                    </thead>
                                    <tbody>
                                    {boardDataSet.length > 0 ? (
                                        boardDataSet.map((board) => (
                                            <tr key={board.no}>
                                                <td>{board.no}</td>
                                                <td><Link to={`/BoardRead/${board.no}`} >{board.title}</Link></td>
                                                <td>{board.writer}</td>
                                                <td>{board.created}</td>
                                            </tr>
                                        ))
                                    ) : (
                                        <tr>
                                            <td colSpan={4} style={{ textAlign: 'center' }}>데이터가 없습니다.</td>
                                        </tr>
                                    )}
                                    </tbody>
                                </table>
                                <div className="search-bar" style={{textAlign: "center"}}>
                                    <select onChange={(event => setSearchOption(event.target.value))}>
                                        <option value="title">제목</option>
                                        <option value="writer">작성자</option>
                                    </select>
                                    <input type="text" name="query" placeholder="Search" title="Enter search keyword" onChange={event => setSearch(event.target.value)} onKeyDown={enterKey}/>
                                    <button type="button" title="Search" className="btn btn-light" onClick={axiosBoardList}>검색</button>
                                    <Paging
                                        currentPage={page}
                                        pageItemSize={pageItemSize}
                                        totalData={BoardList.length}
                                        clickPage={handlePageChange}
                                        pageSize={10}
                                    />

                                </div>
                                </div>
                        </div>

                    </div>
                </div>
            </section>

        </main>

    );
}

export default Board;

 

 

 

 

App.tsx

 

Route path 에 게시글 no 를 인자로 받는다

여기는 소스를 첨부하지 않겠다

 

 

 

 

 

boardRead.tsx - 글 상세 보기 페이지

글 작성 페이지를 복사해서 만들었다

import React, {useEffect, useState} from "react";
import axios from "axios";
import {Link, useNavigate, useParams} from "react-router-dom";
import Cookies from 'js-cookie';
import { useUserContext } from '../utils/UserInfo';

// 게시판 변수 타입 설정
interface BoardRead {
    title: string;
    contents: string;
    writer: string;
}


function BoardRead() {

    // navigate 훅 사용
    const navigate = useNavigate();  
    // Route path 로 받은 no 값을 받아온다
    const { no } = useParams();

    const [board, setBoarRead] = useState<BoardRead>({
        title: "",
        contents: "",
        writer: ""
    });

    useEffect(() => {
        axios.get("/api/boardRead" , {
            params : {
                no
            }
        }).then((response => {
            setBoarRead(response.data);
        })).catch((error) => {
            console.log(error)
        })
    }, [no])


    const [data, setData] = useState<BoardRead[]>([]);

    // 태그의 name 을 인식해서 값을 가져온다
    const dataCheck = (event: { target: { value: string; name: string; }; }) => {
        const { value, name } = event.target;
        setData({
            ...data,
            [name] : value,
        })
    }

    const axiosSave : () => Promise<void> = async () => {

        const token = document.cookie;
        if (!token){
            // token 이 없다면 로그인 화면으로
            navigate('/login');
        }else{
            // npm install js-cookie
            // npm install @types/js-cookie
            // Cookies 에서 바로 token 을 가져올 수 있게 js-cookie 를 설치해서 사용하였다
            let checkToken : string | undefined = "";
            if(!Cookies.get('refreshToken')){
                // refreshToken 이 없다면 로그인 화면으로
                navigate('/login');
            }else{
                checkToken = Cookies.get('refreshToken')
            }

            await axios.post("http://localhost:7172/api/boardSave" , data ,{
                    params: {checkToken}
                }
            ).then((response) => {
                if(response.data == "success"){
                    alert("저장하였습니다.");
                    navigate('/board');
                }
            }).catch((error) => {
                alert("저장 실패하였습니다.");
            })
        }
    }

    return (
        <main id="main" className="main">

            <div className="pagetitle">
                <h1>게시판</h1>
                <nav>
                    <ol className="breadcrumb">
                        <li className="breadcrumb-item"><Link to="/">Home</Link></li>
                        <li className="breadcrumb-item">Forms</li>
                        <li className="breadcrumb-item active">Layouts</li>
                    </ol>
                </nav>
            </div>


            <section className="section">
                <div className="row">
                    <div className="col-lg-6">
                        <div className="card">
                            <div className="card-body">
                                <h5 className="card-title">상세보기</h5>

                                <form className="row g-3">
                                    <div className="col-6">
                                        <label htmlFor="title" className="form-label">제목</label>
                                        <input type="text" className="form-control" id="title" name="title" value={board.title} onChange={dataCheck} />
                                    </div>
                                    <div className="col-6">
                                        <label htmlFor="writer" className="form-label">글쓴이</label>
                                        <input type="text" className="form-control" id="writer" name="writer" value={board.writer} onChange={dataCheck}/>
                                    </div>
                                    <div className="col-12">
                                        <label htmlFor="contents" className="form-label">내용</label>
                                        <textarea className="form-control" id="contents" name="contents" style={{height: "200px"}} value={board.contents} onChange={dataCheck}></textarea>
                                    </div>
                                    <div className="text-center">
                                        <button type="button" className="btn btn-primary" onClick={axiosSave}>저장</button>
                                        <Link to="/board" className="btn btn-secondary">뒤로</Link>
                                    </div>
                                </form>

                            </div>
                        </div>

                    </div>
                </div>
            </section>
        </main>

    );
}

export default BoardRead;

 

 

 

게시판 목록에서 클릭한 글번호를 useParams() 으로 받아와서

axios 를 이용하여 글을 조회한다

태그에는 value 에 데이터를 바인딩해 준다

 

 

 

 

현재 태그에 readOnly 설정을 하지 않았는데도 글이 작성되지 않을 것이다

그 이유는 불러온 값은 setBoardRead() 을 이용하여 board 에 담았는데

태그 필드에서 onChange 는  data바라보고 있기 때문이다

 

해당 소스는 게시글 수정을 위해 소스를 변경할 것이고

태그에 readOnly 설정을 하는 방식은 아래 처럼 하면 된다

<input type="text" Id="title" name="title" value={board.title} onChange={dataCheck} readOnly={true} />

 

 

 

 

Controller
    // 게시글 조회하기
    @GetMapping("/boardRead")
    public Board boardRead(@RequestParam Long no) throws Exception{
        System.err.println("no : " + no);
        
//        Optional<Board> vo = boardRepository.findById(no);
//        return vo.get();

        // 이런방식도 있다 
        return boardRepository.findById(no).orElseGet(()-> {
                Board result = new Board();
                result.setNo(no);
                result.setTitle("오류가 발생하였습니다");
                result.setWriter("오류가 발생하였습니다");
                result.setContents("오류가 발생하였습니다");
                return result;
        });

    }

findById 는 JPA 기본 메서드로 설명을 생략하겠다

 

 

 

 

 

 

TEST

 

데이터가 정상적으로 조회되었다

 

이제 전역변수를 이용하여 수정, 삭제를 제어하고 기능을 추가하면 된다

댓글