개발 기록/도서관 관리 시스템

[Library Management System] 25.10.31 | (구현) 마이페이지-리뷰 내역 페이지 전체 목록 조회, 리뷰 수정 기능

dev.jelee 2025. 11. 3. 23:30

[ 작업한 내용 ]

# Frontend

1. 리뷰 내역 페이지에서 리뷰 전체 목록 조회 API 연결

// 리뷰 전체 api
const fetchBookReview = async (page, size) => {

  setLoading(true);
  setError(null);

  try {
    const response = await axios.get(
      `http://localhost:8080/api/v1/user/me/reviews`,
      {
        params: {
          page: page,
          size: size,
        },
        withCredentials: true,
        headers: {
          Accept: "application/json",
        }
      }
    );

    console.log(response.data.data);
    setData(response.data.data.content);
  } catch (error) {
    console.log("Error: ", error.response);
    setError(error.response);
  } finally {
    console.log("리뷰 전체 목록 조회 완료");
    setLoading(false);
  }
}


{/* 대출 내역 items */}
{!loading && !error && (
  <>
    {data.map((review) => (
      <div 
        key={review.id}
        className="flex flex-col w-full border border-gray-200 p-3 mb-2"
      >
        <div className="flex flex-row w-full overflow-hidden">
          {/* 썸네일 */}
          <div className="flex w-[100px] min-w-[100px] items-start shrink-0">
            <img src="https://placehold.co/420x600" alt="" className="w-auto block h-auto" />
          </div>
          {/* 텍스트 */}
          <div className="flex flex-col justify-start self-start w-full min-w-0 leading-6 text-[15px] ml-2 text-gray-700">
            <div className="font-bold text-black">{review.bookTitle}</div>
            <div>작성일: {review.createdDate.split('T').join(" ")}</div>
            <div className="flex flex-row w-full mt-2 leading-6 overflow-hidden items-center">
              <div className="min-w-0 px-2 py-1 h-[82px] border border-gray-300 line-clamp-3">
                {review.content}
              </div>
              <div className="flex flex-col self-end ml-2">
                <button className="mb-2 px-3 py-2 border border-gray-300 leading-none whitespace-nowrap">수정</button>
                <button className="px-3 py-2 border border-gray-300 leading-none whitespace-nowrap">삭제</button>
              </div>
            </div>
          </div>
          {/* <div className="border flex-1 text-[15px]">
            <button className="border border-gray-500 px-2 py-3 leading-none">수정</button>
            <button className="border border-gray-500 px-2 py-3 leading-none">삭제</button>
          </div> */}
        </div>
      </div>
    ))}
  </>
)}

2. 리뷰내역 정보 ui 작업 및 페이징 기능 추가

 // 이전 페이지
const handlePrev = () => {
  if (page > 0) {
    setPage(page - 1);
    fetchBookReview(currentFilter, page - 1);
  }
}

// 다음 페이지
const handleNext = () => {
  if (page < totalPages - 1) {
    setPage(page + 1);
    fetchBookReview(currentFilter, page + 1);
  }
}

// 페이지 번호
const handlePageClick = (pageNumber) => {
  setPage(pageNumber);
}


{/* 페이징 */}
<div className="mt-2">
  <button
    onClick={ handlePrev }
    disabled={page === 0 || totalPages === 0}
    className="px-3 py-1 cursor-pointer disabled:opacity-50"
  >이전</button>
  {[...Array(totalPages)].map((_, i) => (
    <button
      key={i}
      onClick={() => handlePageClick(i)}
      className={`px-[10px] py-[6px] mx-[3px] text-base cursor-pointer ${
        i === page ? "text-teal-600 font-bold" : ""
      }`}
    >{i + 1}</button>
  ))}
  <button
	  onClick={ handleNext }
    disabled={page === totalPages - 1 || totalPages === 0}
    className="px-3 py-1 cursor-pointer disabled:opacity-50"
  >이후</button>
</div>

3. 특정 리뷰 클릭 시 모달창으로 상세내용 처리

- tailwind에서 제공하는 modal 사용

<div class="flex flex-col w-full mt-3 text-center sm:mt-0 sm:text-left">
  <h3 id="dialog-title" class="text-base font-semibold text-gray-900">리뷰 상세</h3>
  {/* 내용 */}
  <div class="flex flex-col items-start mt-2">
    {/* 도서정보 */}
    <div class="flex flex-row items-start text-sm text-gray-500">
      {/* 썸네일 */}
      <div className="flex w-[100px] min-w-[100px] items-start shrink-0">
        <img src="https://placehold.co/420x600" alt="" className="w-auto block h-auto" />
      </div>
      <div className="flex flex-col justify-start self-start w-full min-w-0 leading-6 text-[15px] ml-2 text-gray-700">
        <div className="font-bold text-black">책제목</div>
        <div className="text-black">저자</div>
        <div className="text-black">출판사</div>
        <div className="text-black">출판일</div>
        <div className="text-black">ISBN</div>
      </div>
    </div>
    
    {/* 리뷰내용 */}
    <div className="flex flex-row w-full mt-2 leading-6 overflow-hidden items-center">
      <div className="w-full min-w-0 px-2 py-1 h-[82px] border border-gray-200 rounded line-clamp-3">
        리뷰내용
      </div>
    </div>
  </div>
</div>

<div class="px-4 pb-7 sm:pb-5 sm:flex sm:flex-row-reverse sm:px-6">
	<button 
	type="button" command="close" commandfor={id} 
	class="
	inline-flex w-full justify-center rounded-md 
	bg-teal-600 hover:bg-teal-700 
	text-sm font-semibold text-white shadow-xs 
	px-3 py-2 sm:ml-3 sm:w-auto
	"
	>변경</button>
	<button type="button" command="close" commandfor={id} class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs inset-ring inset-ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto">취소</button>
</div>

4. 본인 작성 리뷰 전체 목록과 상세 조회 시 응답 DTO 필드 수정

- 리뷰 전체 목록 응답 DTO

public class UserReviewListResDTO {
  private Long id;
  private String bookTitle;
  private String content;
  private LocalDateTime createdDate;
  private LocalDateTime updatedDate;

  public UserReviewListResDTO(Review review) {
    this.id = review.getId();
    this.bookTitle = review.getBook().getTitle();
    this.content = review.getContent();
    this.createdDate = review.getCreatedDate();
    this.updatedDate = review.getUpdatedDate();
  }
}

 

- 리뷰 상세 응답 DTO

public class UserReviewDetailResDTO {
  private Long id;
  private String bookTitle;
  private String author;
  private String publisher;
  private LocalDate publishedDate;
  private String isbn;
  private String content;
  private LocalDateTime createdDate;
  private LocalDateTime updatedDate;

  public UserReviewDetailResDTO(Review review) {
    this.id = review.getId();
    this.bookTitle = review.getBook().getTitle();
    this.author = review.getBook().getAuthor();
    this.publisher = review.getBook().getPublisher();
    this.publishedDate = review.getBook().getPublishedDate();
    this.isbn = review.getBook().getIsbn();
    this.content = review.getContent();
    this.createdDate = review.getCreatedDate();
    this.updatedDate = review.getUpdatedDate();
  }
}

5. 리뷰내역 수정 API 연결

- 리뷰 수정 API

// 리뷰 수정 api
const fetchUpdateReview = async () => {
  try {
    const response = await axios.patch(
      `http://localhost:8080/api/v1/user/me/reviews/${reviewId}`,
      { content: updateReview, },
      { withCredentials: true, }
    );

    console.log("리뷰 수정 성공");
    alert("리뷰 수정 성공했습니다.");
    return response;
  } catch (error) {
    console.log("Error: ", error.response);
    setError(error.response);
  } finally {
    console.log("리뷰 수정 완료");
  }
}

 

- 닫기 버튼 클릭 시 리뷰 이전 내용 저장

// 닫기 버튼
const handleClose = () => {
  setUpdateReview(originReview);
}

 

- 리뷰 후 수정 버튼 클릭 시 DB에 내용 저장 및 리뷰 전체 목록 새로고침

// 리뷰 수정 버튼
const handleUpdateReview = async () => {
  await fetchUpdateReview();
  await fetchBookReview();

  document.getElementById(id)?.close();
}